从零打造桌面温湿度监测站:STM32F103与TM1616的完美组合
你是否厌倦了市面上千篇一律的电子温湿度计?想亲手打造一个既实用又能展示技术实力的桌面小工具?今天我们就用STM32F103C8T6这块经典单片机,搭配TM1616数码管驱动芯片和DHT11传感器,制作一个完全自定义的温湿度显示器。不同于简单的驱动测试,我们将从实际应用角度出发,解决显示闪烁、功耗优化等真实场景中的问题。
1. 硬件选型与搭建
1.1 核心组件介绍
这个项目的硬件架构非常简单,只需要三个主要部件:
- STM32F103C8T6最小系统板:作为主控,价格低廉且性能足够
- TM1616数码管驱动模块:支持4位7段数码管显示,内置亮度调节
- DHT11温湿度传感器:单总线接口,测量范围20-90%RH和0-50℃
提示:选择TM1616而非TM1637的主要原因是前者支持8级亮度调节,更适合需要长时间运行的桌面设备。
1.2 硬件连接示意图
将各模块按照以下方式连接:
| STM32引脚 | 连接目标 | 备注 |
|---|---|---|
| PB6 | DHT11数据线 | 需接4.7K上拉电阻 |
| PB7 | TM1616 STB | 片选信号 |
| PB8 | TM1616 CLK | 时钟信号 |
| PB9 | TM1616 DIO | 数据输入/输出 |
| 3.3V | 各模块VCC | 供电 |
| GND | 各模块GND | 共地 |
2. TM1616驱动深度优化
2.1 基础驱动封装
原始驱动代码虽然能用,但直接放在主循环中会导致显示闪烁。我们需要将其封装成更健壮的显示库:
// tm1616.h #ifndef __TM1616_H #define __TM1616_H #include "stm32f10x.h" #define TM1616_STB_PIN GPIO_Pin_7 #define TM1616_CLK_PIN GPIO_Pin_8 #define TM1616_DIO_PIN GPIO_Pin_9 #define TM1616_PORT GPIOB typedef enum { LED_OFF = 0x00, LED_ON = 0x08 } TM1616_PowerState; typedef enum { BRIGHTNESS_1 = 0, BRIGHTNESS_2, // ... 其他亮度等级 BRIGHTNESS_8 } TM1616_Brightness; void TM1616_Init(void); void TM1616_DisplayDigits(uint8_t digits[4]); void TM1616_SetBrightness(TM1616_Brightness level); void TM1616_SetPower(TM1616_PowerState state); #endif2.2 显示刷新策略优化
常见的显示闪烁问题通常源于不合理的刷新方式。我们采用双缓冲技术来解决:
// tm1616.c static uint8_t displayBuffer[4] = {0}; static uint8_t backBuffer[4] = {0}; void TM1616_UpdateDisplay(void) { // 原子操作切换缓冲区 memcpy(displayBuffer, backBuffer, 4); TM1616_DisplayDigits(displayBuffer); } void TM1616_SetDigit(uint8_t position, uint8_t value) { if(position < 4) { backBuffer[position] = value; } }3. 温湿度数据采集与处理
3.1 DHT11驱动实现
DHT11的通信时序要求严格,我们需要精确的微秒级延时:
// dht11.c #define DHT11_PIN GPIO_Pin_6 #define DHT11_PORT GPIOB uint8_t DHT11_ReadData(float *temperature, float *humidity) { uint8_t data[5] = {0}; // 主机发起开始信号 GPIO_ResetBits(DHT11_PORT, DHT11_PIN); Delay_us(18000); GPIO_SetBits(DHT11_PORT, DHT11_PIN); Delay_us(40); // 等待传感器响应 if(!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { uint32_t timeout = 10000; while(!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) && timeout--); timeout = 10000; while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) && timeout--); // 读取40位数据 for(uint8_t i=0; i<5; i++) { for(uint8_t j=0; j<8; j++) { while(!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)); Delay_us(30); data[i] <<= 1; if(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)) { data[i] |= 1; while(GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)); } } } // 校验数据 if(data[4] == (data[0]+data[1]+data[2]+data[3])) { *humidity = data[0] + data[1]*0.1; *temperature = data[2] + data[3]*0.1; return 1; } } return 0; }3.2 数据滤波算法
原始传感器数据常有波动,我们需要添加简单的滑动平均滤波:
#define FILTER_WINDOW_SIZE 5 typedef struct { float tempBuffer[FILTER_WINDOW_SIZE]; float humiBuffer[FILTER_WINDOW_SIZE]; uint8_t index; } SensorFilter; void Filter_Init(SensorFilter *filter) { memset(filter, 0, sizeof(SensorFilter)); } void Filter_AddData(SensorFilter *filter, float temp, float humi) { filter->tempBuffer[filter->index] = temp; filter->humiBuffer[filter->index] = humi; filter->index = (filter->index + 1) % FILTER_WINDOW_SIZE; } float Filter_GetAverage(float *buffer) { float sum = 0; for(uint8_t i=0; i<FILTER_WINDOW_SIZE; i++) { sum += buffer[i]; } return sum / FILTER_WINDOW_SIZE; }4. 系统整合与功耗优化
4.1 主程序架构设计
采用状态机模式管理显示内容切换和传感器读取:
typedef enum { SHOW_TEMP, SHOW_HUMI, SHOW_ALT } DisplayMode; void System_Run(void) { static DisplayMode mode = SHOW_TEMP; static uint32_t lastReadTime = 0; static SensorFilter filter; // 每2秒读取一次传感器 if(HAL_GetTick() - lastReadTime > 2000) { float temp, humi; if(DHT11_ReadData(&temp, &humi)) { Filter_AddData(&filter, temp, humi); } lastReadTime = HAL_GetTick(); } // 每5秒切换显示模式 switch(mode) { case SHOW_TEMP: DisplayTemperature(Filter_GetAverage(filter.tempBuffer)); if(HAL_GetTick() % 10000 < 2000) mode = SHOW_HUMI; break; case SHOW_HUMI: DisplayHumidity(Filter_GetAverage(filter.humiBuffer)); if(HAL_GetTick() % 10000 >= 2000) mode = SHOW_TEMP; break; } // 根据环境光线自动调节亮度 AutoAdjustBrightness(); }4.2 动态亮度调节技术
利用TM1616的8级亮度控制功能,实现自动亮度调节:
void AutoAdjustBrightness(void) { uint16_t lightLevel = GetLightSensorLevel(); // 假设有光敏电阻 TM1616_Brightness brightness; if(lightLevel > 800) brightness = BRIGHTNESS_8; else if(lightLevel > 600) brightness = BRIGHTNESS_7; // ... 其他条件 else brightness = BRIGHTNESS_2; TM1616_SetBrightness(brightness); }5. 进阶功能扩展
5.1 温度报警功能
当温度超过设定阈值时,让数码管闪烁提示:
void CheckTemperatureAlert(float temp) { static uint8_t alertActive = 0; static uint32_t lastBlinkTime = 0; if(temp > TEMP_ALERT_THRESHOLD) { alertActive = 1; if(HAL_GetTick() - lastBlinkTime > 500) { static uint8_t visible = 0; if(visible) { DisplayTemperature(temp); visible = 0; } else { TM1616_ClearDisplay(); visible = 1; } lastBlinkTime = HAL_GetTick(); } } else if(alertActive) { alertActive = 0; DisplayTemperature(temp); } }5.2 低功耗模式实现
利用STM32的睡眠模式进一步降低功耗:
void EnterLowPowerMode(void) { // 配置唤醒源 EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line6; // DHT11引脚 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); // 进入停止模式 TM1616_SetPower(LED_OFF); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后重新初始化时钟 SystemInit(); TM1616_Init(); }6. 项目外壳设计与成品展示
虽然代码是核心,但一个好的外壳能让项目更加完整。这里有几个DIY建议:
- 3D打印外壳:使用PLA材料打印一个简约的倾斜支架
- 亚克力激光切割:制作透明保护罩,同时展示内部结构
- 木质底座:搭配胡桃木底座提升质感
注意:设计外壳时要考虑散热问题,避免将单片机紧贴密闭空间。