实时读取和保存串口数据问题解决记录(附Windows和Linux下的代码)

这两天在研究用C语言从串口接收数据。在Windows下接收数据网上已经有例子直接能用,但是在Linux下接收数据总是有问题,费了几天时间才解决,这里简单记录一下踩过的坑。

Linux下使用termios库来进行串口通信,主要是VTIME和VMIN两个参数的配置,具体的参数含义和组合说明有文章已经写得很清楚了,具体看Linux 串口编程学习记录(termios.h)

但是按照这样配置完成后发现读取的数据老是掉帧。把录制下来的数据打开后一个个字节地寻找帧头帧尾查看问题原因,发现是循环read的过程中,每次read经常是漏了一两个字节的数据,造成数据丢失,解析不出完整帧导致掉帧。最后发现是其中一些特定数据被用作特殊控制了。具体解决方法在解决方法:Linux串口接收字节0x11,0x0d,0x13丢失。简单来说就是网上的很多Linux下读写串口的示例程序都没对c_iflag进行有效的设置,这样传送ASCII码时没什么问题,但传送二进制数据时遇到0x0d,0x11和0x13却会被用作特殊控制了,关掉 ICRNL 和 IXON 选项即可解决。

实际数据特殊控制字符
0x0d回车符CR
0x11^Q VSTART字符
0x13^S VSTOP字符

在我的程序中具体表现是360000字节的数据中没有一个0x11或者0x13,0x0d倒还是正常的。

插入一行代码就能解决

// 把终端I/O设置为原始模式(串口通讯就是终端I/O的原始模式)时输入属性设置为
options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

具体在Linux下读取串口数据并保存在二进制文件中的完整代码附在下面

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

int main(int argc, char *argv[])
{
    int fd;
    uint8_t buf[60];
    int msg_length;
    
    // 用来存储原始串口消息
    FILE *fp_msg = fopen("msg_result.txt", "wb+");

    // O_RDWR表示以读写方式打开,O_NOCTTY表示不将串口设备作为控制终端
    fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY );
    if (fd < 0)
    {
        perror("open serial port failed!!!");
        return -1;
    }
    
    // 使用tcgetattr()函数获取当前的串口配置参数,然后修改需要的参数,最后使用tcsetattr()函数重新设置串口参数
    struct termios options;
    tcgetattr(fd, &options);

    cfsetispeed(&options, B921600);                         // 串口波特率为115200
    cfsetospeed(&options, B921600);
    options.c_cflag |= CLOCAL | CREAD;                      // 忽略调制解调器状态线 | 启用接收器
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;                                 // 使用8个数据位
    options.c_cflag &= ~PARENB;                             // 禁用奇偶校验
    options.c_cflag &= ~CSTOPB;                             // 使用1个停止位
    
    // options.c_cflag |= CRTSCTS;                             // 使用硬件流控制。在高速(19200bps或更高)传输时,使用软件流控制会使效率降低,这个时候必须使用硬件流控制。
    // options.c_iflag |= IXON | IXOFF; //使用软件流控制
    // options.c_cflag &= ~OPOST;        //原始数据(RAW)输出
    options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_oflag &= ~OPOST;
    options.c_cc[VMIN] = 60;                                
    options.c_cc[VTIME] = 0;                               // 串口读取的超时等待时间
    tcsetattr(fd, TCSANOW, &options);

    // 读取前先清空缓存,防止数据污染
    tcflush(fd, TCIOFLUSH);

    // while (1)
    for(int32_t j = 0; j < 6000; j++)
	{
        msg_length = read(fd, buf, sizeof(buf));
        printf("%d attempt get %d byte\n", j, msg_length);
        
        if (msg_length > 0)
        {
            fwrite(buf, sizeof(uint8_t), msg_length, fp_msg);
        }
	}

    fclose(fp_msg);
    close(fd);
    return 1;
}

顺便把Windows下读取串口数据并保存在二进制文件中的完整代码也附上吧

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include <Windows.h>
#include <time.h>
#include <winnt.h>

