当前位置: 首页 > news >正文

STM32F030 + SHT15 + Modbus RTU 工程

概述

在STM32F030平台上实现SHT15温湿度传感器数据采集,并通过Modbus RTU协议对外提供数据。

硬件连接

1. STM32F030 引脚分配

SHT15连接:DATA  -> PA0 (GPIO_Input) 需接10K上拉电阻SCK   -> PA1 (GPIO_Output)VDD   -> 3.3VGND   -> GNDUART连接(Modbus RTU):USART1_TX -> PA9USART1_RX -> PA10(RS485接口需通过MAX485芯片转换)

工程代码

1. 主程序 (main.c)

#include "stm32f0xx.h"
#include "sht15.h"
#include "modbus.h"
#include "delay.h"
#include <string.h>// Modbus保持寄存器定义
volatile uint16_t modbus_registers[MODBUS_REG_COUNT] = {0x0000,  // 寄存器0: 设备状态0x0000,  // 寄存器1: 温度值*10 (整数)0x0000,  // 寄存器2: 湿度值*10 (整数)0x0000,  // 寄存器3: 温度小数部分0x0000,  // 寄存器4: 湿度小数部分0x0000,  // 寄存器5: CRC校验0x0000,  // 寄存器6: 设备地址0x0000,  // 寄存器7: 波特率设置
};// 全局变量
static uint8_t device_address = 0x01;  // Modbus设备地址
static float temperature = 0.0;
static float humidity = 0.0;int main(void)
{// 系统时钟初始化SystemInit();RCC->AHBENR |= RCC_AHBENR_GPIOAEN;  // 使能GPIOA时钟// 初始化外设SHT15_Init();        // 初始化SHT15MODBUS_Init(9600);   // 初始化Modbus, 波特率9600SysTick_Init();      // 初始化系统滴答定时器// 设置设备地址modbus_registers[6] = device_address;while(1){// 每秒读取一次温湿度if(SHT15_Read(&temperature, &humidity) == 0){// 将浮点数转换为Modbus寄存器格式// 温度: 整数部分放大10倍存储int16_t temp_int = (int16_t)temperature;int16_t temp_frac = (int16_t)((temperature - temp_int) * 100);modbus_registers[1] = (uint16_t)temp_int;modbus_registers[3] = (uint16_t)temp_frac;// 湿度: 整数部分放大10倍存储int16_t hum_int = (int16_t)humidity;int16_t hum_frac = (int16_t)((humidity - hum_int) * 100);modbus_registers[2] = (uint16_t)hum_int;modbus_registers[4] = (uint16_t)hum_frac;}// 处理Modbus通信MODBUS_Process();Delay_ms(1000);  // 1秒采样间隔}
}

2. SHT15驱动 (sht15.h)

#ifndef __SHT15_H
#define __SHT15_H#include "stm32f0xx.h"// 引脚定义
#define SHT15_DATA_PIN      GPIO_Pin_0
#define SHT15_SCK_PIN       GPIO_Pin_1
#define SHT15_GPIO          GPIOA// 命令定义
#define SHT15_MEASURE_TEMP  0x03
#define SHT15_MEASURE_HUMI  0x05
#define SHT15_WRITE_STATUS  0x06
#define SHT15_READ_STATUS   0x07
#define SHT15_RESET         0x1E// 状态寄存器位定义
#define STATUS_LOW_RES      0x01
#define STATUS_NO_OTP_RELOAD 0x02
#define STATUS_HEATER       0x04
#define STATUS_BATTERY_LOW  0x40// 错误码
#define SHT15_OK            0
#define SHT15_ERR_COMM      1
#define SHT15_ERR_CRC       2
#define SHT15_ERR_TIMEOUT   3// 函数声明
void SHT15_Init(void);
uint8_t SHT15_Read(float *temperature, float *humidity);
static uint8_t SHT15_Measure(uint16_t *value, uint8_t mode);
static uint8_t SHT15_CRC_Check(uint8_t *data, uint8_t len);
static void SHT15_TransStart(void);
static uint8_t SHT15_WriteByte(uint8_t data);
static uint8_t SHT15_ReadByte(uint8_t ack);
static void SHT15_ConnectionReset(void);
static float SHT15_CalcTemperature(uint16_t raw);
static float SHT15_CalcHumidity(uint16_t raw, float temp);#endif

