STM32F030 + SHT15 + Modbus RTU 工程

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工程配置

项目设置:

  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校验

专注于matlab/simulink,电子电路,编程