从大端数据中提取记录

Fré*_*aca 5 ada endianness gnat

我有以下用于网络协议实现的代码。由于协议是大端,我想使用Bit_Order属性和High_Order_First值,但似乎我犯了一个错误。

With Ada.Unchecked_Conversion;
with Ada.Text_IO; use Ada.Text_IO;
with System; use System;

procedure Bit_Extraction is

   type Byte is range 0 .. (2**8)-1 with Size => 8;

   type Command is (Read_Coils,
                    Read_Discrete_Inputs
                   ) with Size => 7;

   for Command use (Read_Coils => 1,
                    Read_Discrete_Inputs => 4);

   type has_exception is new Boolean with Size => 1;

    type Frame is record
      Function_Code : Command;
      Is_Exception : has_exception := False;
   end record
     with Pack => True,
     Size => 8;

   for Frame use
      record
         Function_Code at 0 range 0 .. 6;
         Is_Exception at 0 range 7 .. 7;
      end record;

   for Frame'Bit_Order use High_Order_First;
   for Frame'Scalar_Storage_Order use High_Order_First;

   function To_Frame is new Ada.Unchecked_Conversion (Byte, Frame);

   my_frame : Frame;
begin
   my_frame := To_Frame (Byte'(16#32#)); -- Big endian version of 16#4#
   Put_Line (Command'Image (my_frame.Function_Code)
             & " "
             & has_exception'Image (my_frame.Is_Exception));
end Bit_Extraction;
Run Code Online (Sandbox Code Playgroud)

编译没问题,但结果是

raised CONSTRAINT_ERROR : bit_extraction.adb:39 invalid data
Run Code Online (Sandbox Code Playgroud)

我忘记或误解了什么?

更新

真正的记录实际上是

raised CONSTRAINT_ERROR : bit_extraction.adb:39 invalid data
Run Code Online (Sandbox Code Playgroud)

其中Transaction_IdentifierWordLength是 16 位宽。

如果我删除Is_Exception字段并将Function_Code扩展到 8 位,这些将正确显示。

要解码的帧的转储如下:

00000000  00 01 00 00 00 09 11 03  06 02 2b 00 64 00 7f
Run Code Online (Sandbox Code Playgroud)

所以我唯一的问题是提取最后一个字节的第 8 位。

egi*_*lhh 5

所以,

    for Frame use
      record
         Transaction_Id at 0 range 0 .. 15;
         Protocol_Id at 2 range 0 .. 15;
         Frame_Length at 4 range 0 .. 15;
         Unit_id at 6 range 0 .. 7;
         Function_Code at 7 range 0 .. 6;
         Is_Exception at 7 range 7 .. 7;
      end record;
Run Code Online (Sandbox Code Playgroud)

您似乎希望 Is_Exception 成为最后一个字节的 LSB?随着for Frame'Bit_Order use System.High_Order_First;LSB的将是第7位,

(而且,16#32#永远不会-- Big endian version of 16#4#,位模式只是不匹配)

相对于它们所在的单词而不是字节指定所有字段可能更直观和清晰:

         Unit_ID at 6 range 0..7;
         Function_Code at 6 range 8 .. 14;
         Is_Exception at 6 range 15 .. 15;
Run Code Online (Sandbox Code Playgroud)

鉴于上述定义,Command最后一个字节的合法值将是:

  • 2 -> READ_COILS FALSE
  • 3 -> READ_COILS 真
  • 8 -> READ_DISCRETE_INPUTS FALSE
  • 9 -> READ_DISCRETE_INPUTS 真

顺便说一句,通过将您的更新应用到您的原始程序,并添加/更改以下内容,您的程序对我有用

添加

    with Interfaces;
Run Code Online (Sandbox Code Playgroud)

添加

    type Byte_Array is array(1..8) of Byte with Pack;
Run Code Online (Sandbox Code Playgroud)

改变,因为我们不知道定义

    Transaction_ID : Interfaces.Unsigned_16;
    Protocol_ID : Interfaces.Unsigned_16; 
    Frame_Length : Interfaces.Unsigned_16;
    Unit_ID : Interfaces.Unsigned_8;
Run Code Online (Sandbox Code Playgroud)

改变

    function To_Frame is new Ada.Unchecked_Conversion (Byte_Array, Frame);
Run Code Online (Sandbox Code Playgroud)

改变

    my_frame := To_Frame (Byte_Array'(00, 01, 00, 00, 00, 09, 16#11#, 16#9#));
Run Code Online (Sandbox Code Playgroud)


Fré*_*aca 5

我终于发现哪里不对劲了。

实际上,Modbus Ethernet Frame 定义中提到,如果出现异常,返回的代码应该是功能代码加 128 (0x80)(参见维基百科上的解释)。这就是为什么我想通过布尔值来表示它但我的表示子句是错误的原因。

正确的条款是这些:

   for Frame use
      record
         Transaction_Id at 0 range 0 .. 15;
         Protocol_Id at 2 range 0 .. 15;
         Frame_Length at 4 range 0 .. 15;
         Unit_id at 6 range 0 .. 7;
         Is_Exception at 6 range 8 .. 8;
         Function_Code at 6 range 9 .. 15;
      end record;
Run Code Online (Sandbox Code Playgroud)

这样,Modbus 网络协议就被正确建模(或者没有,但至少,我的代码正在工作)。

我真的很感谢egilhhsimonwright让我找出问题所在并解释方面背后的语义。

显然,我不知道谁奖励:)