资讯动态

嵌入式总线实战(一):RS-485土壤温湿度传感器通讯指南

发布时间:2026/8/12 19:44:12 来源:尧图企业网站定制
#阿文的成长日记引言本文是嵌入式总线实战专栏的第一篇笔记重点说明基于STM32的USART硬件配合RS-485收发控制引脚完成土壤传感器的数据采集与解析然后在通过USB转串口将数据实时打印到电脑上完成实时监控。欢迎各位大佬私信留言RS-485概述什么是RS-485它是美国电子工业协会Electronic Industries AssociationEIA于1983年发布的串行通信接口标准是一种串行半双工异步的物理层总线。具有以下几大特点1.允许同一总线上并联多个设备最大可以支持32个2.传输信号时采用差分信号传输3.传输距离来说相对较远理论通讯距离可达1200米4.RS-485的速率可高达10Mbit/s5.接线简单只需两根数据线即可完成通信。Modbus协议RS-485理论上适配所有的串口协议常用的协议有Modbus协议Profibus协议等。Modbus协议在RS-485上绝对是最通用的“普通话”。根据SN-3000-TR-N01土壤温湿度传感器的通信协议文档该传感器采用Modbus-RTU协议这里我们就重点说一下Modbus-RTU。Modbus-RTURemote Terminal Unit是Modbus协议在串行链路上的一种传输模式采用16进制编码具有较高的传输效率和紧凑的帧结构。在RS-485网络中Modbus-RTU是最常用的实现方式。一个完整的Modbus-RTU帧由以下部分组成且帧与帧之间通过至少3.5个字符时间的空闲间隔来分隔。地址码1字节标识从站设备的地址范围0-2470为广播地址1-247为从站地址。功能码1字节指定要执行的操作类型如读寄存器、写寄存器等。数据N字节根据功能码不同包含寄存器地址、数据长度、寄存器值等信息。CRC校验2字节循环冗余校验用于检测传输错误采用CRC-16算法。Modbus-RTU定义了多种功能码本次土壤温湿度传感器主要使用读保持寄存器0x03功能码来读取土壤温度和湿度数据。我们了解了什么是RS-585和Modbus协议接下来我们进入实战环节。开发环境与工具首先是开发环境与工具的选择具体工具和版本如下所示。工具版本STM32CubeMXV6.18.0STM32CubeIDEV2.2.0ST-LINK/V2——SSCOM串口助手V5.13.1Saleae逻辑分析仪V2.3.18接口硬件电路设计配置好开发环境我们就要完成硬件电路的设计了我将从芯片选型RS-485,USB转串口模块土壤温湿度传感器的硬件连接这几个方面介绍。主控片的芯片选择采用STM32F407IGH6芯片该芯片是一款性能强劲、外设丰富的Cortex-M4微控制器。其高达168MHz的主频、1MB Flash、使其非常适合电机控制、工业自动化、人机界面、高端物联网网关等复杂应用。土壤温湿度芯片的选择采用SN-3000-TR-N01传感器该传感器适用于土壤温度以及水分的测量经与德国原装高精度传感器比较和土壤实际烘干称重法标定精度高响应快输出稳定。受土壤含盐量影响较小适用于各种土质。可长期埋入土壤中耐长期电解耐腐蚀抽真空灌封 完全防水。 该传感器广泛适用于科学实验、节水灌溉、温室大棚、花卉蔬菜、草地牧场、 土壤速测、植物培养、污水处理、粮食仓储及各种颗粒物含水量和温度的测量。RS-485的硬件连接RS-485芯片的RO端接在主控片的USART1_RXDI端接在USART1_TXRE,DE短接并且接入V5电压让芯片内部进行切换RS485A和B端接入土壤温湿度的RS485的A和B端口。土壤温湿度传感器的电气连接USB转串口硬件连接为了能够实时监测到传感器的数据接入了USB转串口模块将土壤温湿度的数据通过串口实时发送到电脑上我们采用UART6,硬件连接方式如上图所示。实物硬件连接图除此之外主控片需要外接电源时钟系统复位系统等这里我相信大家都只知道怎么去做就不罗嗦了。接下来我们就重点说以下软件代码是如何实现的系统软件设计我们做完硬件的实现接下来就是对功能的实现了到了考验我们代码能力的时候了土壤传感器数据帧格式该土壤传感器采用Modbus-RTU的数据帧格式上文已经说明了其帧的组成我们重点来看下该传感器的询问帧和应答帧的示例通过该示例我们发现读取该传感器的数据地址码为0x01并且先返回湿度值在返回温度值。温度计算当温度低于 0 ℃ 时温度数据以补码的形式上传。温度FF9F H(十六进制) -97 温度 -9.7℃湿度计算湿度1E6 H (十六进制) 486 湿度 48.6%RH查阅相关手册发现其波特率可设置成4800bit/s9600bit/s2400bit/s,本次我们采用4800bit/s且UART的数据帧结构如下所示下图是关于土壤传感器所用的UART1外设的初始化配置此外我们还需通过单片机的UART6外设将土壤传感器的数据通过USB转串口模块发送到串口助手上这样我们就可以实时监测到数据具体的初始化配置如下核心代码实现#include main.h #include usart.h #include gpio.h #include stdio.h #include mbcrc.h #define ADDRESS 0x01 //定义土壤传感器的的地址 #define FUNC_CODE 0X03//定义功能码 //定义问询帧 uint8_t send[8] {ADDRESS,FUNC_CODE,0x00,0x00,0x00,0x02,0x00,0x00}; //定义应答帧 uint8_t recv[9] {0}; short crc,temp;//定义返回的crc校验返回的变量以及中间变量 float humi,tem;//定义接收温度湿度的变量 //重定向 int _write(int file, char *ptr, int len) { HAL_UART_Transmit(huart6, (uint8_t*)ptr, len, HAL_MAX_DELAY); return len; // 返回发送的字节数 } int main(void) { HAL_Init();//HAL固件库的初始化 SystemClock_Config();//系统时钟的初始化 MX_GPIO_Init();//GPIO的初始化 MX_USART1_UART_Init();//UART1的初始化 MX_USART6_UART_Init();//UART6的初始化 while (1) { //1.构建问询数据帧 crc usMBCRC16(send, 6);//获取问询数据帧的校验码 send[6] crc0xff;//crc的低8位 send[7] (crc8)0xff;//crc的高8位 //2.发送问询帧 HAL_UART_Transmit(huart1, send, 8, 100); //3.判断是否是0x01地址的从设备返回的数据 HAL_UART_Receive(huart1, recv[0], 1, HAL_MAX_DELAY); if(recv[0] ! 0x01) { continue; }//重新接收 //4.接收剩余应答数据帧 HAL_UART_Receive(huart1, recv[1], 8, HAL_MAX_DELAY); crc 0; crc usMBCRC16(recv, 7); if(crc ! ((recv[8]8)|recv[7])) { continue; } //5.处理数据 //湿度值 temp (recv[3]8)|recv[4]; humi (float)temp/10; //温度值 temp 0; temp (recv[5]8)|recv[6]; tem (float)temp/10; printf(temp: %.2f,humi:%.2f\r\n,tem,humi); HAL_Delay(1000); } }#include mbcrc.h static const UCHAR aucCRCHi[] { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 }; static const UCHAR aucCRCLo[] { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 }; //PUCFrame校验数组的首地址uslen:代表校验数组的长度 USHORT usMBCRC16( UCHAR * pucFrame, USHORT usLen ) { UCHAR ucCRCHi 0xFF; UCHAR ucCRCLo 0xFF; int iIndex; while( usLen-- ) { iIndex ucCRCLo ^ *( pucFrame ); ucCRCLo ( UCHAR )( ucCRCHi ^ aucCRCHi[iIndex] ); ucCRCHi aucCRCLo[iIndex]; } return ( USHORT )( ucCRCHi 8 | ucCRCLo ); }#ifndef _MB_CRC_H #define _MB_CRC_H typedef unsigned short USHORT; typedef unsigned char UCHAR; USHORT usMBCRC16( UCHAR * pucFrame, USHORT usLen ); #endif调试与验证程序编写完毕后将数据烧录到MCU中就要验收我们的成果了下面我们将对我们所写的程序进行调试与验证。物理层时序验证为了能够更加直观分析出数据的发送与接收我通过逻辑分析仪抓取了通信时的数字波形我们来分析下吧我们发现主机先按照Modbus-RTU格式将数据发送给从机之后过了大概10余ms的时间土壤温湿度传感器从机将数据也是按照Modbus-RTU的数据帧格式发送给主机然后主机又开始发送一次问询帧循环往复。应用层数据解析验证我们对该数据帧进行分析发现湿度为0%RH温度约为23.9℃通过串口我们发现获取的数据与数据帧相同。此时传感器处于干燥空气中湿度为0符合物理常识。至此软件解析逻辑与硬件收发链路全部验证通过。调试踩坑记录在初次上电后通过串口打印调试我发现串口并没有将数据实时发送到电脑上并且我利用逻辑分析仪抓取波形发现并没有捕捉到数据帧的波形。内心一度怀疑传感器是不是买到了坏件。冷静下来后没有急着改代码而是先用万用表排除了供电和虚焊问题最后回头翻看传感器数据手册才发现自己初始化USART时将UART的波特率设置成了115200bit/s传感器只需4800bit/s。修改UART波特率之后重新烧录波形瞬间跳了出来不得不说调Modbus通信时逻辑分析仪比串口助手靠谱一万倍。总结其实RS-485和RS-232都是依托于UART进行通信的他们都属于是物理层的电气标准。通过本次STM32驱动土壤温湿度传感器的项目实践我们成功实现了基于RS-485总线和Modbus-RTU协议的工业级传感器数据采集。后续将持续更新I2C,SPI,CAN总线的实战大家敬请期待

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价