Ada - 使用 ttyUSB 设备(FTDI 芯片)时的计时问题

Ada - timing problem when using ttyUSB device (FTDI chip)

我在使用 "GNAT.Serial_Communications" 包的简单 Ada 程序中遇到了一个奇怪的问题。

该程序通过串行端口设备 (8,N,1 @ 115200 波特) 发送七个字节 (126, 1, 0, 0, 0, 0, 254),我已验证此操作正确执行使用直接连接到串行端口引脚的逻辑分析仪。

奇怪的是,发送的前三个字节之间有两个很大的间隔(在 0.6 毫秒和 2.4 毫秒之间变化)。其余字节以预期的最大速度发送。每个单独的字节都完美地形成并以正确的速率发送。字节之间的空闲时间是变化的部分。换句话说,时间看起来是这样的...

[126]..........(big gap)..........[1]..........(big gap)..........[0][0][0][0][254]

我有这个程序的 C 版本,它没有表现出这种行为,所有字节都在一次突发中发送,没有间隙。

同样,cat将相同的数据从Bash直接输入串口也不会在前三个字节之间有这么大的差距。

看来 Ada 库/运行时的某些方面在我的字节流中间引入了这种可变延迟。

对于我的项目,这是一个可以容忍的问题,但我想尝试找出发生这种情况的原因。

从串口读取字节显然没有问题,它正确接收了所有内容。

with Interfaces; use Interfaces;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Streams; use Ada.Streams;
with GNAT.Serial_Communications;

procedure Main is
   SP : GNAT.Serial_Communications.Serial_Port;

   In_Data : String (1 .. 6) := "~ddddS"; --  this string literal is illustrative only
   In_Buffer : Stream_Element_Array (1 .. 6);
   Length : Stream_Element_Offset;

   type PropIO_Octet is mod (2 ** 8);
   type PropIO_Packet_Data is array (1 .. 4) of PropIO_Octet;

   Packet_Start :         constant PropIO_Octet := 16#7E#;
   Packet_Escape_Marker : constant PropIO_Octet := 16#7D#;
   Packet_Escape_XOR :    constant PropIO_Octet := 16#20#;

   Command_Hard_Reset : constant PropIO_Octet := 16#00#;
   Command_Watchdog :   constant PropIO_Octet := 16#01#;


   procedure TX_Octet (Octet : in PropIO_Octet; Allow_Escape : in Boolean) is
      Out_Buffer : Stream_Element_Array (1 .. 1);
      Temp_Oct : PropIO_Octet := Octet;
   begin
      if Allow_Escape then
         if (Temp_Oct = Packet_Start) or (Temp_Oct = Packet_Escape_Marker) then
            Out_Buffer (Stream_Element_Offset (1)) := Character'Pos (Character'Val (Packet_Escape_Marker));

            GNAT.Serial_Communications.Write (SP, Out_Buffer);

            Temp_Oct := Temp_Oct xor Packet_Escape_XOR;
         end if;
      end if;

      Out_Buffer (Stream_Element_Offset (1))
        := Character'Pos (Character'Val (Temp_Oct));

      GNAT.Serial_Communications.Write (SP, Out_Buffer);
   end TX_Octet;


   procedure TX_Packet (
     Command : in PropIO_Octet;
     Data : in PropIO_Packet_Data
     ) is

      CS : PropIO_Octet := 16#00#;
   begin
      TX_Octet (Packet_Start, False);

      TX_Octet (Command, True);
      CS := CS xor Command;

      for D of Data loop
         TX_Octet (D, True);
         CS := CS xor D;
      end loop;

      CS := 16#FF# - CS;
      TX_Octet (CS, True);

   end TX_Packet;


begin
   GNAT.Serial_Communications.Open(SP, "/dev/ttyUSB0");

   GNAT.Serial_Communications.Set (
      Port      => SP,
      Rate      => GNAT.Serial_Communications.B115200,
      Bits      => GNAT.Serial_Communications.CS8,
      Stop_Bits => GNAT.Serial_Communications.One,
      Parity    => GNAT.Serial_Communications.None
      );

   for i in 1 .. 10 loop
      TX_Packet (Command_Watchdog, (0,0,0,0));
      delay 1.0;

      GNAT.Serial_Communications.Read (SP, In_Buffer, Length);

      for i in 1 .. Length loop
         In_Data (Integer (i)) := Character'Val (In_Buffer (i));
      end loop;

      Put_Line ("Response: " & In_Data (1 .. (Integer (Length))));
   end loop;

   GNAT.Serial_Communications.Close (SP);
end Main;

我 运行 此代码在 Ubuntu 19.10(最新更新)和 Gnat 8.3.0 下。通过USB连接的串口设备是合法的FTDI设备。

知道是什么原因造成的吗?

这不是问题的实际答案(我猜答案已经在评论中了),而只是关于示例代码的提示。由于类型GNAT.Serial_Communications.Serial_Port源自Ada.Streams.Root_Stream_Type,您还可以使用面向流的属性'Read'Write来读写串口。例如:

main.adb

with Ada.Text_IO;
with GNAT.Serial_Communications;

procedure Main is

   package SC renames GNAT.Serial_Communications;

   type Byte is mod 2**8;
   type Byte_Array is array (Natural range <>) of Byte;

   package Byte_IO is
      new Ada.Text_IO.Modular_IO (Byte);

   Port_1 : aliased SC.Serial_Port;
   Port_2 : aliased SC.Serial_Port;

   End_Token : constant := 16#FF#;

   Buffer_Out : Byte_Array (0 .. 5) := (0, 1, 2, 3, 4, End_Token);
   Buffer_In  : Byte;

begin

   --  Open the ports.
   SC.Open (Port_1, "/dev/ttyS98");
   SC.Open (Port_2, "/dev/ttyS99");

   --  Write the byte array (packet) to port 1 in one go.
   Byte_Array'Write (Port_1'Access, Buffer_Out); 

   --  Read the byte array (packet) back from port 2, byte-by-byte.
   loop

      Byte'Read (Port_2'Access, Buffer_In);
      exit when Buffer_In = End_Token;

      Byte_IO.Put (Buffer_In);

   end loop;

   Ada.Text_IO.New_Line;

   --  Close the ports.
   SC.Close (Port_1);
   SC.Close (Port_2);

end Main;

可以使用socat(和伪终端,pty)模拟两个串口来测试程序。

console 1(创建模拟串口)

$ sudo socat -d -d pty,raw,echo=0,link=/dev/ttyS98 pty,raw,echo=0,link=/dev/ttyS99
2020/01/14 20:23:04 socat[2540] N PTY is /dev/pts/1
2020/01/14 20:23:04 socat[2540] N PTY is /dev/pts/2
2020/01/14 20:23:04 socat[2540] N starting data transfer loop with FDs [5,5] and [7,7]

控制台 2(运行 示例程序)

$ sudo ./main 
   0   1   2   3   4

注意:需要 sudo 才能创建和访问模拟设备。