STM32F103C8T6实战:用TM1638模块DIY一个温控器(按键+数码管显示)

张开发
2026/4/19 12:43:38 15 分钟阅读

分享文章

STM32F103C8T6实战:用TM1638模块DIY一个温控器(按键+数码管显示)
STM32F103C8T6实战用TM1638模块DIY一个温控器按键数码管显示在电子DIY的世界里将单片机与各种模块组合实现实用功能是最令人兴奋的事情之一。今天我们要用STM32F103C8T6这块性价比极高的单片机搭配TM1638显示驱动模块打造一个完整的温控系统。这个项目不仅能让你的STM32技能得到实战锻炼还能收获一个真正可用的温度控制器可以用来控制电热毯、恒温箱或者任何需要温度控制的场景。1. 项目整体设计1.1 硬件架构规划我们的温控系统由三个核心部分组成主控单元STM32F103C8T6最小系统板人机交互模块TM1638驱动的8位数码管8个LED8个按键温度传感模块DS18B20数字温度传感器硬件连接示意图如下模块STM32引脚说明TM1638 CLKPB6时钟信号TM1638 DIOPB7数据输入/输出TM1638 STBPB8片选信号DS18B20 DQPA0单总线数据线提示TM1638模块只需要3个IO口就能驱动8位数码管、8个LED和8个按键这种高效的接口设计非常适合资源有限的单片机项目。1.2 软件功能设计系统软件需要实现以下核心功能温度采集通过DS18B20获取环境温度温度显示在TM1638的数码管上显示当前温度和设定温度按键控制通过TM1638的按键调整设定温度控制输出根据温度差控制继电器或其他执行机构// 系统状态机定义 typedef enum { NORMAL_MODE, // 正常显示模式 SETTING_MODE // 温度设定模式 } SystemMode_t;2. TM1638驱动开发2.1 显示功能实现TM1638的显示驱动需要处理数码管和LED两部分。数码管显示采用动态扫描方式每个数码管由7段组成加上小数点共8段。关键显示函数实现void TM1638_DisplayDigit(uint8_t pos, uint8_t digit, bool dot) { uint8_t seg_data digitToSegment[digit] | (dot ? 0x80 : 0x00); TM1638_WriteData(0xC0 (pos 1), seg_data); } void TM1638_DisplayTemperature(float temp, uint8_t start_pos) { uint8_t integer (uint8_t)temp; uint8_t decimal (uint8_t)((temp - integer) * 10); TM1638_DisplayDigit(start_pos, integer / 10, false); // 十位 TM1638_DisplayDigit(start_pos 1, integer % 10, true); // 个位小数点 TM1638_DisplayDigit(start_pos 2, decimal, false); // 小数位 }2.2 按键扫描优化原始文章的按键扫描采用了简单的20ms轮询方式我们可以改进为中断消抖的混合模式// 按键状态机 typedef enum { KEY_IDLE, KEY_PRESS_DETECTED, KEY_DEBOUNCE, KEY_PRESS_CONFIRMED, KEY_RELEASE_DETECTED } KeyState_t; void EXTI9_5_IRQHandler(void) { if(EXTI_GetITStatus(EXTI_Line8) ! RESET) { // TM1638的STB引脚连接的外部中断 keyState KEY_PRESS_DETECTED; EXTI_ClearITPendingBit(EXTI_Line8); } } void Key_Process(void) { static uint32_t debounceTimer 0; switch(keyState) { case KEY_PRESS_DETECTED: debounceTimer HAL_GetTick(); keyState KEY_DEBOUNCE; break; case KEY_DEBOUNCE: if(HAL_GetTick() - debounceTimer 20) { // 20ms消抖 currentKey TM1638_ReadKey(); keyState KEY_PRESS_CONFIRMED; } break; // 其他状态处理... } }3. 温度采集与处理3.1 DS18B20驱动实现DS18B20是常用的数字温度传感器采用单总线协议。我们需要实现精确的时序控制#define DS18B20_PORT GPIOA #define DS18B20_PIN GPIO_Pin_0 uint8_t DS18B20_Start(void) { uint8_t response 0; // 复位脉冲 GPIO_Init(DS18B20_PORT, DS18B20_PIN, GPIO_Mode_Out_OD); GPIO_WriteLow(DS18B20_PORT, DS18B20_PIN); Delay_us(480); // 释放总线 GPIO_WriteHigh(DS18B20_PORT, DS18B20_PIN); Delay_us(60); // 检测应答 GPIO_Init(DS18B20_PORT, DS18B20_PIN, GPIO_Mode_IN_FLOATING); response GPIO_ReadInputPin(DS18B20_PORT, DS18B20_PIN); Delay_us(420); return response; // 0存在1不存在 } float DS18B20_ReadTemp(void) { uint8_t tempL, tempH; int16_t temp; DS18B20_Start(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0x44); // 启动温度转换 while(!DS18B20_ReadBit()); // 等待转换完成 DS18B20_Start(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0xBE); // 读取暂存器 tempL DS18B20_ReadByte(); tempH DS18B20_ReadByte(); temp (tempH 8) | tempL; return temp / 16.0f; // 转换为摄氏度 }3.2 温度滤波算法原始数据往往会有波动我们可以实现一个简单的滑动平均滤波#define TEMP_FILTER_SIZE 5 float TemperatureFilter(float newTemp) { static float tempBuffer[TEMP_FILTER_SIZE] {0}; static uint8_t index 0; float sum 0; tempBuffer[index] newTemp; index (index 1) % TEMP_FILTER_SIZE; for(uint8_t i 0; i TEMP_FILTER_SIZE; i) { sum tempBuffer[i]; } return sum / TEMP_FILTER_SIZE; }4. 控制系统实现4.1 PID控制算法为了实现精确的温度控制我们可以引入PID算法typedef struct { float Kp, Ki, Kd; // PID系数 float integral; // 积分项 float prevError; // 上一次误差 float setpoint; // 设定值 float output; // 输出值 float outputLimit; // 输出限幅 } PIDController; void PID_Init(PIDController* pid, float Kp, float Ki, float Kd, float limit) { pid-Kp Kp; pid-Ki Ki; pid-Kd Kd; pid-integral 0; pid-prevError 0; pid-setpoint 0; pid-output 0; pid-outputLimit limit; } float PID_Update(PIDController* pid, float measurement, float dt) { float error pid-setpoint - measurement; // 比例项 float proportional pid-Kp * error; // 积分项带抗饱和 pid-integral error * dt; float integral pid-Ki * pid-integral; // 微分项 float derivative pid-Kd * (error - pid-prevError) / dt; pid-prevError error; // 计算输出 pid-output proportional integral derivative; // 输出限幅 if(pid-output pid-outputLimit) pid-output pid-outputLimit; else if(pid-output 0) pid-output 0; return pid-output; }4.2 控制逻辑实现将PID输出转换为实际的加热控制信号void Control_Update(void) { static uint32_t lastTime 0; uint32_t currentTime HAL_GetTick(); float dt (currentTime - lastTime) / 1000.0f; // 转换为秒 if(dt 1.0f) { // 每秒更新一次控制 float temp DS18B20_ReadTemp(); float filteredTemp TemperatureFilter(temp); float output PID_Update(heaterPID, filteredTemp, dt); // PWM输出控制加热器 uint16_t pwmValue (uint16_t)(output * 100); // 假设输出范围0-100 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, pwmValue); lastTime currentTime; } }5. 系统集成与优化5.1 主程序流程设计将各个模块整合到一个协调的工作流程中int main(void) { // 硬件初始化 HAL_Init(); SystemClock_Config(); TM1638_Init(); DS18B20_Init(); PID_Init(heaterPID, 5.0, 0.1, 1.0, 100.0); // PID参数 // 初始温度设定 heaterPID.setpoint 25.0f; // 默认25°C while (1) { // 按键处理 Key_Process(); // 温度采集与显示 float currentTemp DS18B20_ReadTemp(); TM1638_DisplayTemperature(currentTemp, 4); // 右侧显示实际温度 TM1638_DisplayTemperature(heaterPID.setpoint, 0); // 左侧显示设定温度 // 温度控制 Control_Update(); // 系统状态指示灯 static uint32_t blinkTimer 0; if(HAL_GetTick() - blinkTimer 500) { TM1638_SetLED(0, (heaterPID.output 50)); // 加热时LED亮 blinkTimer HAL_GetTick(); } HAL_Delay(50); // 主循环延时 } }5.2 用户界面优化为了提升用户体验我们可以增加以下功能设定温度闪烁提示在设置模式下让设定温度闪烁温度超限报警当温度超过安全范围时LED闪烁报警长按加速调节按住或-键时温度调整速度逐渐加快// 在按键处理函数中添加长按检测 void Key_Process(void) { static uint32_t pressTime 0; static uint8_t speedLevel 0; if(keyState KEY_PRESS_CONFIRMED) { pressTime HAL_GetTick(); speedLevel 0; } if(keyState KEY_PRESS_CONFIRMED || (keyState KEY_PRESSED HAL_GetTick() - pressTime 1000)) { float step 0.1f * (1 speedLevel); // 基础步长0.1长按时加速 switch(currentKey) { case KEY_UP: heaterPID.setpoint step; if(heaterPID.setpoint 80.0) heaterPID.setpoint 80.0; break; case KEY_DOWN: heaterPID.setpoint - step; if(heaterPID.setpoint 30.0) heaterPID.setpoint 30.0; break; } if(HAL_GetTick() - pressTime 1000 speedLevel * 500) { speedLevel (speedLevel 5) ? speedLevel 1 : 5; } } }6. 项目扩展与进阶6.1 添加温度记录功能利用STM32的内部Flash或外接EEPROM存储温度记录#define TEMP_LOG_SIZE 24 // 存储24小时数据 typedef struct { float temps[TEMP_LOG_SIZE]; uint8_t index; } TempLog; void TempLog_Add(TempLog* log, float temp) { log-temps[log-index] temp; log-index (log-index 1) % TEMP_LOG_SIZE; } float TempLog_GetAverage(TempLog* log) { float sum 0; for(uint8_t i 0; i TEMP_LOG_SIZE; i) { sum log-temps[i]; } return sum / TEMP_LOG_SIZE; }6.2 无线通信扩展通过HC-05蓝牙模块或ESP8266 WiFi模块实现手机监控void Bluetooth_SendTemp(float temp, float setpoint) { char buffer[32]; sprintf(buffer, Temp:%.1fC,Set:%.1fC\r\n, temp, setpoint); HAL_UART_Transmit(huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY); }6.3 多级菜单系统扩展按键功能实现更复杂的菜单交互typedef enum { MAIN_SCREEN, TEMP_SETTING, PID_TUNING, SYSTEM_INFO } MenuState_t; void Menu_Process(uint8_t key) { static MenuState_t menuState MAIN_SCREEN; switch(menuState) { case MAIN_SCREEN: if(key KEY_SET) menuState TEMP_SETTING; break; case TEMP_SETTING: if(key KEY_SET) menuState MAIN_SCREEN; // 温度调节处理... break; // 其他菜单状态... } }在实际项目中我发现TM1638模块的显示亮度在白天和夜晚需要不同设置可以通过长按某个按键来循环调整亮度等级。另外为防误操作温度设定最好增加确认步骤避免意外改变设定值。

更多文章