1. STM32F103C8T6与DL10B-232-V05温度传感器通信方案解析
在工业控制和物联网应用中,温度监测是最基础也最关键的环节之一。DL10B-232-V05是一款基于RS232接口的数字温度传感器,采用Modbus协议通信,具有测量精度高、抗干扰能力强等特点。而STM32F103C8T6作为经典的Cortex-M3内核单片机,凭借其丰富的外设资源和稳定的性能,成为嵌入式开发的理想选择。
本项目实现了STM32F103C8T6通过状态机机制与DL10B-232-V05传感器的稳定通信,主要解决以下问题:
- 异步串行通信的时序管理
- Modbus协议帧的完整性和正确性校验
- 通信异常时的自动重试机制
- 温度数据的实时显示
2. 硬件连接与初始化配置
2.1 硬件接口设计
DL10B-232-V05传感器采用标准的RS232接口,而STM32F103C8T6内置的是USART外设,两者电气电平不兼容,需要进行电平转换。推荐使用MAX3232等RS232电平转换芯片,具体连接方式如下:
code复制传感器 电平转换芯片 STM32
TXD ---- R1IN T1OUT ---- PA10(USART1_RX)
RXD ---- T1IN R1OUT ---- PA9(USART1_TX)
GND ------------------------ GND
注意:RS232通信必须共地,否则无法建立稳定的通信链路。同时建议在信号线上添加0.1uF的滤波电容。
2.2 外设初始化代码实现
系统初始化包含三个关键部分:
c复制// 在main函数中的初始化序列
int main(void)
{
OLED_Init(); // 显示模块初始化
DL10_Init(); // 串口及传感器初始化
Timer_Init(); // 定时器初始化
last_action_time = GetTick();
Delay_ms(2000); // 上电稳定等待
while(1) {
// 状态机主循环
}
}
其中DL10_Init()的实现要点:
c复制void DL10_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 配置USART1 Tx (PA9) 为推挽复用输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART1 Rx (PA10) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART参数配置
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_Cmd(USART1, ENABLE);
// NVIC配置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
3. 通信状态机设计与实现
3.1 状态机整体架构
状态机是异步串行通信管理的核心模式,本设计采用五种状态确保通信可靠性:
c复制typedef enum {
STATE_IDLE, // 空闲状态
STATE_SEND_REQUEST, // 发送请求
STATE_WAIT_RESPONSE, // 等待响应
STATE_PROCRSS_RESPONSE, // 处理响应
STATE_RETRY_DELAY, // 重试延迟
} CommState_t;
状态转移图如下:
code复制[IDLE] --1s超时--> [SEND_REQUEST] --发送完成--> [WAIT_RESPONSE]
^ |
| v
|------[成功处理]<--[PROCESS_RESPONSE]<--[收到数据]
| |
| v
\------------------[重试失败]<--[RETRY_DELAY]
3.2 各状态详细实现
3.2.1 空闲状态(STATE_IDLE)
c复制case STATE_IDLE:
if((GetTick()-last_action_time) >= 1000) { // 1秒周期
Reset_States();
State = STATE_SEND_REQUEST;
}
break;
这是系统的默认状态,每隔1秒触发一次温度读取。GetTick()函数获取系统运行时间(单位ms),通过定时器中断维护。
3.2.2 发送请求状态(STATE_SEND_REQUEST)
c复制case STATE_SEND_REQUEST:
SendData_DL10(); // 发送Modbus请求帧
WaitForResponse = 1;
last_action_time = GetTick(); // 记录发送时间
State = STATE_WAIT_RESPONSE;
break;
SendData_DL10()函数构造并发送标准的Modbus RTU查询帧:
c复制void SendData_DL10(void)
{
uint8_t query[8] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A};
// 设备地址:0x01
// 功能码:0x03(读保持寄存器)
// 起始地址:0x0000
// 寄存器数量:0x0001
// CRC校验:0x840A
for(int i=0; i<8; i++) {
USART_SendData(USART1, query[i]);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
}
3.2.3 等待响应状态(STATE_WAIT_RESPONSE)
c复制case STATE_WAIT_RESPONSE:
if(RxComplete) { // 收到完整帧
State = STATE_PROCRSS_RESPONSE;
}
else if(CharTimeOutFlag || OverallTimeOutFlag) { // 超时
State = STATE_RETRY_DELAY;
}
else if((GetTick()-last_action_time) >= 200) { // 绝对超时
State = STATE_RETRY_DELAY;
}
break;
超时判断依赖两个标志:
- CharTimeOutFlag:字符间隔超时(>4ms)
- OverallTimeOutFlag:整体响应超时(>50ms)
3.2.4 处理响应状态(STATE_PROCRSS_RESPONSE)
c复制case STATE_PROCRSS_RESPONSE:
if(Check_Crc()) { // CRC校验通过
Dl10_Temp = Process_Data(); // 解析温度值
OLED_ShowFloatNum(0,0,Dl10_Temp,2,2,OLED_8X16);
OLED_Update();
Retry_Count = 0;
State = STATE_IDLE;
}
else { // CRC校验失败
State = STATE_RETRY_DELAY;
}
break;
温度值解析示例(假设传感器返回数据为0x1234,对应温度值为46.6℃):
c复制float Process_Data(void)
{
// 假设接收缓冲区RxBuffer格式:
// [Addr][Func][ByteCount][DataHi][DataLo][CRCLo][CRCHi]
uint16_t raw = (RxBuffer[3] << 8) | RxBuffer[4];
return raw * 0.1f; // 根据传感器手册确定转换系数
}
3.2.5 重试延迟状态(STATE_RETRY_DELAY)
c复制case STATE_RETRY_DELAY:
Retry_Count++;
if(Retry_Count >= 3) { // 超过最大重试次数
Retry_Count = 0;
State = STATE_IDLE;
}
else { // 指数退避算法
backoff_ms = 100 * (1 << (Retry_Count - 1));
Delay_ms(backoff_ms); // 100,200,400ms
State = STATE_SEND_REQUEST;
}
break;
4. 定时器中断与超时管理
4.1 定时器配置
使用TIM2作为系统时基,配置为1ms中断:
c复制void Timer_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 定时器配置:1ms中断
TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz/72=1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 使能中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
4.2 中断服务程序
c复制void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
SystemTick++; // 系统时基计数器
// 字符间隔超时检测(4ms)
if(RxIndex > 0) {
CharIntervalCount++;
if(CharIntervalCount > 4) {
CharTimeOutFlag = 1;
if(!RxComplete && RxIndex > 0) {
RxComplete = 1;
}
}
}
else {
CharIntervalCount = 0;
}
// 整体响应超时检测(50ms)
if(WaitForResponse) {
OverallTimeOutCount++;
if(OverallTimeOutCount > 50) {
OverallTimeOutFlag = 1;
WaitForResponse = 0;
if(!RxComplete && RxIndex > 0) {
RxComplete = 1;
}
}
}
else {
OverallTimeOutCount = 0;
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
5. 关键问题与优化建议
5.1 常见问题排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无任何响应 | 1. 硬件连接错误 2. 波特率不匹配 3. 传感器未上电 |
1. 检查TX/RX交叉连接 2. 确认双方波特率一致 3. 测量传感器供电电压 |
| 收到乱码 | 1. 地线未连接 2. 电磁干扰严重 3. 电平转换芯片故障 |
1. 确保共地 2. 缩短连线并加屏蔽 3. 更换MAX3232芯片 |
| CRC校验失败 | 1. 数据接收不完整 2. 缓冲区溢出 3. 计算算法错误 |
1. 调整超时参数 2. 增大接收缓冲区 3. 核对CRC计算代码 |
5.2 性能优化建议
- 动态调整采样频率:在温度变化缓慢时降低采样率,节省功耗
c复制// 根据温度变化率调整采样间隔
float temp_change = fabs(Dl10_Temp - last_temp);
if(temp_change < 0.5f) {
sample_interval = 5000; // 5秒
} else {
sample_interval = 1000; // 1秒
}
- 增加数据滤波:采用滑动平均或卡尔曼滤波提高数据稳定性
c复制#define FILTER_SIZE 5
float temp_history[FILTER_SIZE];
float filtered_temp = 0;
// 更新滤波队列
for(int i=FILTER_SIZE-1; i>0; i--) {
temp_history[i] = temp_history[i-1];
}
temp_history[0] = Dl10_Temp;
// 计算平均值
for(int i=0; i<FILTER_SIZE; i++) {
filtered_temp += temp_history[i];
}
filtered_temp /= FILTER_SIZE;
- 完善错误处理:记录通信失败次数,超过阈值报警
c复制static uint32_t error_count = 0;
if(Retry_Count >= 3) {
error_count++;
if(error_count > 10) {
OLED_ShowString(0,2,"SENSOR ERROR!",OLED_8X16);
// 可以扩展系统复位或通知上位机
}
}
6. 扩展功能实现
6.1 多传感器支持
通过修改Modbus设备地址实现多传感器轮询:
c复制uint8_t device_ids[] = {0x01, 0x02, 0x03}; // 传感器地址列表
uint8_t current_dev = 0;
void SwitchNextDevice(void)
{
current_dev = (current_dev + 1) % (sizeof(device_ids)/sizeof(device_ids[0]));
}
// 修改查询帧构造
query[0] = device_ids[current_dev]; // 设置目标设备地址
6.2 数据存储与上传
添加SD卡或EEPROM存储历史数据:
c复制void SaveToEEPROM(float temp)
{
// 假设使用AT24C02 EEPROM
uint8_t buf[4];
*(float*)buf = temp;
I2C_WriteBytes(EEPROM_ADDR, current_addr, buf, 4);
current_addr += 4;
if(current_addr >= EEPROM_SIZE) {
current_addr = 0; // 循环写入
}
}
6.3 上位机通信
通过串口将数据发送到PC端:
c复制void SendToPC(float temp)
{
printf("TEMP:%.1fC\r\n", temp); // 通过USART发送
// 或者使用自定义协议
uint8_t frame[8] = {0xAA, 0x55};
*(float*)(frame+2) = temp;
frame[6] = CalcChecksum(frame, 6);
frame[7] = 0x55;
USART_SendArray(USART2, frame, 8);
}
通过这个项目,我们实现了一个稳定可靠的温度监测系统,涵盖了从硬件接口设计、状态机编程到异常处理的完整开发流程。在实际应用中,建议根据具体环境调整超时参数和重试策略,并考虑添加看门狗等可靠性增强措施。
