从大端数据提取记录

时间:2019-06-18 19:55:11

标签: 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;

编译可以,但是结果是

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_Identifier Word Length 的宽度为16位。

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

要解码的帧的转储如下:

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

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

5 个答案:

答案 0 :(得分:3)

看看这个AdaCore post on bit order and byte order,看看他们如何处理。阅读这些内容之后,您可能会发现帧值的位顺序实际上是16#08#,可能不是您所期望的。

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

您的记录只有一个字节,因此字节顺序对其本身并不重要。当字段值较大(> 8位长)时,字节顺序起作用。

答案 1 :(得分:3)

所以

for Frame'Bit_Order use System.High_Order_First;

似乎您希望Is_Exception是最后一个字节的LSB? 使用16#32#,最低有效位(LSB)将是第7位,

(而且-- 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

鉴于上面 with Interfaces; 的定义,最后一个字节的合法值为:

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

顺便说一句, 通过将您的更新应用于原始程序,并添加/更改以下内容,您的程序就可以为我工作

添加

    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#));

更改

$js .= '"createdRow": function( row, data, dataIndex){
              $(row).addClass(\'redClass\');
              }';
    $js.='customize: function(xlsx){
            var sheet = xlsx.xl.worksheets["sheet1.xml"];
            $("row c[r*="10"]",sheet).attr("s","25"); 
            },';

答案 2 :(得分:3)

您的原始记录声明工作正常(GNAT抱怨Packwarning: pragma Pack has no effect, no unplaced components)。问题在于计算小端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#

答案 3 :(得分:3)

我终于找到了问题所在。

实际上,Modbus Ethernet Frame definition提到,在发生异常的情况下,返回的代码应为功能代码加128(0x80)(请参阅explanation on Wikipedia)。这就是为什么我想通过 Boolean 值来表示它但我的表示子句错误的原因。

正确的子句是这些:

   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网络协议进行建模(或者至少可以,但我的代码正在运行)。

我非常感谢egilhhsimonwright使我发现问题所在并解释了各个方面的语义。

很明显,我不知道是谁奖励的:)

答案 4 :(得分:0)

bit_order编译指示不会颠倒位在内存中出现的顺序。它仅定义当解释表示形式中字节位置的First_Bit和Last_Bit偏移时,是将最高有效位(最左端)逻辑上称为零(High_Order_First),还是将最低有效位称为零(Low_Order_First)。条款。请记住,这些偏移量取自记录组件属于AS A VALUE的标量的MSB或LSB。因此,为了使字节位置在小字节序CPU上具有与在大字节序CPU上相同的含义(以及多字节机器标量的内存表示形式,当一个或多个记录组件具有相同字节时存在)位置的last_bit值超过单个字节的容量),则必须 指定“ Scalar_Storage_Order”。