从零开始:用STM32打造一个会“思考”的电子时钟
基于STM32的 电子时钟设计 源代码(无实物)基于STM32F103战舰开发板,利用LCD屏来实时显示温湿度传感器DHT11所测得的数据和时间日期的信息;需要实现的功能是:整点报时功能;倒计时功能;当前时间日期可以设置,且掉电后保持可设置的闹铃时间到,蜂鸣器发出嘀嘀滴(长)声短按KEY_UP循环切换温度阈值/时间/闹钟/倒计时/主界面功能长按KEY_UP保存当前参数值按键0 循环切换设置哪一个值
基于STM32的 电子时钟设计 源代码(无实物) 基于STM32F103战舰开发板,利用LCD屏来实时显示温湿度传感器DHT11所测得的数据和时间日期的信息;需要实现的功能是:整点报时功能;倒计时功能;当前时间日期可以设置,且掉电后保持 可设置的闹铃时间到,蜂鸣器发出嘀嘀滴(长)声 短按KEY_UP 循环切换温度阈值/时间/闹钟/倒计时/主界面功能 长按KEY_UP 保存当前参数值 按键0 循环切换设置哪一个值 按键1 上调数值(主界面时,在开启倒计时功能,启动倒计时) 按键2 下调数值(主界面时,在开启倒计时功能,暂停倒计时) 通过按键控制的功能:通过按键可以设置上调和下调温度阈值,可以设定一个温度标准值,当所测温度超过标准值时,设置蜂鸣器进行报警(短声嘀嘀)和相灯的快频率闪烁,(超温报警) 注意: 1、设定时间,日期,星期时,按下设置的键,相应的参数会闪动,提示正在修改数据 2、设定完以后,再按一下 KEY_UP键,就退出设定的界面,回到正常显示状态 3、用DHT11显示温湿度,精确到小数点后一位 (如36.3) 4、月日分秒,温湿度数据动态刷新显示 RTC实时时钟 看门狗 定时器中断 DHT11
最近在捣鼓一个基于STM32的电子时钟项目,虽然手头没有实物,但代码已经调得差不多了。这个时钟功能挺全的:整点报时、倒计时、温湿度监测、闹钟、超温报警,该有的都有了。用的是STM32F103战舰开发板,配合LCD屏和DHT11传感器,下面分享一些关键代码和实现思路。
一、整体架构设计
整个系统围绕几个核心模块展开:RTC实时时钟、DHT11温湿度采集、LCD显示驱动、按键处理和蜂鸣器控制。采用状态机的方式管理不同界面(主界面、时间设置、闹钟设置等),通过定时器中断确保时间精度。
// 系统状态定义
typedef enum {
SYS_MAIN, // 主界面
SYS_TIME_SET, // 时间设置
SYS_ALARM_SET, // 闹钟设置
SYS_TEMP_SET, // 温度阈值设置
SYS_COUNTDOWN // 倒计时界面
} SystemState;
SystemState g_sys_state = SYS_MAIN;
二、RTC时钟与掉电保持
STM32的RTC模块自带电池备份域,掉电后依靠纽扣电池保持运行。初始化时先检查是否是第一次上电,如果是就需要设置初始时间。
void RTC_Init(void) {
// 检查是否为首次配置
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
// 初始化RTC时钟源
RCC_LSEConfig(RCC_LSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
// 设置时间
RTC_SetTime(2024, 1, 1, 12, 0, 0);
BKP_WriteBackupRegister(BKP_DR1, 0x5050);
}
}
// 获取当前时间
RTC_Time get_current_time(void) {
RTC_Time time;
time.year = RTC_GetCounter() / 31536000 + 1970; // 简化计算
// ... 其他时间字段计算
return time;
}
这里有个小技巧:通过备份寄存器BKP_DR1写入特定值(0x5050)作为是否首次配置的标志。如果不是首次,直接从RTC计数器读取时间。
三、DHT11温湿度采集
DHT11是单总线数字传感器,时序要求比较严格。采集时需要先发送开始信号,然后等待传感器响应。
#define DHT11_OUT_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_12)
#define DHT11_OUT_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_12)
#define DHT11_IN() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)
uint8_t DHT11_Read_Data(float *temp, float *humi) {
uint8_t buf[5] = {0};
// 主机拉低18ms
DHT11_OUT_LOW();
delay_ms(18);
DHT11_OUT_HIGH();
delay_us(30);
// 等待传感器响应
if (DHT11_IN() == RESET) {
while (DHT11_IN() == RESET); // 等待拉高
while (DHT11_IN() == SET); // 等待拉低
// 读取40位数据
for (int i = 0; i < 40; i++) {
while (DHT11_IN() == RESET);
delay_us(40);
buf[i/8] <<= 1;
if (DHT11_IN() == SET) {
buf[i/8] |= 1;
while (DHT11_IN() == SET);
}
}
// 校验数据
if (buf[4] == (buf[0] + buf[1] + buf[2] + buf[3])) {
*humi = buf[0] + buf[1] * 0.1;
*temp = buf[2] + buf[3] * 0.1;
return 1;
}
}
return 0;
}
注意点:DHT11的时序很关键,微秒级延时必须准确。读取温度值后需要转换成带一位小数的浮点数格式显示。
四、按键处理与状态切换
通过KEY_UP键短按循环切换功能,长按保存设置。这里用状态机实现界面切换,按键检测放在定时器中断里做防抖处理。
void KEY_Handler(void) {
static uint32_t key_press_time = 0;
if (KEY_UP == 0) { // 按键按下
if (key_press_time == 0) {
key_press_time = GetTickCount();
} else if (GetTickCount() - key_press_time > 1000) { // 长按1秒
// 保存当前设置到Flash
Save_Settings();
key_press_time = 0;
}
} else { // 按键释放
if (key_press_time > 0 && GetTickCount() - key_press_time < 1000) {
// 短按:切换系统状态
g_sys_state = (g_sys_state + 1) % 5;
Update_Display_Mode();
}
key_press_time = 0;
}
}
五、整点报时与闹钟功能
整点报时比较简单,每分钟检查一次是否到整点。闹钟功能需要比较当前时间与设定的闹钟时间。
void Check_Alarm(void) {
RTC_Time now = get_current_time();
// 整点报时
if (now.minute == 0 && now.second == 0) {
BEEP_Beep(500, 3); // 响3次,每次500ms
}
// 闹钟检查
if (g_alarm.enable &&
now.hour == g_alarm.hour &&
now.minute == g_alarm.minute &&
now.second < 3) { // 响3秒
BEEP_Beep(1000, 1); // 长响1秒
}
// 超温报警
if (g_current_temp > g_temp_threshold) {
BEEP_Beep(200, 5); // 短促报警
LED_Flash(100); // LED快速闪烁
}
}
蜂鸣器控制函数BEEP_Beep()通过PWM产生不同频率的声音,实现嘀嘀滴的长短音区别。
六、倒计时功能的实现
倒计时功能在主界面下通过按键1启动,按键2暂停。使用一个单独的定时器进行秒级计时。
typedef struct {
uint32_t total_seconds;
uint32_t remaining_seconds;
uint8_t is_running;
} Countdown;
Countdown g_countdown = {0};
void Countdown_Start(void) {
if (g_countdown.total_seconds > 0) {
g_countdown.remaining_seconds = g_countdown.total_seconds;
g_countdown.is_running = 1;
}
}
// 在1秒定时器中断中调用
void Countdown_Update(void) {
if (g_countdown.is_running && g_countdown.remaining_seconds > 0) {
g_countdown.remaining_seconds--;
if (g_countdown.remaining_seconds == 0) {
// 倒计时结束,触发提示
BEEP_Beep(1000, 3);
g_countdown.is_running = 0;
}
}
}
七、看门狗与系统稳定性
为了防止程序跑飞,加入了独立看门狗(IWDG)。在main循环中定期喂狗。
void IWDG_Init(uint16_t reload) {
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_32); // 32分频
IWDG_SetReload(reload); // 设置重载值
IWDG_ReloadCounter(); // 重载计数器
IWDG_Enable(); // 使能看门狗
}
// 在主循环中定期调用
void Feed_Dog(void) {
IWDG_ReloadCounter();
}
八、显示效果的优化
为了让设置时参数闪动更明显,采用交替显示的方式:正常显示0.5秒,空白0.5秒。
void Display_Blink_Value(uint16_t x, uint16_t y, uint16_t value, uint8_t blink) {
static uint8_t blink_state = 0;
static uint32_t last_time = 0;
if (GetTickCount() - last_time > 500) {
blink_state = !blink_state;
last_time = GetTickCount();
}
if (!blink || blink_state) {
LCD_ShowNum(x, y, value, 2, 16);
} else {
LCD_ShowString(x, y, " ", 16); // 显示空格实现闪烁效果
}
}
九、一些踩过的坑
- DHT11时序问题:最开始直接用delay函数,结果经常读取失败。后来改用在定时器中断中计数的方式,稳定性大大提升。
- RTC初始化:忘记使能备份域访问权限,导致时间无法保存。调试了半天才发现少了
PWR_BackupAccessCmd(ENABLE)这一行。
- 按键长按检测:最初在主循环中检测,响应不灵敏。放到定时器中断中每10ms检测一次后,效果好了很多。
- 显示刷新:所有数据同时刷新会导致LCD闪烁。后来改为分时刷新:时间每秒刷新,温湿度每5秒刷新,界面切换时立即刷新。
这个项目虽然只是软件模拟,但涵盖了STM32开发的很多基础知识点:GPIO控制、定时器、中断、RTC、Flash读写等。最重要的是状态机的运用,让复杂的多功能切换变得清晰可控。
基于STM32的 电子时钟设计 源代码(无实物) 基于STM32F103战舰开发板,利用LCD屏来实时显示温湿度传感器DHT11所测得的数据和时间日期的信息;需要实现的功能是:整点报时功能;倒计时功能;当前时间日期可以设置,且掉电后保持 可设置的闹铃时间到,蜂鸣器发出嘀嘀滴(长)声 短按KEY_UP 循环切换温度阈值/时间/闹钟/倒计时/主界面功能 长按KEY_UP 保存当前参数值 按键0 循环切换设置哪一个值 按键1 上调数值(主界面时,在开启倒计时功能,启动倒计时) 按键2 下调数值(主界面时,在开启倒计时功能,暂停倒计时) 通过按键控制的功能:通过按键可以设置上调和下调温度阈值,可以设定一个温度标准值,当所测温度超过标准值时,设置蜂鸣器进行报警(短声嘀嘀)和相灯的快频率闪烁,(超温报警) 注意: 1、设定时间,日期,星期时,按下设置的键,相应的参数会闪动,提示正在修改数据 2、设定完以后,再按一下 KEY_UP键,就退出设定的界面,回到正常显示状态 3、用DHT11显示温湿度,精确到小数点后一位 (如36.3) 4、月日分秒,温湿度数据动态刷新显示 RTC实时时钟 看门狗 定时器中断 DHT11
代码仓库地址:[此处省略,实际项目中需填写]
下次准备加上网络对时和天气显示功能,让这个电子时钟更加实用。不过那是另一个故事了。

更多推荐



所有评论(0)