int main()
{
    HANDLE hCom;
	TCHAR serial_port[100];
    uint8_t buf[58] = {0};
	DWORD RLen = 0;
    BOOL status;

	wsprintf(serial_port, TEXT("\\\\.\\COM%d"), 5);
	// COM口 
    // 允许读和写 
    // 指定共享属性,由于串口不能共享,所以该参数必须为0 
    // 打开已经存在的串口 
    // 属性描述,该值为FILE_FLAG_OVERLAPPED,表示使用异步I/O,该参数为0,表示同步I/O操作
	hCom = CreateFile(serial_port, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hCom == INVALID_HANDLE_VALUE){
		// printf("打开COM口失败! %s\n", serial_port);
		return 0;
	}

	SetupComm(hCom, 5800, 5800);                //设置输入缓冲区和输出缓冲区的大小
 
	// 超时设置
	COMMTIMEOUTS TimeOuts;
	// 设定读超时
	TimeOuts.ReadIntervalTimeout = 0;           //读间隔超时
	TimeOuts.ReadTotalTimeoutMultiplier = 0;    //读时间系数
	TimeOuts.ReadTotalTimeoutConstant = 5000;   //读时间常量
	// 设定写超时
	TimeOuts.WriteTotalTimeoutMultiplier = 1;   //写时间系数
	TimeOuts.WriteTotalTimeoutConstant = 1;     //写时间常量
	SetCommTimeouts(hCom, &TimeOuts);           //设置超时

	// 配置串口
	DCB dcb;
	GetCommState(hCom, &dcb);
	dcb.BaudRate = 921600;                      // 波特率
	dcb.ByteSize = 8;                           // 每个字节有8位
	dcb.Parity = NOPARITY;                      // 无奇偶校验位
	dcb.StopBits = ONESTOPBIT;                  // 一个停止位
	SetCommState(hCom, &dcb);

    // 用来存储原始串口消息
    FILE *msg_result_txt = fopen("msg_result.txt", "wb+");
    assert(msg_result_txt != NULL);

    // while (1)
    for(int32_t j = 0; j < 60000; j++)
	{
		// PurgeComm(hCom, PURGE_TXCLEAR | PURGE_RXCLEAR); // 清空缓冲区
		status = ReadFile(hCom, buf, sizeof(buf), &RLen, NULL);
		if (status)
		{
            fwrite(buf, sizeof(uint8_t), RLen, msg_result_txt);
			printf("%d attempt get %d byte\n", j, RLen);
		}
	}

    fclose(msg_result_txt);
    CloseHandle(hCom);

    return 1;
}
  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用Windows API来进行串口读写。以下是一个简单的示例代码: ```c #include <stdio.h> #include <windows.h> int main() { HANDLE hSerial; DCB dcbSerialParams = {0}; COMMTIMEOUTS timeouts = {0}; DWORD dwBytesRead = 0; char read_buffer[256] = {0}; // 打开串口 hSerial = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hSerial == INVALID_HANDLE_VALUE) { printf("Error: Unable to open serial port\n"); return 1; } // 配置串口参数 dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if (!GetCommState(hSerial, &dcbSerialParams)) { printf("Error: Unable to get serial port state\n"); CloseHandle(hSerial); return 1; } dcbSerialParams.BaudRate = CBR_9600; dcbSerialParams.ByteSize = 8; dcbSerialParams.StopBits = ONESTOPBIT; dcbSerialParams.Parity = NOPARITY; if (!SetCommState(hSerial, &dcbSerialParams)) { printf("Error: Unable to set serial port state\n"); CloseHandle(hSerial); return 1; } // 配置读取超时时间 timeouts.ReadIntervalTimeout = 50; timeouts.ReadTotalTimeoutConstant = 50; timeouts.ReadTotalTimeoutMultiplier = 10; if (!SetCommTimeouts(hSerial, &timeouts)) { printf("Error: Unable to set serial port timeouts\n"); CloseHandle(hSerial); return 1; } // 读取串口数据 if (!ReadFile(hSerial, read_buffer, sizeof(read_buffer), &dwBytesRead, NULL)) { printf("Error: Unable to read from serial port\n"); CloseHandle(hSerial); return 1; } printf("Read %d bytes from serial port: %s\n", dwBytesRead, read_buffer); // 关闭串口 CloseHandle(hSerial); return 0; } ``` 在上面的示例代码中,我们打开了COM1串口,并将波特率设置为9600,数据位设置为8,停止位设置为1,无奇偶校验。然后,我们读取串口数据,并将其输出到控制台上。你可以根据自己的需求修改代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值