本文将系统梳理 RS-485 硬件设计的核心要点——包括终端匹配电阻配置、偏置电阻设计、隔离防护策略及布线规范并深入介绍 Modbus 协议在 LuatOS 嵌入式开发环境中的具体实现与应用范式。以 Air780EHV 系列工业级模组为例其外设资源覆盖多模态人机交互与工业现场总线场景显示与视觉支持 SPI 串口屏、电子墨水屏、OLED 单色屏等多类型 LCD 驱动并集成 30万像素摄像头接口满足本地图像采集需求工业通信接口原生支持 CAN 2.0B 总线、10/100M 自适应以太网RJ45、RS-485 半双工总线、USB 2.0 Full-Speed 及多路 UART通用扩展接口SPI、I2C、PWM、GPIO 等标准数字接口完备可灵活对接传感器与执行机构。网络协议栈方面Air780EHV 内置完整的 TCP/IP 协议实现涵盖 TCP/UDP、TCP-SSL/TLS 加密传输、FTP、MQTT、HTTP/WebSocket、NTP 网络授时并原生集成 Modbus RTU/TCP 协议解析引擎。一、485总线接口与UART的关系485总线接口本质上是UART总线接口的一种应用需要搭配485收发器芯片实现。最新参考设计文件详见资料中心下图为典型的参考设计二、电平匹配问题在UART与485收发器芯片的搭配中最常见需要注意的一个问题是电平匹配。由于上一章节参考设计中Air780EHV和SP3485都是3.3V的IO电平所以不再需要分立元器件电平转换电路或电平转换芯片。当双方电平不一致时则需要分立元器件电平转换电路或电平转换芯片。常见的分立元器件电平转换电路如下三、485总线接口的TVS防护工业现场环境复杂485总线经常面临静电、浪涌等威胁因此接口保护必不可少。485接口用TVS常用的型号有SM712系列如果防护等级要求较高也可以选择如下推荐的型号。ESD等级防护适用于一般静电防护场景。TVS等级防护具备2KV 1.2/50uS浪涌能力。SMBJ7.0CAWTSS等级防护具备4KV 10/700uS浪涌能力。四、Modbus通信协议与485总线接口强相关的通信协议是Modbus。此前我们发布过一版LuatOS的modbus核心库但使用难度较高。为此我们特别设计了exmodbus扩展库——在核心库的基础上封装了更简洁易用的API大大降低了开发难度让开发者可以快速集成Modbus通信。exmodbus最新API文档详见https://docs.openluat.com/osapi/ext/exmodbus/核心示例持续更新中敬请关注PROJECTRTU_MASTERVERSION001.000.000-- 在日志中打印项目名和项目版本号log.info(main,PROJECT,VERSION)localexmodbusrequire(exmodbus)-- 使用 Air8000 开发板测试打开这两个gpio.setup(16,1)-- RS485 芯片供电引脚localrs485_dir_gpio17-- RS485 方向引脚-- 使用 Air780EPM 开发板测试打开这三个-- gpio.setup(1, 1) -- Air780EPM RS485 芯片供电引脚-- gpio.setup(23, 1) -- Air780EPM vref 脚拉高-- local rs485_dir_gpio 24 -- Air780EPM RS485 方向引脚V1.2 是 25V1.3 是 24-- 创建 RTU 主站配置参数-- 说明创建 RTU 主站时只需要配置如下参数即可localcreate_config{-- 串口配置参数modeexmodbus.RTU_MASTER,-- 通信模式uart_id1,-- UART 端口号baud_rate115200,-- 波特率data_bits8,-- 数据位stop_bits1,-- 停止位parity_bitsuart.None,-- 校验位byte_orderuart.LSB,-- 字节顺序rs485_dir_gpiors485_dir_gpio,-- RS485 方向引脚rs485_dir_rx_level0,-- RS485 接收方向电平}-- 初始化从站 1 数据结构-- 用于记录从站 1 保持寄存器 0-1 的值localslave1_data{}-- 配置读取从站 1 保持寄存器 0-1 的值localread_config{raw_requeststring.char(0x01,-- 从站地址0x03,-- 功能码读取保持寄存器0x00,0x00,-- 寄存器起始地址0x00,0x02,-- 寄存器数量0xC4,0x0B-- CRC16校验码),timeout1000-- 超时时间 1000 ms}-- 创建 RTU 主站实例localrtu_masterexmodbus.create(create_config)-- 判断主站是否创建成功并记录日志ifnotrtu_masterthenlog.info(exmodbus_test,rtu_master 创建失败)elselog.info(exmodbus_test,rtu_master 创建成功)end-- 读取从站 1 保持寄存器数据的函数localfunctionread_slave1_holding_registers()log.info(exmodbus_test,开始读取从站 1 保持寄存器 0-1 的值)-- 执行读取操作localread_resultrtu_master:read(read_config)-- 根据返回状态处理结果ifread_result.statusexmodbus.STATUS_SUCCESSthenlocalrespread_result.raw_response-- 特别说明-- 接下来的判断是针对 modbus RTU 标准响应格式的应答原始帧来解析的-- 在实际项目中应根据自己项目中的实际应答原始帧格式进行解析-- 如果实际格式与此处演示的格式不一致需要修改接下来的解析代码-- 1. 检查总长度必须为 9 字节1 地址 1 功能码 1 字节数 4 数据 2 CRCif#resp~9thenlog.info(exmodbus_test,响应长度错误期望 9 字节实际:,#resp)returnend-- 2. 检查从站地址ifstring.byte(resp,1)~0x01thenlog.info(exmodbus_test,从站地址不匹配收到:,string.byte(resp,1))returnend-- 3. 检查功能码localfunc_codestring.byte(resp,2)iffunc_code0x83thenlocalexc_codestring.byte(resp,3)log.info(exmodbus_test,从站返回异常响应异常码:,exc_code)returnelseiffunc_code~0x03thenlog.info(exmodbus_test,功能码错误收到:,func_code)returnend-- 4. 检查字节数字段应为 4localbyte_countstring.byte(resp,3)ifbyte_count~4thenlog.info(exmodbus_test,字节数字段错误期望 4实际:,byte_count)returnend-- 5. 校验CRC-- 计算前 7 字节的 CRClocalcrc_calculatedcrypto.crc16_modbus(resp:sub(1,7))-- 提取接收到的 CRClocalcrc_receivedstring.unpack(I2,resp,8)-- 比较 CRCifcrc_calculated~crc_receivedthenlog.info(exmodbus_test,CRC 校验错误计算值:,crc_calculated,接收值:,crc_received)returnend-- 6. 解析寄存器数据从第 4 字节开始大端序localdata1string.unpack(I2,resp,4)-- 寄存器 0偏移 4localdata2string.unpack(I2,resp,6)-- 寄存器 1偏移 6-- 7. 记录数据slave1_data[0]data1 slave1_data[1]data2-- 8. 记录日志log.info(exmodbus_test,成功读取到从站 1 保持寄存器 0-1 的值寄存器 0 数值为,slave1_data[0],寄存器 1 数值为,slave1_data[1])elseifread_result.statusexmodbus.STATUS_TIMEOUTthenlog.info(exmodbus_test,未收到从站 1 的响应超时)endend-- 定时任务函数每 2 秒调用一次读取函数localfunctiontask()whiletruedoifrtu_masterthen-- 每 2 秒调用一次读取函数read_slave1_holding_registers()elselog.info(exmodbus_test,rtu_master 未创建无法执行 read_slave1_holding_registers())endsys.wait(2000)endend-- 初始化任务sys.taskInit(task)-- 用户代码已结束----------------------------------------------- 结尾总是这一句sys.run()-- sys.run()之后后面不要加任何语句!!!!!今天的内容就分享到这里了