如何用C语言编写基于ASCII文本的RS-232协议

gfttwv5a  于 2022-12-03  发布在  其他
关注(0)|答案(3)|浏览(158)

我必须在RS-232上实现一个相对简单的通信协议。它是一个基于ASCII的文本协议,具有几种帧类型。
每个帧看起来都像这样:

* ___________________________________
 * |     |         |         |        |
 * | SOH |   Data  | CRC-16  | EOT    |
 * |_____|_________|_________|________|
 *   1B    nBytes      2B       1B

1.标头开始(1字节)
1.数据(n字节)

  1. CRC-16(2个字节)
  2. EOT(传输结束)
    每个数据字段需要用分号“;”分隔;“:例如,对于HEADER类型数据(包含代码、版本、时间、日期、源、id 1、id 2值):
    {代码};{版本};{时间};{日期};{源代码};{id 1};{标识2}
    我的问题是用C语言实现这一点的最佳方式是什么?
    我尝试为每种类型的框架定义多个结构体,例如:
typedef struct {
    uint8_t soh;
    char code;
    char ver;
    Time_t time;
    Date_t date;
    char src; // Unsigned char
    char id1[20]; // STRING_20
    char id2[20]; // STRING_20
    char crlf;
    uint16_t crc;
    uint8_t eot;
} stdHeader_t;

我已经声明了一个全局缓冲区:

uint8_t DATA_BUFF[BUFF_SIZE];

然后我有一个函数sendHeader(),我想在其中使用RS-232 send函数,通过将dataBuffer转换为头结构体并填充该结构体,逐字节发送所有内容:

static enum_status sendHeader(handle_t *handle)
{
    uint16_t len;
    enum_RETURN_VALUE rs232_err = OK;
    enum_status err = STATUS_OK;

    stdHeader_t *header = (stdHeader_t *)DATA_BUFF;

    memset(DATA_BUFF, 0, size);

    header ->soh= SOH,
    header ->code= HEADER,
    header ->ver= 10, // TODO
    header ->time= handle->time,
    header ->date= handle->date,
    header ->src= handle->config->source,
    memset(header ->id1,handle->config->id1, strlen(handle->config->id1));
    memset(header ->id2,handle->config->id2, strlen(handle->config->id1));
    header ->crlf = '\r\n',
    header ->crc  = calcCRC();
    header ->eot = EOT;

    len = sizeof(stdHeader_t );

    do
    {
        for (uint16_t i = 0; i < len; i++) 
        {
            rs232_err= rs232_tx_send(DATA_BUFF[i], 1); // Send one byte
            if (rs232_err!= OK)
            {
                err = STATUS_ERR;
                break;
            }
        }
        // Break do-while loop if there is an error
        if (err == STATUS_ERR)
        {
            break;
        }
    } while (conditions); 

    return err;
}

我的问题是我不知道如何处理基于ascii文本的协议的问题,上述原则将非常适合基于字节的协议。
还有,不知分号如何实现“;“上述片段中的数据分离,因为所有内容都是逐字节发送的,我需要附加逻辑来了解何时需要发送“;“从目前执行情况来看,这看起来并不太好。
对于字段id 1和id 2,我将字符串值作为handle-〉config的一部分接收,它们可以是任何长度,但最大值是20。因此,在当前实现中,如果实际长度小于20,我将发送比所需更多的值,但我不能在结构体中使用指向char的指针,因为在这种情况下,将只发送指针值。
总结一下,主要问题是:
如何以一种好的和适当的方式实现上述基于文本的rs-232协议?

kkbh8khc

kkbh8khc1#

我的问题是用C语言实现这个(基于ASCII文本的协议)的最佳方式是什么?

  • 由于这是ASCII,因此避免尝试Map多字节整数的字节序问题。只需将整数(包括char)作为十进制文本发送。同样,对于浮点,请使用指数表示法和足够的精度。例如sprintf(buf, "%.*e", DBL_DECIMAL_DIG-1, some_double);。允许"%a"表示法。
  • 请勿对SOHEOT使用相同的代码。不同的值可减少接收器混淆。
  • 使用ISO 8601作为参考发送日期和时间。例如"2022-11-10""23:38:42"
  • 发送前导/尾随为"的字符串。转义不可打印的ASCII字符,以及"\;。10个长字符串的示例为123\\;\"\xFF456--〉"123\\\;\"\xFF456"
  • 疯狂地对接收到的数据进行错误检查。出于各种原因拒绝数据包:字段计数错误、字符串太长、值超出字段范围、CRC错误、超时、接收到任何非ASCII字符。
  • CRC使用ASCII十六进制字符:4个十六进制字符而不是2个字节。
  • 考虑CRC 32或64。
  • 任何带外输入(在接收SOF之前的字节)都会被静默丢弃。这很好地允许在每个命令之后有一个可选的LF。
  • 考虑SOH/EOT之间的 * 唯一 * 字符应 * 可打印 * ASCII:32-126.必要时逃离他人。
  • 由于“它是一个基于ASCII的文本协议,具有几种帧类型",因此我希望输入一个 type 字段。

请参阅What type of framing to use in serial communication了解更多信息。

u4vypkhs

u4vypkhs2#

首先,结构体并不适合表示数据协议。示例中的结构体到处都充满了填充字节,因此它不是一个合适的、可移植的协议表示形式。特别是,不要再考虑将结构体转换为原始uint8_t数组或从原始uint8_t数组转换为结构体a-因为更多的原因,这是有问题的:第一地址对齐和指针混淆。
如果您坚持使用结构,则必须编写序列化/反序列化例程,这些例程将每个成员手动复制到原始uint8_t缓冲区(实际传输必须使用该缓冲区)或从该缓冲区手动复制。
(反)序列化例程可能不是一个坏主意,因为您的文章中没有解决另一个问题:RS-232协议传统上几乎都是Big Endian,但不要指望它--必须明确地记录字节序。
我的问题是我不知道如何处理基于ascii文本的协议的问题,上述原则将非常适合基于字节的协议。
与上面的相比,这是一个小问题。通常可以接受原始数据(基本上除了数据有效载荷之外的所有内容)和ASCII文本的混合。如果你想要一个纯ASCII协议,你可以考虑像“AT命令”这样的协议,但是它们没有太多的错误处理方式。你真的 * 应该 * 有一个CRC 16以及同步字节。提示:我最好选择第一个同步字节不匹配7位ASCII码。这是与MSB设置。0xAA是流行的。
一旦您整理好了数据序列化、字节序和协议结构,您就可以开始考虑诸如有效负载部分中的字符串处理之类的细节了。
最后,RS232是恐龙的东西。没有多少理由不应该使用RS422/RS485。使用RS232的最后一个论点,“计算机带有RS232 COM端口”,在大约15-20年前就过时了。

v2g6jxz6

v2g6jxz63#

你的struct实现缺少的一件事就是打包。出于效率的考虑,根据你的代码运行在哪个处理器上,编译器会在结构中添加填充来对齐特定的字节边界。通常这不会对你的代码产生太大的影响,但是如果你通过串行流发送数据,每个字节都很重要,那么你也会发送随机的零。
本文很好地解释了填充,以及如何为像您这样的用例打包结构
Structure Padding

相关问题