STM32F030 + SHT15 + Modbus RTU 工程
概述
在STM32F030平台上实现SHT15温湿度传感器数据采集,并通过Modbus RTU协议对外提供数据。
硬件连接
1. STM32F030 引脚分配
SHT15连接:
DATA -> PA0 (GPIO_Input) 需接10K上拉电阻
SCK -> PA1 (GPIO_Output)
VDD -> 3.3V
GND -> GND
UART连接(Modbus RTU):
USART1_TX -> PA9
USART1_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(); // 初始化SHT15
MODBUS_Init(9600); // 初始化Modbus, 波特率9600
SysTick_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);
else
GPIO_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个时钟周期读取ACK
GPIO_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/NACK
GPIO_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);
else
GPIO_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;
else
crc <<= 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); // 高字节,ACK
data[1] = SHT15_ReadByte(1); // 低字节,ACK
data[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_RX
GPIO_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);
// 配置USART1
USART_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);
// 使能USART1
USART_Cmd(USART1, ENABLE);
// 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 空闲中断用于检测帧结束
// 配置NVIC
NVIC_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;
}
// 计算CRC
crc = 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工程配置
项目设置:
-
目标芯片:STM32F030C8Tx
-
时钟配置:8MHz HSI,系统时钟48MHz
-
优化级别:-O1
-
包含路径:
- .\
- ..\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. 调试建议
-
硬件连接检查:
- 确保SHT15的DATA线有上拉电阻
- 确保RS485方向控制正确
- 检查电源电压3.3V稳定
-
软件调试:
- 先单独测试SHT15读取
- 再测试串口通信
- 最后测试完整的Modbus协议
-
优化方向:
- 添加看门狗
- 实现EEPROM保存设备地址
- 添加Modbus异常处理
- 实现软件CRC校验