3. SHT15驱动 (sht15.c)

#include "sht15.h"
#include "delay.h"
#include <math.h>// 初始化SHT15
void SHT15_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct;// 使能GPIOA时钟RCC->AHBENR |= RCC_AHBENR_GPIOAEN;// 配置DATA引脚为开漏输出(初始状态)GPIO_InitStruct.GPIO_Pin = SHT15_DATA_PIN;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;  // 开漏输出GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;    // 上拉GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;GPIO_Init(SHT15_GPIO, &GPIO_InitStruct);// 配置SCK引脚为推挽输出GPIO_InitStruct.GPIO_Pin = SHT15_SCK_PIN;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;  // 推挽输出GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;GPIO_Init(SHT15_GPIO, &GPIO_InitStruct);// 初始状态GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);GPIO_SetBits(SHT15_GPIO, SHT15_DATA_PIN);Delay_ms(20);  // 等待传感器稳定// 发送复位命令SHT15_ConnectionReset();
}// 启动传输序列
static void SHT15_TransStart(void)
{GPIO_SetBits(SHT15_GPIO, SHT15_DATA_PIN);GPIO_SetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);GPIO_ResetBits(SHT15_GPIO, SHT15_DATA_PIN);Delay_us(5);GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);GPIO_SetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);GPIO_SetBits(SHT15_GPIO, SHT15_DATA_PIN);Delay_us(5);GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);
}// 写一个字节到SHT15
static uint8_t SHT15_WriteByte(uint8_t data)
{uint8_t i, ack = 0;// 设置DATA为输出模式GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = SHT15_DATA_PIN;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;GPIO_Init(SHT15_GPIO, &GPIO_InitStruct);// 发送8位数据for(i = 0; i < 8; i++){if(data & 0x80)GPIO_SetBits(SHT15_GPIO, SHT15_DATA_PIN);elseGPIO_ResetBits(SHT15_GPIO, SHT15_DATA_PIN);data <<= 1;GPIO_SetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);}// 在第9个时钟周期读取ACKGPIO_SetBits(SHT15_GPIO, SHT15_SCK_PIN);// 设置DATA为输入模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(SHT15_GPIO, &GPIO_InitStruct);Delay_us(5);ack = GPIO_ReadInputDataBit(SHT15_GPIO, SHT15_DATA_PIN);GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);return (ack == 0);  // 返回ACK是否有效
}// 从SHT15读取一个字节
static uint8_t SHT15_ReadByte(uint8_t ack)
{uint8_t i, data = 0;// 设置DATA为输入模式GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = SHT15_DATA_PIN;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(SHT15_GPIO, &GPIO_InitStruct);// 读取8位数据for(i = 0; i < 8; i++){data <<= 1;GPIO_SetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);if(GPIO_ReadInputDataBit(SHT15_GPIO, SHT15_DATA_PIN))data |= 0x01;GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);}// 发送ACK/NACKGPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;GPIO_Init(SHT15_GPIO, &GPIO_InitStruct);if(ack)GPIO_ResetBits(SHT15_GPIO, SHT15_DATA_PIN);elseGPIO_SetBits(SHT15_GPIO, SHT15_DATA_PIN);GPIO_SetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);// 释放DATA线GPIO_SetBits(SHT15_GPIO, SHT15_DATA_PIN);return data;
}// 连接复位
static void SHT15_ConnectionReset(void)
{uint8_t i;// 发送9个时钟脉冲,DATA保持高电平GPIO_SetBits(SHT15_GPIO, SHT15_DATA_PIN);for(i = 0; i < 9; i++){GPIO_SetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);GPIO_ResetBits(SHT15_GPIO, SHT15_SCK_PIN);Delay_us(5);}// 发送传输启动序列SHT15_TransStart();
}// CRC校验
static uint8_t SHT15_CRC_Check(uint8_t *data, uint8_t len)
{uint8_t crc = 0;uint8_t i, j;for(i = 0; i < len; i++){crc ^= data[i];for(j = 0; j < 8; j++){if(crc & 0x80)crc = (crc << 1) ^ 0x31;elsecrc <<= 1;}}return crc;
}// 测量温湿度
static uint8_t SHT15_Measure(uint16_t *value, uint8_t mode)
{uint8_t data[3];uint8_t crc, i;// 发送启动序列SHT15_TransStart();// 发送测量命令if(!SHT15_WriteByte(mode))return SHT15_ERR_COMM;// 等待测量完成GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = SHT15_DATA_PIN;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(SHT15_GPIO, &GPIO_InitStruct);for(i = 0; i < 100; i++)  // 等待最多100ms{if(GPIO_ReadInputDataBit(SHT15_GPIO, SHT15_DATA_PIN) == 0)break;Delay_ms(1);}if(i >= 100)return SHT15_ERR_TIMEOUT;// 读取测量结果data[0] = SHT15_ReadByte(1);  // 高字节,ACKdata[1] = SHT15_ReadByte(1);  // 低字节,ACKdata[2] = SHT15_ReadByte(0);  // CRC,NACK// CRC校验crc = SHT15_CRC_Check(data, 2);if(crc != data[2])return SHT15_ERR_CRC;*value = (data[0] << 8) | data[1];return SHT15_OK;
}// 计算温度
static float SHT15_CalcTemperature(uint16_t raw)
{float temperature;// 12位精度temperature = (float)raw * 0.01 - 40.0;return temperature;
}// 计算相对湿度
static float SHT15_CalcHumidity(uint16_t raw, float temp)
{float humidity, linear_humidity;// 线性转换linear_humidity = -2.0468 + 0.0367 * raw - 1.5955E-6 * raw * raw;// 温度补偿humidity = (temp - 25.0) * (0.01 + 0.00008 * raw) + linear_humidity;if(humidity > 100.0) humidity = 100.0;if(humidity < 0.1) humidity = 0.1;return humidity;
}// 读取温湿度
uint8_t SHT15_Read(float *temperature, float *humidity)
{uint16_t raw_temp, raw_humi;uint8_t ret;// 测量温度ret = SHT15_Measure(&raw_temp, SHT15_MEASURE_TEMP);if(ret != SHT15_OK)return ret;*temperature = SHT15_CalcTemperature(raw_temp);// 测量湿度ret = SHT15_Measure(&raw_humi, SHT15_MEASURE_HUMI);if(ret != SHT15_OK)return ret;*humidity = SHT15_CalcHumidity(raw_humi, *temperature);return SHT15_OK;
}

