从大端数据中提取记录

Extracting record from big endian data

我有以下网络协议实现代码。由于协议是大端,我想使用 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;

编译没问题,结果是

raised CONSTRAINT_ERROR : bit_extraction.adb:39 invalid data

我忘记或误解了什么?

更新

其实真正的记录是

type Frame is record
      Transaction_Id : Transaction_Identifier;
      Protocol_Id : Word := 0;
      Frame_Length : Length;
      Unit_Id : Unit_Identifier;
      Function_Code : Command;
      Is_Exception : Boolean := False;    
end record with Size => 8 * 8, Pack => True;

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;

其中Transaction_IdentifierWordLength为16位宽.

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

要解码的帧转储如下:

00000000  00 01 00 00 00 09 11 03  06 02 2b 00 64 00 7f

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

看看这个 AdaCore post on bit order and byte order 看看他们是如何处理的。看完之后你可能会发现你的帧值的位序真的是16#08#,这可能不是你所期望的。

Big Endian / Little Endian 通常指的是字节序而不是位序,所以当你看到网络协议是Big Endian 时,它们指的是字节序。避免为您的记录设置 Bit_Order。在现代系统中,您几乎永远不需要它。

您的记录只有一个字节大小,因此字节顺序本身并不重要。当您有更大的字段值(>8 位长)时,字节顺序开始发挥作用。

所以,

    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;

您似乎希望 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;

根据上面 Command 的定义,最后一个字节的合法值将是:

  • 2 -> READ_COILS 错误
  • 3 -> READ_COILS 真
  • 8 -> READ_DISCRETE_INPUTS 错误
  • 9 -> READ_DISCRETE_INPUTS 真

顺便说一句, 通过将您的更新应用于您的原始程序,以及 adding/changing 以下内容,您的程序对我有用

添加

    with Interfaces;

添加

    type Byte_Array is array(1..8) of Byte with Pack;

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

    Transaction_ID : Interfaces.Unsigned_16;
    Protocol_ID : Interfaces.Unsigned_16; 
    Frame_Length : Interfaces.Unsigned_16;
    Unit_ID : Interfaces.Unsigned_8;

改变

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

改变

    my_frame := To_Frame (Byte_Array'(00, 01, 00, 00, 00, 09, 16#11#, 16#9#));

您的原始记录声明工作正常(GNAT 抱怨 Packwarning: pragma Pack has no effect, no unplaced components)。问题在于计算 little-endian Byte.

---------------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |    BE bit numbers
---------------------------------
| c   c   c   c   c   c   c | e |
---------------------------------
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |    LE bit numbers
---------------------------------

因此,如果您希望 CommandRead_Discrete_Inputs,则 Byte 需要设置 BE 位 4(LE 位 3),即 LE 16#8#

终于找到问题了

其实就是Modbus Ethernet Frame definition mentioned that, in case of exception, the returned code should be the function code plus 128 (0x80) (see explanation on Wikipedia)。这就是为什么我想通过 布尔值 值来表示它但我的表示子句是错误的。

正确的从句是这些:

   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;

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

非常感谢 and 让我找到问题所在并解释方面背后的语义。

很明显,不知道打赏的是谁:)

bit_order pragma 不会颠倒位在内存中出现的顺序。它简单地定义了在解释 [=17] 时最高有效位(最左边)在逻辑上被称为零(High_Order_First)还是最低有效位被称为零(Low_Order_First) =] 和 Last_Bit 从表示子句中的字节位置偏移。请记住,这些偏移量取自记录组件所属的标量的 MSB 或 LSB 作为一个值。因此,为了使字节位置在小端 CPU 上具有与在大端 CPU 上相同的含义(以及多字节机器标量的内存表示,当一个或多个具有相同字节位置的记录组件具有超过单个字节容量的 last_bit 值)然后 'Scalar_Storage_Order 必须 also 被指定。