ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

嵌入式Linux下Modbus RTU主站开发实战:串口配置与RS485通信

嵌入式Linux下Modbus RTU主站开发实战:串口配置与RS485通信 做嵌入式Linux下的串口设备开发Modbus RTU几乎是绕不开的一关。不管是接温湿度传感器、压力变送器还是电表和阀门控制器Modbus RTU这套老协议仍然活跃在工业现场的第一线。这篇文章就把我在嵌入式Linux端做Modbus RTU主站、通过串口配置读写传感器数据的完整过程梳理一遍从termios串口参数怎么设到报文怎么拼、CRC怎么算再到RS485方向切换的时序坑以及最后调试时怎么定位问题。因为Linux下的串口本质就是文件操作没有现成的Modbus协议栈也得自己组帧解析所以这篇文章对做嵌入式Linux应用开发、想在板子上对接工业传感器的朋友会比较实用。先说一下项目的硬件环境主控是ARM Cortex-A系列核心板跑的是精简的嵌入式Linux系统板子上带了几路UART外接了SP3485这类RS485收发器通信链路是半双工的RS485总线挂了一个Modbus RTU从站的温湿度变送器。我需要在Linux用户态写一个应用程序周期性读取从站的温度、湿度寄存器值然后把数据用于后续的逻辑判断和上报。整个项目难度不大但却把嵌入式Linux串口开发里最典型的几个环节全部串起来了串口参数配置、RS485方向控制、Modbus RTU组帧解帧、CRC校验、超时重试和轮询调度。1. 整体设计与思路拆解1.1 为什么选Modbus RTU而不是其他协议Modbus协议族里RTU和TCP两种用得最多。TCP模式适合网络化场景比如PLC与上位机走以太网而RTU模式面向串行链路特别适合RS485总线这种多点、半双工、远距离百米级的场合。对于传感器采集这类需求用RS485总线可以一条线上挂32个甚至更多从站设备每个设备一个地址主站轮流发指令读取链路的硬件成本极低。很多刚入行的同学会问现在无线和以太网这么普及为什么还要用RS485和Modbus RTU因为工业现场对可靠性和抗干扰的要求极高RS485使用差分信号传输共模抑制能力强在电机频繁启停、变频器干扰严重的环境里依然能稳定跑几万米而且协议简单、开放、免费几乎市面上所有的仪表、PLC、变频器都原生支持Modbus RTU。所以搞嵌入式Linux的人学会这套RS485Modbus RTU的组合到了工业项目里基本不会没饭吃。1.2 在嵌入式Linux上实现Modbus主站的两种路线主站逻辑本身并不复杂核心就三步组好请求帧、通过串口发出去、读回响应帧并解析。但具体实现方式我复盘后觉得有两种路线值得讨论。第一种是直接用系统调用操作串口设备文件自己拼报文、自己解析。好处是完全可控代码量也不大一个项目的核心通信模块几百行C代码就能搞定而且不依赖第三方库交叉编译时也不用处理依赖。坏处是任何一个细节没处理到位比如串口没有设成原始模式、CRC高低字节搞反、485方向没有及时翻转都会导致通信失败需要自己一点点排查。第二种是引入libmodbus这样的开源库。libmodbus封装了RTU和TCP两种模式内部把帧格式、CRC、超时重试都给你处理好了你只需要调modbus_read_registers这类API。好处是上手上限低项目周期紧的时候能快速出活坏处是库对串口设备有一些默认假设遇到特殊的RS485方向控制需求非标准linux RS485 ioctl支持的情况下时需要自己写回调接口而且出问题后如果你不理解内部机制反而更不好定位。我自己在这个项目里选了第一种因为嵌入式Linux设备的串口数量、GPIO资源都有限而且设备上跑的从站类型相对固定自己写协议栈反而更容易做定制化的超时和重试策略。后面我也会补一段libmodbus的实现思路方便读者对比。1.3 系统架构与模块划分整个应用我拆成了四个模块串口初始化模块负责打开串口设备、配置termios参数、设置RS485方向控制帧处理模块负责CRC16计算、请求帧组包、响应帧校验和解析业务轮询模块负责任务调度按周期轮询各个从站的寄存器数据上报模块把解析出来的温湿度数据写入共享结构体供上层逻辑或MQTT上报使用。这么做的好处是如果后续从站设备型号变了只需要改帧处理模块里的寄存器地址映射串口和轮询逻辑完全不用动。如果是把总线上的从站数量增加也只需要在配置表里增加条目主循环就能自动轮询。模块之间用简单的结构体传参不引入过多架构负担正好符合嵌入式项目“实用优先”的原则。2. 串口配置与RS485物理层准备2.1 termios串口参数配置详解串口配置在Linux下就是通过termios结构体和tcsetattr等函数完成的。最容易犯的错误是把串口当成普通文件直接read/write但默认的串口往往工作在“行模式”或“回显模式”这会让Modbus RTU的二进制数据被内核驱动或tty层加工导致收发内容被改得乱七八糟。我在项目里统一用以下方式来配置int fd; struct termios tio; fd open(/dev/ttyS3, O_RDWR | O_NOCTTY | O_NDELAY); if (fd 0) { perror(open); return -1; } memset(tio, 0, sizeof(tio)); tio.c_cflag B9600 | CS8 | CLOCAL | CREAD; tio.c_iflag 0; tio.c_oflag 0; tio.c_lflag 0; tio.c_cc[VMIN] 0; tio.c_cc[VTIME] 5; tcflush(fd, TCIOFLUSH); tcsetattr(fd, TCSANOW, tio);上面的配置里c_cflag用了B9600、CS8表示波特率9600、8个数据位CLOCAL和CREAD是必须的CLOCAL表示不检测调制解调器的载波信号CREAD表示使能接收。c_iflag、c_oflag、c_lflag全部清零就是告诉内核不要做任何输入转换、输出转换和行规则处理这是Modbus RTU这种二进制协议通信能够正常工作的基础。VMIN和VTIME的组合要注意。我习惯用VMIN0、VTIME5表示非阻塞读取但最多等待0.5秒VTIME单位是0.1秒。这样read函数在超时后返回0我就可以据此判断本次请求是否超时然后进入重试逻辑。如果设成VMIN1、VTIME0就变成了阻塞式读取在单线程轮询模型里容易把主循环卡死除非你另开线程专门等串口否则不推荐。2.2 波特率、校验位和数据位的匹配原则Modbus RTU在串口链路上一般有两个固定组合9600/8/N/1 或 19200/8/N/1。这里的8N1表示8个数据位、无校验位、1个停止位。你会发现这和我们配置里的CS8对应因为Modbus RTU本身在报文尾部已经包含CRC校验码所以链路层通常不再启用奇偶校验这是规范里的默认推荐配置。实际项目里波特率一定要和从站设备的拨码开关或配置工具保持一致。我遇到过不少次现场故障就是因为变频器或者仪表出厂默认9600而我们的程序里写的是19200结果设备完全无法通信。另外注意rs485总线上的所有设备波特率、数据位、校验位、停止位必须完全一致这个没得商量。如果确实需要启用偶校验比如个别老仪表只支持Even校验那么c_cflag里要加上PARENB并去掉PARODD还得把CS7或CS8配合对齐。但从我的经验看只要设备支持尽量用8N1省心很多。2.3 RS485方向切换的三种实现方式RS485是半双工总线同一时刻只能有一方占用线路发送数据。所以主站发请求时收发器必须处于发送模式发送结束后要尽快切回接收模式否则永远收不到从站应答。这个方向的切换在嵌入式Linux下有三种常见的做法。第一种是硬件自动收发切换。现在很多RS485收发器芯片内置了自动换向电路比如MAX13487它检测到发送数据线上的起始位后会自己打开驱动发完最后一个停止位后自动切回接收。这种方式对软件完全透明只需要当成普通串口操作即可。如果项目硬件还没定强烈建议考虑这种芯片能省掉无数麻烦。第二种是GPIO控制DE/RE引脚。这是最常见的做法驱动代码里在write之前把某个GPIO拉高write结束再拉低。但是这里有个非常关键的时序点write系统调用返回并不代表数据已经全部从UART发完因为Linux的串口驱动内部还有发送FIFO和DMA缓冲。如果write一返回就立刻拉低GPIO很可能最后的几个字节还没发出去就会出现“发出去的命令不完整从站没反应”的古怪现象。正确做法是先用tcdrain把数据真正flush出去然后再拉低方向引脚// 发送前 gpio_set_value(de_pin, 1); // 发送并等待数据全部发送完成 int ret write(fd, frame, len); tcdrain(fd); // 发送完成后切回接收 gpio_set_value(de_pin, 0);第三种是利用内核自带的支持RS485模式的串口驱动。如果内核里的串口驱动实现了TIOCSRS485 ioctl就可以直接把DE/RE引脚的硬件控制交给驱动驱动会根据数据发送流程自动切换方向。这种方式需要在驱动设备树或平台配置里指定收发控制GPIO然后在应用层用ioctl使能struct serial_rs485 rs485conf {0}; rs485conf.flags SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND; rs485conf.delay_rts_before_send 1; rs485conf.delay_rts_after_send 1; ioctl(fd, TIOCSRS485, rs485conf);注意TIOCSRS485的底层支持依赖具体硬件平台和串口驱动型号不是每个Linux内核版本里都有。我做这个项目时用的内核版本是4.19驱动支持得不错但不同嵌入式厂商的内核裁剪情况差异很大用之前最好先去内核源码里搜一下serial_rs485结构体是否被所在驱动引用。3. Modbus RTU协议原理与报文拆解3.1 RTU帧格式和功能码速查Modbus RTU的报文帧由四部分组成从站地址、功能码、数据域、CRC16校验。数据域的长度根据功能码不同而不同CRC16校验码占2个字节而且是低字节在前、高字节在后这个字节序很多初学者会搞反。功能码含义典型并发场景0x01读线圈状态读取数字量输出0x02读离散输入读取按钮、开关状态0x03读保持寄存器读取可写可读的配置参数、变量0x04读输入寄存器读取传感器采集值0x06写单个寄存器设置单个参数0x10写多个寄存器批量设置参数、控制指令做传感器数据采集时最常用的功能码是0x04和0x03。有些厂家的温湿度变送器把测量值放在输入寄存器功能码0x04有些放在保持寄存器功能码0x03具体以设备说明书为准。建议拿到新设备的第一步就是看寄存器表搞清楚地址、数据类型16位无符号、16位有符号、32位浮点还是IEEE754然后把顺序倒过来验证。3.2 一个完整的读写实例读温湿度寄存器假设从站地址是0x01设备手册定义温度寄存器在保持寄存器地址0x0000湿度寄存器在0x0001每个寄存器16位温度值是实际值乘以10的定点数。读两个保持寄存器的请求帧如下从站地址: 0x01 功能码: 0x03 起始地址: 0x00 0x00 寄存器数: 0x00 0x02 CRC16: 0xC4 0x0B完整报文就是01 03 00 00 00 02 C4 0B。CRC的计算范围是地址、功能码、起始地址和寄存器数这几个字节不包含CRC自身。如果从站正常应答响应帧形如从站地址: 0x01 功能码: 0x03 字节数: 0x04 寄存器1值: 0x01 0x2C (温度值 0x012C 300除以10就是30.0℃) 寄存器2值: 0x00 0x79 (湿度值 0x0079 121除以10就是12.1%RH) CRC16: ...3.3 CRC16-MODBUS的具体实现Modbus RTU使用的CRC16算法多项式是0x8005初始值为0xFFFF结果低字节在前发送。网上资料很多但代码风格参差不齐。我在嵌入式环境里用的是查表法计算速度快代码也不算长。查表法需要先生成256个元素的CRC高字节表然后逐字节处理。如果项目代码空间紧张也可以用按位计算的方式性能稍慢但对资源占用极小。uint16_t crc16_modbus(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ data[i]; for (int j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }注意这里用的是0xA001这是0x8005的逆序多项式按位右移时用这个值。计算出来的crc如果按大端思路去看是“反”的比如算出来0x0BC4发送的时候应该是低字节0xC4在前、高字节0x0B在后。很多第一次做Modbus的人在这里翻车明明CRC算法对但把字节序放错了导致从站一直不认这帧。3.4 浮点数在Modbus里的处理工业传感器经常会碰到32位浮点数比如用两个连续的保持寄存器存一个IEEE754标准的float值。这里最大的坑是字节序和字序的组合。标准的IEEE754 float比如25.6十六进制是0x41CCCCCD拆成两个16位寄存器可能是高字在前41CC CCCD存到寄存器N和N1也可能是低字在前。再加上寄存器内部的字节序又分大端和小端所以就存在至少四种排列方式。插一句Modbus规定寄存器内的16位数据是大端序高字节在前低字节在后。所以多数设备的寄存器排列是“每寄存器内大端、寄存器间高字在前”即Modbus大端。遇到浮点数据时我习惯写一个通用的4字节到float的转换函数然后用一个可配置的开关决定是否翻转字序float regs_to_float(uint16_t reg_hi, uint16_t reg_lo) { uint32_t raw ((uint32_t)reg_hi 16) | (uint32_t)reg_lo; float f; memcpy(f, raw, 4); return f; }如果设备手册写的是低字在前就调换hi和lo再转换。代码本身不复杂关键是拿到设备后一定要先用工具验证数据否则极易出现“数据能读回来但数值明显不合理”的情况。4. 嵌入式Linux下Modbus主站的代码实现4.1 组帧、发送、接收的完整流程我把主站读写保持寄存器的核心逻辑封装成一个函数输入参数分别是串口fd、从站地址、起始寄存器地址、寄存器数量和输出缓存。函数内部完成组帧、CRC计算、发送、超时读响应、校验响应帧、解析数据等步骤返回读到的寄存器数量。int modbus_read_holding_registers(int fd, int slave_addr, int start_addr, int reg_count, uint16_t *out) { uint8_t frame[8] {0}; uint8_t rx_buf[256] {0}; int frame_len 6; frame[0] (uint8_t)slave_addr; frame[1] 0x03; frame[2] (uint8_t)(start_addr 8); frame[3] (uint8_t)(start_addr 0xFF); frame[4] (uint8_t)(reg_count 8); frame[5] (uint8_t)(reg_count 0xFF); uint16_t crc crc16_modbus(frame, frame_len); frame[frame_len] crc 0xFF; frame[frame_len 1] crc 8; // 发送并等待数据发送完成 if (rs485_direction_set(1) 0) return -1; int ret write(fd, frame, frame_len 2); tcdrain(fd); rs485_direction_set(0); if (ret ! frame_len 2) return -1; // 等待响应最多200ms int n wait_for_data(fd, 200); if (n 0) return -1; n read(fd, rx_buf, sizeof(rx_buf)); if (n 5) return -1; // 校验从站地址和功能码 if (rx_buf[0] ! slave_addr || rx_buf[1] ! 0x03) { return -1; } // 校验CRC uint16_t recv_crc rx_buf[n - 2] | (rx_buf[n - 1] 8); if (recv_crc ! crc16_modbus(rx_buf, n - 2)) { return -1; } int byte_count rx_buf[2]; int reg_nums byte_count / 2; for (int i 0; i reg_nums; i) { out[i] (rx_buf[3 i * 2] 8) | rx_buf[3 i * 2 1]; } return reg_nums; }wait_for_data通过select实现这是Linux串口编程里的标准做法。它的好处是可以给每次读写设置独立超时不会因为某个从站掉线就长时间卡死主循环int wait_for_data(int fd, int timeout_ms) { fd_set fds; struct timeval tv; FD_ZERO(fds); FD_SET(fd, fds); tv.tv_sec timeout_ms / 1000; tv.tv_usec (timeout_ms % 1000) * 1000; return select(fd 1, fds, NULL, NULL, tv); }用select最大的收益是某个从站没电、断电、总线断掉时超时时间到之后程序能快速返回并进入下一轮重试而不是傻等。4.2 多从站轮询调度策略工业现场一条RS485总线上往往挂了多个从站。主站必须按地址依次轮询并且每个从站的请求之间要保持一定的间隔不能连发。Modbus规范要求帧之间有3.5个字符时间的空闲间隔但实际上RS485轮询场景里主站发给A从站得到响应之后紧接着发给B从站是没问题的只要你的程序不会在半双工总线上同时收发导致干扰。我常用的调度结构是这样的用一个数组维护从站配置和寄存器映射主循环里逐个处理每个从站读取失败三次后标记离线但继续轮询下一个。这样某一个从站坏了不会影响整条总线上的其他设备typedef struct { int slave_addr; int start_addr; int reg_count; int fail_count; int is_online; } slave_config_t;主循环逻辑很简单遍历所有从站调用modbus_read_holding_registers根据返回值更新is_online和fail_count然后sleep一小段比如50ms再处理下一个。这个sleep主要是把CPU占用的节奏降下来也给485总线一个稳定的电平空闲期。4.3 如果改用libmodbus怎么快速替换如果你不想自己维护协议栈libmodbus确实能省不少事。核心代码是这样的modbus_t *ctx modbus_new_rtu(/dev/ttyS3, 9600, N, 8, 1); modbus_set_slave(ctx, 1); modbus_connect(ctx); uint16_t regs[2]; int ret modbus_read_registers(ctx, 0, 2, regs); modbus_close(ctx); modbus_free(ctx);libmodbus把RTU的CRC、帧间隔、超时都封装好了用起来确实方便。但要注意libmodbus默认不走Linux内核的TIOCSRS485接口它通过串口的RTS引脚或者系统调用控制方向的实现和硬件有关我在部分国产板卡上遇到过modbus_connect成功但收发不稳定的情况。若遇到这种情况可以在连接后手动设置RTS模式或改用modbus_rtu_set_rts调用自定义GPIO控制回调。总之库能简化代码但不能省掉你对RS485时序和硬件特性的理解。5. 调试工具与问题排查实录5.1 用PC工具快速验证协议和策略开发调试阶段我建议分两边走。一边用PC上的Modbus主站工具比如Modbus Poll连接从站设备验证设备本身的法行为和数据格式另一边用PC上的从站仿真工具比如Modbus Slave模拟传感器设备验证你嵌入式端的程序逻辑。用Modbus Poll时注意几个关键设置串口号要选对波特率、校验位要和实际设备一致从站ID和寄存器地址也要填对。它能直观地看到每帧报文的CRC和收发时间排查“为什么设备没响应”这种问题特别快。如果Modbus Poll能正常读到数值说明设备没问题问题大概率出在你自己的嵌入式程序如果Modbus Poll也读不到那就要检查硬件链路、地址和波特率了。反过来如果你的嵌入式程序想调试但现场设备不方便动就启动Modbus Slave模拟一个从站把你程序里的寄存器地址、功能码发过来如果Slave端能收到请求并正确回复就能证明你的组帧和CRC是对的。5.2 串口层常见错误定位开发中最磨人的不是不会写代码而是代码看起来全对但就是不通。我建议排查时按下面几个方向来打开串口后先用一个简单的回环测试在串口TX和RX引脚之间短接程序发一个字节看能否原样收到。如果收到说明串口基础收发是好的收不到检查设备节点、权限、复用配置。用hexdump或打印函数把每次写的内容打出来确认请求帧本身没拼错。打印结果和你用Modbus Poll工具里看到的报文对照逐字节比对。读取响应时也打印完整帧结合CRC校验判断问题出在物理层还是协议层。CRC一直错先怀疑串口配置是否正确再看看你组的CRC字节序对不对。如果RS485总线上的其他设备没有通信时一切正常一接上某个从站就乱码大概率是共地问题。485虽然是差分信号但收发器的工作地线最好和从站设备的地连在一根线上不然共模电压超限会导致误码。我会把调试中排查到的问题整理成一个速查表放在最后方便大家直接对照。5.3 RS485方向切换的时序谜题前面提到tcdrain解决了“发送未完就切接收”的问题但在实际调试中还遇到一个更隐蔽的现象第一次发指令能从站正常响应第二次开始就超时。排查到最后发现是我在一帧数据发送完成后延时不够就立刻发下一帧导致从站还没处理完上一帧主站的下一帧就到了。Modbus协议要求主站请求之间要有间隔实际的解决办法是每次请求之间至少间隔50ms到100ms给从站留足处理时间。另外如果用的是GPIO控制DE引脚要注意电平逻辑。很多RS485模块的高电平有效方向定义为DE为高时发送RE为低时使能接收。如果硬件上DE和RE是分开接的就需要同时操作两个GPIO或者用一个GPIO接两个引脚DE接高电平有效RE反相。如果硬件设计者用了一个GPIO控制两个引脚编码时只需要高/低两个状态逻辑就很清晰。5.4 主机从机单独测试正常、接一起就不正常的处理思路热词里有个典型问题485主机和从机分别与PC连接都正常主机直接连从机却不正常。这个问题项目里确实常见原因大概有这几类。第一类是缺少终端电阻。虽然短距离通信不接终端电阻大概率也能通但两条线的信号边沿反射会让信号质量变差在高速或者长线时尤其明显。主机和从机之间距离如果超过几米建议在总线两端各接一个120欧姆终端电阻。第二类是波特率或校验方式不一致。单独测的时候你分别看了两边参数但连接前没有统一确认。尤其是校验位很多仪表不同的寄存器通道可能设置还不一样。第三类是地电位差。PC通过USB转485适配器调试时适配器和板的参考地可能恰好比较接近两个设备真实连接时如果没有共地可能会导致AB线的共模电压漂移造成通信异常。调试时可以从最简场景开始先用一台主机、一台从机、两根A/B线外加一根地线确认能通后再逐步加设备这样能把变量控制到最小。5.5 常见问题速查表现象可能原因排查动作全部设备无响应主站串口未打开、波特率不匹配、RS485方向未切回接收先用回环测试确认串口再检查配置和GPIO时序单个从站无响应从站地址错误、从站掉电、寄存器地址越界用Modbus Poll直接读该从站确认能收到响应但CRC校验一直失败串口被配置成行模式或回显、CRC计算或字节序错误检查termios的c_iflag/c_lflag逐字节比对CRC收到异常响应0x83或0x84寄存器地址不存在、从站不支持该功能码查阅设备寄存器表用Modbus Poll确认地址范围首帧正常后续帧超时请求间隔太短、从站处理不过来、485方向切换晚增加轮询sleep检查tcdrain后的GPIO拉低时机偶尔通信错误或乱码未共地、缺少终端电阻、电磁干扰、线缆过长加共地线总线两端加120欧电阻降波特率验证读回的数据数值明显不对寄存器地址偏移、字节序/字段序弄错、数据类型不对参考设备手册用Modbus Poll交叉验证原始hex值用了libmodbus但不稳定RTS方向控制与硬件不匹配、FIFO/中断干扰改用TIOCSRS485或自定义GPIO回调6. 项目扩展思考与后续演化6.1 数据解析后的应用走向读回的温湿度数据如果只是在串口终端里打印那项目意义就小了很多。实际项目中我会把解析后的数据存入共享内存或通过消息队列送给上层业务线程业务线程根据阈值做报警、联动继电器或者把数据打包成JSON通过MQTT上传到物联网平台。这也就是很多嵌入式Linux项目里“边缘采集云上报”的典型路径。做数据上报时还有个容易被忽视的点传感器数据可能有毛刺或瞬时跳变。比如温湿度变送器受到干扰瞬间读出一个明显不合理的数值直接上传会影响上层决策。比较简单的做法是在程序里加一段滑动平均滤波或者对超过合理范围的数据直接丢弃并重新读取。工业现场“数据清洗”这个活儿虽然听起来很土但确实能大幅减少误报警。6.2 从Modbus RTU到Modbus TCP的转换如果现场设备是Modbus RTU而你的采集终端或上位机在以太网侧只支持Modbus TCP那么可以在嵌入式Linux网关里做一个协议转换一个线程专门负责串口侧RTU的收发另一个线程监听TCP端口解析Modbus TCP报文后把请求转发给串口侧的RTU逻辑再把响应组回TCP帧返回。这种网关项目的技术要点本质上还是在串口那侧把RTU帧处理好所以本文前面讲的内容依然适用。6.3 可维护性与命名规范最后说一点和个人习惯有关的东西串口设备、从站地址、寄存器映射这类信息不要散落在代码各处建议集中放到一个配置文件里比如JSON或简单的ini格式。程序启动时读取配置动态构建从站表。这样现场换了个不同寄存器地址的设备只需要改配置文件不需要重新编译整个程序。尤其客户在现场能远程改配置总比带着交叉编译环境出差强得多。我个人在实际操作中的体会是Modbus RTU这套东西本身不难难的是串口和RS485这一层硬件/驱动的“脏活”。只要把termios配好、把RS485方向切换时序处理好、把CRC的字节序搞清楚后面所有功能码的读写都是同一套模板的重复劳动。如果对Linux串口编程还不熟建议先用PC的USB转485加一个从站设备把本文的代码跑通再上板子能少走很多弯路。
返回列表