4. Modbus协议栈 (modbus.h)

#ifndef __MODBUS_H
#define __MODBUS_H#include "stm32f0xx.h"
#include <stdint.h>// Modbus寄存器定义
#define MODBUS_REG_COUNT    16
#define MODBUS_HOLDING_START 0// Modbus功能码
#define MODBUS_FC_READ_COILS          0x01
#define MODBUS_FC_READ_DISCRETE_INPUTS 0x02
#define MODBUS_FC_READ_HOLDING_REGS    0x03
#define MODBUS_FC_READ_INPUT_REGS      0x04
#define MODBUS_FC_WRITE_SINGLE_COIL    0x05
#define MODBUS_FC_WRITE_SINGLE_REG     0x06
#define MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F
#define MODBUS_FC_WRITE_MULTIPLE_REGS  0x10// Modbus异常码
#define MODBUS_EX_NONE                0x00
#define MODBUS_EX_ILLEGAL_FUNCTION    0x01
#define MODBUS_EX_ILLEGAL_DATA_ADDRESS 0x02
#define MODBUS_EX_ILLEGAL_DATA_VALUE   0x03
#define MODBUS_EX_SLAVE_DEVICE_FAILURE 0x04// 缓冲区大小
#define MODBUS_BUF_SIZE    256
#define MODBUS_FRAME_SIZE  128// Modbus帧结构
typedef struct {uint8_t  address;uint8_t  function;uint16_t starting_address;uint16_t quantity;uint16_t byte_count;uint8_t  data[MODBUS_FRAME_SIZE];uint16_t crc;
} MODBUS_Frame;// 函数声明
void MODBUS_Init(uint32_t baudrate);
void MODBUS_Process(void);
void USART1_IRQHandler(void);
uint16_t MODBUS_CRC16(uint8_t *buf, uint16_t len);
void MODBUS_Send_Exception(uint8_t slave_addr, uint8_t function, uint8_t exception);
void MODBUS_Send_Response(uint8_t *data, uint16_t len);// 外部变量
extern volatile uint16_t modbus_registers[MODBUS_REG_COUNT];#endif

