1. 项目概述:STM32F103与DS18B20的温度时钟系统
在嵌入式开发领域,STM32系列MCU因其出色的性能和丰富的外设资源成为工程师的首选。最近我在一个工业环境监测项目中,需要实现一个带温度显示的实时时钟系统。经过方案对比,最终选择了STM32F103C8T6作为主控,搭配DS18B20温度传感器和内置RTC模块的方案。
这个方案最大的优势在于:
- 硬件成本极低 - STM32F103C8T6核心板仅需10元左右,DS18B20传感器约5元
- 接口简单 - DS18B20采用单总线协议,仅需1个GPIO引脚
- 精度满足要求 - DS18B20在-10℃~85℃范围内精度可达±0.5℃
- 低功耗设计 - RTC模块在备用电池供电下仅消耗约1μA电流
2. 硬件设计与关键要点
2.1 核心器件选型
主控芯片:STM32F103C8T6
- 72MHz Cortex-M3内核
- 64KB Flash,20KB RAM
- 丰富的外设资源(USART、I2C、SPI等)
- 内置RTC模块,支持备用电池供电
温度传感器:DS18B20(TO-92封装)
- 测量范围:-55℃~+125℃
- 精度:±0.5℃(-10℃~85℃范围内)
- 分辨率可配置:9~12位
- 单总线接口,节省IO资源
时钟源:32.768kHz晶振
- 为RTC提供精确时钟基准
- 典型精度±20ppm(约每月±1分钟误差)
2.2 电路连接细节
DS18B20的连接需要特别注意:
- 必须使用4.7kΩ上拉电阻,确保总线在空闲时为高电平
- 建议使用独立供电模式(非寄生供电),提高稳定性
- DQ线长度不宜超过20米,长距离传输需降低总线速度
RTC电路设计要点:
- 晶振应尽量靠近MCU的OSC32_IN和OSC32_OUT引脚
- 晶振负载电容通常选择6~12pF(具体值参考晶振规格书)
- VBAT引脚需连接备用电池(CR2032纽扣电池典型值3V)
实际布线时,我的经验是:
- 单总线走线避免与高频信号线平行
- 晶振下方不要走信号线,周围铺地铜增加稳定性
- 备用电池回路可串联100Ω电阻限流
3. 单总线协议深度解析
3.1 通信时序实现
DS18B20的1-Wire协议对时序要求极为严格,误差需控制在微秒级。在STM32上实现时,我采用了以下方法:
精确延时实现:
c复制void Delay_us(uint32_t us)
{
uint32_t count;
while(us--) {
for(count = 0; count < 8; count++); // 72MHz下约1us
}
}
关键时序操作:
- 复位脉冲:主机拉低总线480-960μs后释放
- 存在脉冲:从机应在60-240μs内拉低总线响应
- 写时隙:写"0"需持续60-120μs,写"1"拉低1-15μs后释放
- 读时隙:主机拉低1μs后释放,在15μs内采样总线状态
3.2 通信异常处理
在实际项目中,我发现单总线通信容易受干扰,因此增加了以下保护措施:
- CRC校验:对读取的9字节暂存器数据进行CRC验证
c复制uint8_t DS18B20_CheckCRC(uint8_t *data, uint8_t len)
{
uint8_t crc = 0;
for(uint8_t i=0; i<len; i++) {
crc = crc_table[crc ^ data[i]];
}
return (crc == 0);
}
- 重试机制:通信失败时自动重试3次
c复制for(uint8_t retry=0; retry<3; retry++) {
if(OneWire_Reset() == 0) break;
Delay_ms(10);
}
- 总线状态监测:定期检查总线是否短路
c复制if(GPIO_ReadInputDataBit(ONEWIRE_GPIO, ONEWIRE_PIN) == 0) {
// 总线异常处理
}
4. RTC模块配置与优化
4.1 初始化流程
STM32的RTC模块初始化需要特别注意备份域访问权限:
c复制void RTC_Init(void)
{
// 1. 使能PWR和BKP时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
// 2. 允许访问备份寄存器
PWR_BackupAccessCmd(ENABLE);
// 3. 检查是否首次配置
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) {
// 首次配置代码...
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
}
4.2 时间保持优化
为确保RTC在异常情况下仍能保持时间,我总结了以下经验:
- 备用电池电压不应低于2V,建议使用CR2032(标称3V)
- 在主电源掉电时,应尽快进入低功耗模式
- 定期检查备份域是否被意外复位(通过备份寄存器标志)
- 在主程序初始化时,先读取RTC计数器值判断是否有效
5. 温度测量性能优化
5.1 分辨率选择
DS18B20支持4种分辨率,实际使用中需要权衡:
- 9位:93.75ms转换时间,0.5℃分辨率
- 10位:187.5ms,0.25℃
- 11位:375ms,0.125℃
- 12位:750ms,0.0625℃
在环境监测场景中,我选择11位分辨率,兼顾精度和响应速度:
c复制void DS18B20_SetResolution(uint8_t res)
{
OneWire_Reset();
OneWire_WriteByte(DS18B20_CMD_SKIP_ROM);
OneWire_WriteByte(DS18B20_CMD_WRITE_SCRATCHPAD);
OneWire_WriteByte(0x00); // TH
OneWire_WriteByte(0x00); // TL
OneWire_WriteByte(res); // 配置寄存器
}
5.2 温度滤波算法
为消除测量噪声,我实现了滑动平均滤波:
c复制#define TEMP_FILTER_SIZE 5
float TempFilter_Update(float new_temp)
{
static float temp_buf[TEMP_FILTER_SIZE] = {0};
static uint8_t index = 0;
float sum = 0;
temp_buf[index] = new_temp;
index = (index + 1) % TEMP_FILTER_SIZE;
for(uint8_t i=0; i<TEMP_FILTER_SIZE; i++) {
sum += temp_buf[i];
}
return sum / TEMP_FILTER_SIZE;
}
6. 系统集成与调试
6.1 主程序架构
采用时间触发式架构,确保各任务按时执行:
c复制while(1) {
uint32_t now = RTC_GetCounter();
// 每秒更新显示
if(now != last_display) {
Update_Display();
last_display = now;
}
// 每5秒读取温度
if((now - last_temp) >= 5) {
Read_Temperature();
last_temp = now;
}
// 其他任务...
}
6.2 调试技巧
- 逻辑分析仪使用:捕获单总线波形,验证时序参数
- 串口调试输出:关键变量实时监控
- 备用电池测试:断开主电源验证RTC保持
- 温度校准:使用标准温度源对比修正
7. 项目优化方向
基于当前实现,还可以进一步优化:
- 低功耗设计:在电池供电时进入STOP模式,仅RTC运行
- 多点测温:利用ROM搜索功能支持多个DS18B20
- 数据记录:添加SPI Flash存储历史温度数据
- 无线传输:集成蓝牙或LoRa模块实现远程监控
这个项目完整展示了从硬件设计到软件实现的整个过程,特别是在单总线协议和RTC模块的应用上积累了不少实战经验。对于刚接触STM32的开发者来说,这是一个非常好的综合练习项目。
