C语言串口组包方式

       在软件开发过程中,串口读写功能应该是必修课,读写内容会包含字符串、16进制数组等,而且会涉及到通信的数据格式,针对串口的通信功能来说,个人认为串口的读还是较为有点难度,因为涉及到数据的解析,串口有时候读取的数据并不是一条完整的数据,例如,一条完整的数据内容为:0xaa 0xbb 0xcc 0xdd 0x99 0x88 0x77 0x66 0x00 0x78 0x16 0x80,但往往在串口的读数据时,会出现一包无法读取完毕的现象,很可能上述的数据会分两次读取完毕,比如第一包读取内容为:0xaa 0xbb 0xcc 0xdd 0x99 0x88,第二包读取内容为:0x77 0x66 0x00 0x78 0x16 0x80,此种情况我们必须要对读取到的数据进行组包。字符串的组包方式可以根据字符串的格式来进行,本文只介绍16进制方式的组包。

状态机方式

适用于有限的条件【数据内容的长度一定要知道】,状态机的方式比较通用,可以根据数据格式来确认数据解包时的状态,最终实现解析一个完整的数据包;实例参照如下:

数据格式定义

数据通信协议

类型

内容

字节数

说明

消息头

Head

2

消息包头(0xFF 0xEE)

Len

2

整包数据总长度(11+N)

ID

2

识别设备类型(型号)

CMD

2

指令类型

reserve

2

保留位(0x00 0x00)

数据体

Data

N

 

校验

CHK

1

CRC8校验

如上为数据格式的定义,只有数据头,没有数据尾,而且在数据内容上也没有考虑与消息头相同的情况,后续还需要优化。下面对代码进行展开描述:

代码:

/*
* 接收到串口数据后的处理函数
*
*/
void handle_recv_data(const unsigned char * buf, int len)
{
    int i = 0;
    RLOGD("==== %s ==== len:%d\n", __func__, len);

    util_hexdump('&', dump_hexstr, (unsigned char*)buf, len);
    util_hexdump('#', dump_hexstr, (unsigned char*)serial_data.recvBuf, serial_data.pos);
    while(len > 0)
    {
        //RLOGD("%s serial_data.stat:%d,len:%d  i:%d, pos:%d\n", __func__, serial_data.stat, len, i, serial_data.pos);
        //util_hexdump('Q', dump_hexstr, serial_data.recvBuf, serial_data.pos);
        //判断当前的状态机状态
        switch(serial_data.stat)
        {
            case TYPE_HEADER://消息头
                if(buf[i] == 0xEB && buf[i+1] == 0x90)
                {
                    serial_data.stat = TYPE_LEN;
                    memcpy(serial_data.recvBuf, buf + i, 2);

                    i += 2;
                    serial_data.pos += 2;
                    len -= 2;
                }
                else
                {
                    len--;
                    i++;
                }
                continue;
            case TYPE_LEN://消息总长度
                if(len < 2)
                {
                    serial_data.recvBuf[i] = buf[i];
                    serial_data.pos++;
                    i++;
                }
                else
                {
                    //printf("%s, serial_data.pos:%d\n", __func__, serial_data.pos);
                    if(serial_data.pos == 2)//recvBuf:0xFF, 0xEE
                    {
                        serial_data.stat = TYPE_DATA;
                        memcpy(serial_data.recvBuf + 2, buf + i, 2);
                        serial_data.pos += 2;

                        //printf("%s, buf[%d]:0x%x, buf[%d]:0x%x\n", __func__, i, buf[i], i+1, buf[i+1]);
                        serial_data.dataLen = charArray2Short((unsigned char *)buf, i, i+1);
                        len -= 2;
                        i += 2;
                    }
                    else//recvBuf:0xFF, 0xEE, 0x00
                    {
                        serial_data.stat = TYPE_DATA;//更新状态机
                        serial_data.recvBuf[i] = buf[i];//缓存当前buf到缓存recvBuf中,此处recvBuf更新后0xff 0xee 0x00 0xXX,前四个字节已经填充完毕
                        //更新结构体下标
                        serial_data.pos++;
                        //更新当前串口数据的长度;
                        serial_data.dataLen = charArray2Short((unsigned char *)buf, i, i+1);
                        len--;
                        i++;
                    }
                    RLOGD("%s, serial_data.dataLen:%d\n", __func__, serial_data.dataLen);
                }
                continue;
            case TYPE_DATA://状态机为data
                //printf("%s, serial_data.pos:%d, serial_data.dataLen:%d\n", __func__, serial_data.pos, serial_data.dataLen);
                if(serial_data.pos > (serial_data.dataLen - 1) && len > 1)
                {
                    util_hexdump('*', dump_hexstr, (unsigned char*)serial_data.recvBuf, serial_data.pos);
                    //已经组成完整的数据包:0xff 0xee 数据长度 .... CRC
                    handle_recv_master_cmd((const unsigned char*)serial_data.recvBuf, serial_data.dataLen);
                    // next loop
                    serial_data.stat = TYPE_HEADER;//更新状态机,初始化消息头状态,继续下个循环
                    serial_data.dataLen = 0;
                    serial_data.pos = 0;
                    memset(serial_data.recvBuf, 0, sizeof(serial_data.recvBuf));
                }
                else
                {//数据内容的拷贝
                    serial_data.recvBuf[serial_data.pos] = buf[i];
                    i++;
                    serial_data.pos++;
                    len--;
                }

                continue;
            default:
                RLOGD("%s, error serial data!\n", __func__);
                break;
        }

    }
    //最后一个字节刚好结束循环,因此在结束循环后再次判断数据内容的完整度
    if(serial_data.pos > 0 && (serial_data.pos > (serial_data.dataLen - 1)))
    {
        util_hexdump('G', dump_hexstr, (unsigned char*)serial_data.recvBuf, serial_data.pos);
        handle_recv_master_cmd((const unsigned char*)serial_data.recvBuf, serial_data.dataLen);
        // next loop
        serial_data.stat = TYPE_HEADER;
        serial_data.dataLen = 0;
        serial_data.pos = 0;
        memset(serial_data.recvBuf, 0, sizeof(serial_data.recvBuf));
    }



}
//结构体的定义与初始化
enum
{
    TYPE_HEADER,
    TYPE_LEN,
    TYPE_DATA,
};