5. Modbus协议栈 (modbus.c)

#include "modbus.h"
#include "delay.h"
#include <string.h>// Modbus接收缓冲区
static uint8_t modbus_rx_buf[MODBUS_BUF_SIZE];
static uint16_t modbus_rx_index = 0;
static uint8_t modbus_rx_complete = 0;// Modbus发送缓冲区
static uint8_t modbus_tx_buf[MODBUS_BUF_SIZE];
static uint16_t modbus_tx_index = 0;
static uint16_t modbus_tx_len = 0;// 超时计时
static uint32_t modbus_timeout = 0;
#define MODBUS_TIMEOUT_MS  50  // 帧间隔超时时间// 初始化Modbus
void MODBUS_Init(uint32_t baudrate)
{GPIO_InitTypeDef GPIO_InitStruct;USART_InitTypeDef USART_InitStruct;NVIC_InitTypeDef NVIC_InitStruct;// 使能时钟RCC->AHBENR |= RCC_AHBENR_GPIOAEN;      // 使能GPIOA时钟RCC->APB2ENR |= RCC_APB2ENR_USART1EN;  // 使能USART1时钟// 配置USART1引脚// PA9 - USART1_TX// PA10 - USART1_RXGPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;  // 复用功能GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_2;GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;GPIO_Init(GPIOA, &GPIO_InitStruct);// 配置AF功能GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);// 配置USART1USART_InitStruct.USART_BaudRate = baudrate;USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_InitStruct.USART_StopBits = USART_StopBits_1;USART_InitStruct.USART_Parity = USART_Parity_No;USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_Init(USART1, &USART_InitStruct);// 使能USART1USART_Cmd(USART1, ENABLE);// 使能接收中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);  // 空闲中断用于检测帧结束// 配置NVICNVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStruct.NVIC_IRQChannelPriority = 0;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStruct);
}// USART1中断处理
void USART1_IRQHandler(void)
{uint8_t data;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){data = USART_ReceiveData(USART1);if(modbus_rx_index < MODBUS_BUF_SIZE){modbus_rx_buf[modbus_rx_index++] = data;}modbus_timeout = 0;  // 重置超时计时}else if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){// 空闲中断,表示一帧数据接收完成data = USART_ReceiveData(USART1);  // 读DR清空中断(void)data;if(modbus_rx_index > 0){modbus_rx_complete = 1;}}if(USART_GetITStatus(USART1, USART_IT_TC) != RESET){// 发送完成中断USART_ClearITPendingBit(USART1, USART_IT_TC);}
}// CRC16计算
uint16_t MODBUS_CRC16(uint8_t *buf, uint16_t len)
{uint16_t crc = 0xFFFF;uint16_t i, j;for(i = 0; i < len; i++){crc ^= buf[i];for(j = 0; j < 8; j++){if(crc & 0x0001){crc >>= 1;crc ^= 0xA001;}else{crc >>= 1;}}}return crc;
}// 发送异常响应
void MODBUS_Send_Exception(uint8_t slave_addr, uint8_t function, uint8_t exception)
{uint8_t tx_buf[5];uint16_t crc;tx_buf[0] = slave_addr;tx_buf[1] = function | 0x80;  // 异常功能码tx_buf[2] = exception;crc = MODBUS_CRC16(tx_buf, 3);tx_buf[3] = crc & 0xFF;tx_buf[4] = (crc >> 8) & 0xFF;// 发送数据for(uint8_t i = 0; i < 5; i++){while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, tx_buf[i]);}
}// 发送响应
void MODBUS_Send_Response(uint8_t *data, uint16_t len)
{for(uint16_t i = 0; i < len; i++){while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, data[i]);}
}// 处理读保持寄存器(功能码03)
static uint8_t MODBUS_Handle_Read_Holding_Registers(uint8_t *frame, uint16_t len)
{uint8_t response[256];uint16_t start_addr, reg_count, byte_count;uint16_t crc, i;if(len < 8)  // 至少需要地址(1)+功能码(1)+起始地址(2)+数量(2)+CRC(2)return MODBUS_EX_ILLEGAL_FUNCTION;// 解析起始地址和寄存器数量start_addr = (frame[2] << 8) | frame[3];reg_count = (frame[4] << 8) | frame[5];// 校验地址范围if(start_addr >= MODBUS_REG_COUNT || (start_addr + reg_count) > MODBUS_REG_COUNT ||reg_count == 0 || reg_count > 125){return MODBUS_EX_ILLEGAL_DATA_ADDRESS;}// 构建响应byte_count = reg_count * 2;response[0] = frame[0];  // 地址response[1] = 0x03;      // 功能码response[2] = byte_count; // 字节数// 复制寄存器数据for(i = 0; i < reg_count; i++){response[3 + i * 2] = (modbus_registers[start_addr + i] >> 8) & 0xFF;response[4 + i * 2] = modbus_registers[start_addr + i] & 0xFF;}// 计算CRCcrc = MODBUS_CRC16(response, 3 + byte_count);response[3 + byte_count] = crc & 0xFF;response[4 + byte_count] = (crc >> 8) & 0xFF;// 发送响应MODBUS_Send_Response(response, 5 + byte_count);return MODBUS_EX_NONE;
}// 处理写单个寄存器(功能码06)
static uint8_t MODBUS_Handle_Write_Single_Register(uint8_t *frame, uint16_t len)
{uint8_t response[8];uint16_t reg_addr, reg_value, crc;if(len < 8)return MODBUS_EX_ILLEGAL_FUNCTION;// 解析寄存器地址和值reg_addr = (frame[2] << 8) | frame[3];reg_value = (frame[4] << 8) | frame[5];// 校验地址范围if(reg_addr >= MODBUS_REG_COUNT){return MODBUS_EX_ILLEGAL_DATA_ADDRESS;}// 写入寄存器modbus_registers[reg_addr] = reg_value;// 特殊寄存器处理if(reg_addr == 6)  // 设备地址{// 更新设备地址}else if(reg_addr == 7)  // 波特率设置{// 波特率设置处理}// 构建响应(回显写入的数据)response[0] = frame[0];response[1] = 0x06;response[2] = frame[2];response[3] = frame[3];response[4] = frame[4];response[5] = frame[5];crc = MODBUS_CRC16(response, 6);response[6] = crc & 0xFF;response[7] = (crc >> 8) & 0xFF;MODBUS_Send_Response(response, 8);return MODBUS_EX_NONE;
}// 处理写多个寄存器(功能码16)
static uint8_t MODBUS_Handle_Write_Multiple_Registers(uint8_t *frame, uint16_t len)
{uint8_t response[8];uint16_t start_addr, reg_count, byte_count;uint16_t crc, i;if(len < 9)return MODBUS_EX_ILLEGAL_FUNCTION;start_addr = (frame[2] << 8) | frame[3];reg_count = (frame[4] << 8) | frame[5];byte_count = frame[6];// 校验if(start_addr >= MODBUS_REG_COUNT || (start_addr + reg_count) > MODBUS_REG_COUNT ||reg_count == 0 || reg_count > 123 ||byte_count != reg_count * 2){return MODBUS_EX_ILLEGAL_DATA_ADDRESS;}// 写入寄存器for(i = 0; i < reg_count; i++){modbus_registers[start_addr + i] = (frame[7 + i * 2] << 8) | frame[8 + i * 2];}// 构建响应response[0] = frame[0];response[1] = 0x10;response[2] = frame[2];response[3] = frame[3];response[4] = frame[4];response[5] = frame[5];crc = MODBUS_CRC16(response, 6);response[6] = crc & 0xFF;response[7] = (crc >> 8) & 0xFF;MODBUS_Send_Response(response, 8);return MODBUS_EX_NONE;
}// 处理Modbus帧
static void MODBUS_Process_Frame(uint8_t *frame, uint16_t len)
{uint8_t slave_addr = frame[0];uint8_t function_code = frame[1];uint16_t crc_received, crc_calculated;uint8_t exception = MODBUS_EX_NONE;// 检查最小长度if(len < 4)  // 至少需要地址+功能码+CRC(2)return;// CRC校验crc_received = (frame[len-1] << 8) | frame[len-2];crc_calculated = MODBUS_CRC16(frame, len-2);if(crc_received != crc_calculated)return;  // CRC错误,丢弃帧// 检查地址是否匹配if(slave_addr != 0x01 && slave_addr != 0xFF)  // 0xFF是广播地址return;// 根据功能码处理switch(function_code){case MODBUS_FC_READ_HOLDING_REGS:exception = MODBUS_Handle_Read_Holding_Registers(frame, len);break;case MODBUS_FC_WRITE_SINGLE_REG:exception = MODBUS_Handle_Write_Single_Register(frame, len);break;case MODBUS_FC_WRITE_MULTIPLE_REGS:exception = MODBUS_Handle_Write_Multiple_Registers(frame, len);break;default:exception = MODBUS_EX_ILLEGAL_FUNCTION;break;}// 发送异常响应if(exception != MODBUS_EX_NONE && slave_addr != 0xFF){MODBUS_Send_Exception(slave_addr, function_code, exception);}
}// 主处理函数
void MODBUS_Process(void)
{static uint32_t last_tick = 0;uint32_t current_tick = SysTick_GetTick();// 超时检测if(modbus_rx_index > 0 && (current_tick - modbus_timeout) > MODBUS_TIMEOUT_MS){modbus_rx_complete = 1;}// 处理接收完成的帧if(modbus_rx_complete){MODBUS_Process_Frame(modbus_rx_buf, modbus_rx_index);// 清除接收状态modbus_rx_index = 0;modbus_rx_complete = 0;}
}

