硬件配置
采用RS3485芯片,供电电压3V3,EN引脚连接GPIOA1(可更改)
采用SHT20芯片RS485版本,V+接5V,V-接GND;
另外单片机再接一路串口给电脑用来显示。

引脚设置
PA1设置为GPIO_OUTPUT,默认拉低
USART1使能,配置默认;
USART2使能,波特率改为9600并开启NVIC中断;

协议知识
RS485是一种硬件电气标准,它定义了设备之间如何进行物理连接和信号传输。你可以把它想象成一条专为工业环境设计的高速公路。
一条RS485总线可以连接多个设备(理论上最多32个标准负载,使用中继器可扩展至更多),形成总线型网络,大大简化了布线。
在大多数应用中,RS485采用半双工模式,即数据在同一时间只能单向传输,要么发,要么收,这简化了总线结构并避免了冲突。
Modbus是一种通信协议,它定义了设备之间交换数据的格式和规则。如果说RS485是路,Modbus就是路上车辆必须遵守的交通规则,确保所有设备都能"说同一种语言"。
通信流程:
- 主芯片想要读取1号从站(如温度传感器)的数据。
- 主站按照Modbus协议的规则,打包一个请求报文,其中包含从站地址(1)、功能码(如"读寄存器")等信息。
- 这个报文通过RS485接口转换成电信号,在总线上广播出去。
- 总线上所有设备都能收到信号,但只有地址为1的从站会识别并处理这个请求。
- 1号从站将温度值打包成Modbus响应报文,通过RS485接口发回给主站。
本次所用到的是标准Modbus RTU协议

代码编写
逻辑比较简单,发送指令,收到指令开启中断并将结果拷贝通过另一路串口发送到电脑
cpp
#ifndef __SHT20_H
#define __SHT20_H
#include "main.h"
#include <string.h>
#include "usart.h"
//------------------------数据变量定义------------------------------------
extern uint8_t uart2_rx_buf[];
extern uint8_t uart1_tx_buf[];
//------------------------外部接口函数------------------------------------
void UART2_To_UART1_Forward(void);
void RS485_Send(uint8_t *data, uint16_t len);
void Test_Sht20(void);
//------------------------硬件接口配置------------------------------------
#define RS485_EN_GPIO_Port GPIOA
#define RS485_EN_Pin GPIO_PIN_1
#define RS485_RX_MODE() HAL_GPIO_WritePin(RS485_EN_GPIO_Port, RS485_EN_Pin, GPIO_PIN_RESET) // 低电平=接收
#define RS485_TX_MODE() HAL_GPIO_WritePin(RS485_EN_GPIO_Port, RS485_EN_Pin, GPIO_PIN_SET) // 高电平=发送
//-----------------------------------------------------------------------
#endif
cpp
#include "sht20.h"
//------------------------数据变量定义------------------------------------
uint8_t uart2_rx_buf[16];
uint8_t uart1_tx_buf[16];
volatile uint16_t rx_len = 0;
const uint8_t SHT20_CMD_READ_TEMP[8] = {0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x60, 0x0A};
const uint8_t SHT20_CMD_READ_HUMI[8] = {0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x90, 0x0A};
const uint8_t SHT20_CMD_READ_BOTH[8] = {0x01, 0x04, 0x00, 0x01, 0x00, 0x02, 0x20, 0x0B};
const uint8_t SHT20_CMD_READ_ADDR[8] = {0x01, 0x03, 0x01, 0x01, 0x00, 0x01, 0x20, 0x0B};
//-----------------------具体函数实现------------------------------------
void UART2_To_UART1_Forward(void)
{
if (rx_len == 0) return;
memcpy(uart1_tx_buf, uart2_rx_buf, rx_len);
HAL_UART_Transmit(&huart1, uart1_tx_buf, rx_len, 100);
rx_len = 0;
}
//-----------------------------------------------------------------------
void RS485_Send(uint8_t *data, uint16_t len)
{
RS485_TX_MODE(); //切换为发送模式
HAL_UART_Transmit(&huart2, data, len, 100);
while (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC) == RESET);
RS485_RX_MODE(); //切回接收
}
//-----------------------------------------------------------------------
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
static uint16_t rx_index = 0;
static uint32_t last_rx_time = 0;
if (huart->Instance == USART2)
{
uint32_t now = HAL_GetTick();
if (now - last_rx_time > 10 && rx_index > 0) rx_index = 0;
last_rx_time = now;
if (rx_index < 16 - 1) rx_index++;
HAL_UART_Receive_IT(&huart2, &uart2_rx_buf[rx_index], 1);
if (rx_index >= 7) {
rx_len = rx_index;
rx_index = 0;
UART2_To_UART1_Forward();
}
}
}
//-----------------------------------------------------------------------
void Test_Sht20(void)
{
HAL_UART_Receive_IT(&huart2, uart2_rx_buf, 1);
while(1)
{
RS485_Send((uint8_t *)SHT20_CMD_READ_TEMP, 8);
HAL_Delay(1000);
RS485_Send((uint8_t *)SHT20_CMD_READ_HUMI, 8);
HAL_Delay(1000);
RS485_Send((uint8_t *)SHT20_CMD_READ_BOTH, 8);
HAL_Delay(1000);
}
}
测试结果

以上就完成了模块的测试,后续优化可参考
①目前是根据字节数判断截止,但是返回字节数量并不固定,可以用时间戳来实现
②后续不可能都在节点1上,可以考虑自动CRC生成,但器件少的情况更建议手动配置数组