struct _SERIAL_DATA
{
    int stat;
    unsigned char recvBuf[1024];
    int pos;
    int dataLen;
};

struct _SERIAL_DATA serial_data;

int mcu_uart_com_init()
{
    //初始化结构体
    memset(&serial_data, 0, sizeof(struct _SERIAL_DATA));
    serial_data.stat = TYPE_HEADER;
    serial_data.dataLen = 0;
    serial_data.pos = 0;
    memset(serial_data.recvBuf, 0, sizeof(serial_data.recvBuf));
}

 

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: C语言是一种高级编程语言,可以用来开发各种应用程序。其中,串口通信是一种常见的通信方式,可以用于智能家居、工业自动化等领域。 串口通信有两个关键因素:波特率和数据位。波特率指的是单位时间内传输的数据比特数,常见的有9600、115200等,需要在软件中设定一致。数据位指的是每个字符数据传输时占用的比特数,常见的有7、8等,需要和硬件设备一致。 对于C语言来说,可以使用标准的I/O库stdio.h中的fopen()、fread()和fwrite()方法来实现串口读写数据。首先要打开串口通信设备,可以通过fopen()方法以读写方式打开设备文件,然后可以使用fwrite()方法向串口发送数据,使用fread()方法从串口接收数据。 另外,还需要使用系统头文件termios.h中的结构体和函数对串口进行配置。可以使用结构体termios来设置串口属性,例如串口波特率、数据位、停止位等。还可以使用函数tcsetattr()将这些属性设置到操作系统中,并使用tcgetattr()函数获取串口参数。 在C语言中实现串口读写数据需要注意诸多问题,例如数据缓冲区的处理、错误处理等。如果处理不当容易导致程序崩溃或者无法正常工作。 总之,C语言是一种非常强大的编程语言,可以用来实现各种复杂的功能,实现串口读写数据只是其中一种最基础的功能。需要对C语言有深入的理解和丰富的编程经验才能更好地实现串口通信。 ### 回答2: 串口是一种常见的通信接口,可以在计算机和其他设备之间进行数据传输。在C语言中,可以使用串口库函数实现对串口的读写操作。下面是一个简单的实现: 1.首先需要打开串口。可以使用open()函数来打开串口设备,指定串口的名称、打开方式和权限等参数。 2.设置串口的属性。可以使用tcgetattr()和tcsetattr()函数来设置串口的波特率、数据位、校验位和停止位等属性。 3.向串口写数据。可以使用write()函数向串口写入数据,指定写入数据的缓冲区和数据长度等参数。 4.从串口读取数据。可以使用read()函数从串口读取数据,指定读取数据的缓冲区和数据长度等参数。 5.关闭串口。使用close()函数关闭串口设备。 除了以上基本操作,还可以使用其他的库函数来实现更高级的串口操作,例如设置串口的流控制、超时等属性,或者在串口中异步发送和接收数据等。在实际应用中,需要根据具体的应用需求来选择适合的串口库函数进行编程。 ### 回答3: C语言可以通过调用操作系统的串口API,来实现串口的读写。串口通信一般使用COM口,可以通过打开COM口来建立串口连接。COM口的打开需要设置串口的参数,括波特率、数据位、停止位和校验位等等。在C语言中,可以使用Windows API或Linux API来设置串口参数。 在Windows环境下,可以使用CreateFile函数打开串口,设置DCB结构体中的各种属性,然后使用ReadFile和WriteFile函数读写数据。在Linux环境下,可以使用open函数打开串口,使用tcgetattr和tcsetattr函数来设置串口属性,然后使用read和write函数来读写数据。 在进行串口读写时,需要注意数据的格式。一般来说,串口传输的数据是二进制数据,可能含ASCII码和非ASCII码字符。在读取数据时,需要根据数据的格式进行解析。在写入数据时,需要将数据转换为正确的格式,并按照协议进行组装。 总的来说,C语言实现串口读写数据需要掌握操作系统的API和串口工作原理,熟练使用串口的各种参数设置和数据解析方法,在实现过程中需要仔细处理各种异常情况,确保数据的可靠性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值