6. 延时函数 (delay.h/c)

// delay.h
#ifndef __DELAY_H
#define __DELAY_H#include "stm32f0xx.h"void SysTick_Init(void);
uint32_t SysTick_GetTick(void);
void Delay_ms(uint32_t ms);
void Delay_us(uint32_t us);#endif// delay.c
#include "delay.h"static volatile uint32_t sys_tick = 0;void SysTick_Init(void)
{// 配置SysTick为1ms中断if(SysTick_Config(SystemCoreClock / 1000)){while(1);}
}uint32_t SysTick_GetTick(void)
{return sys_tick;
}void Delay_ms(uint32_t ms)
{uint32_t tickstart = SysTick_GetTick();while((SysTick_GetTick() - tickstart) < ms);
}void Delay_us(uint32_t us)
{uint32_t i;for(i = 0; i < us * 8; i++)  // 根据时钟频率调整{__NOP();}
}// SysTick中断处理函数
void SysTick_Handler(void)
{sys_tick++;
}

参考代码 实现温湿度传感器 www.youwenfan.com/contentcnu/56262.html

7. KEIL工程配置

项目设置:

  1. 目标芯片:STM32F030C8Tx
  2. 时钟配置:8MHz HSI,系统时钟48MHz
  3. 优化级别:-O1
  4. 包含路径
    • .\
    • ..\CMSIS
    • ..\StdPeriph_Driver\inc

