我必须在RS-232上实现一个相对简单的通信协议。它是一个基于ASCII的文本协议,具有几种帧类型。
每个帧看起来都像这样:
* ___________________________________
* | | | | |
* | SOH | Data | CRC-16 | EOT |
* |_____|_________|_________|________|
* 1B nBytes 2B 1B
1.标头开始(1字节)
1.数据(n字节)
- CRC-16(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协议?
3条答案
按热度按时间kkbh8khc1#
我的问题是用C语言实现这个(基于ASCII文本的协议)的最佳方式是什么?
char
)作为十进制文本发送。同样,对于浮点,请使用指数表示法和足够的精度。例如sprintf(buf, "%.*e", DBL_DECIMAL_DIG-1, some_double);
。允许"%a"
表示法。SOH
和EOT
使用相同的代码。不同的值可减少接收器混淆。"2022-11-10"
、"23:38:42"
。"
的字符串。转义不可打印的ASCII字符,以及"
、\
、;
。10个长字符串的示例为123\\;\"\xFF456
--〉"123\\\;\"\xFF456"
。请参阅What type of framing to use in serial communication了解更多信息。
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年前就过时了。
v2g6jxz63#
你的struct实现缺少的一件事就是打包。出于效率的考虑,根据你的代码运行在哪个处理器上,编译器会在结构中添加填充来对齐特定的字节边界。通常这不会对你的代码产生太大的影响,但是如果你通过串行流发送数据,每个字节都很重要,那么你也会发送随机的零。
本文很好地解释了填充,以及如何为像您这样的用例打包结构
Structure Padding