链接器设置:

--cpu Cortex-M0
--library_type=microlib
--strict
--scatter ".\STM32F030C8Tx.sct"

8. Modbus测试工具使用

使用Modbus Poll或ModScan等工具测试:

读取温度:

设备地址: 0x01
功能码: 0x03
起始地址: 0x0001
寄存器数量: 2
返回:温度值(寄存器1-2)、湿度值(寄存器3-4)

设置设备地址:

功能码: 0x06
寄存器地址: 0x0006
寄存器值: 新地址

9. 调试建议

  1. 硬件连接检查

    • 确保SHT15的DATA线有上拉电阻
    • 确保RS485方向控制正确
    • 检查电源电压3.3V稳定
  2. 软件调试

    • 先单独测试SHT15读取
    • 再测试串口通信
    • 最后测试完整的Modbus协议
  3. 优化方向

    • 添加看门狗
    • 实现EEPROM保存设备地址
    • 添加Modbus异常处理
    • 实现软件CRC校验
http://www.jsqmd.com/news/758209/

相关文章:

  • AML模组启动器:XCOM 2终极模组管理解决方案
  • Dify调试不看日志=裸泳!深度拆解worker.log、api.log、orchestrator.trace三日志协同分析法(内部培训PPT首次公开)
  • 5步轻松上手:原神模型导入工具GIMI完全指南
  • LangChain 动态模型中间件实战使用技巧
  • 2026年4月类Claude Code平台公司推荐,类Claude Code平台,类Claude Code平台产品推荐 - 品牌推荐师
  • 消息队列适用场景
  • 【信创攻坚权威手册】:基于200+政企真实环境数据,Docker 27国产化适配成功率提升至96.7%
  • 辉芒微FT61EC21A-RB芯片评测:SOP8封装下的ADC+PWM,做小风扇调速器到底行不行?
  • RTranslator终极指南:实现完全离线的多设备实时翻译体验
  • 5分钟快速上手:MelonLoader模组加载器终极使用指南
  • 用Arduino和FS-i6X遥控器,从零复现一只会飞的仿生蝴蝶(附完整代码与调试心得)
  • Docker Compose 启动报错 exit code 137 内存不足怎么解决
  • 使用 OpenClaw 时通过 Taotoken 接入多模型 Agent 工作流
  • RocketMQ实战:用MySQL唯一索引和Redis锁搞定消息重复消费(附完整代码)
  • 对比自行维护与通过Taotoken调用大模型API在稳定性上的体验差异
  • 亨得利维修保养服务电话400-901-0695|官方直营门店地址与保养周期全攻略 - 时光修表匠
  • 英雄联盟Akari助手:5个核心功能解决你的游戏痛点
  • Gemini3.1Pro:你的高效办公新搭档
  • 终极解决方案:VisualCppRedist AIO项目完全部署与维护指南
  • 亨得利手表维修保养服务地址电话终极指南:2026年腕表保养周期与成本数据全曝光(附六大直营门店址) - 时光修表匠
  • Android开发工程师:聚焦蓝牙与WiFi技术的实践指南
  • 亨得利维修保养服务电话400-901-0695|官方直营门店地址与维修资质全解析 - 时光修表匠
  • League Akari终极指南:基于LCU API的英雄联盟自动化工具集开发与实战
  • 3个关键步骤,让你的加密音乐重获自由:Unlock-Music浏览器解密完全指南
  • 2026年5月江诗丹顿官方售后网点亲测报告:避坑指南与真实体验 - 亨得利官方服务中心
  • 别再死记硬背了!用立创EDA仿真,5分钟搞懂三极管静态工作点怎么选
  • GDAL—瓦片格式栅格数据创建和修改
  • 保姆级教程:用STM32CubeMX HAL库驱动SG90舵机(附完整代码和接线图)
  • 语聊社交变现核武!盲盒V6MAX源码系统小程序解析,海外盲盒源码与国际版盲盒源码赋能盲盒定制开发,颠覆盲盒app源码程序 - 壹软科技
  • 亨得利维修保养服务电话400-901-0695|官方直营门店地址与2024最新维修数据全公开 - 时光修表匠