基于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);  // 显示空格实现闪烁效果
    }
}

九、一些踩过的坑

  1. DHT11时序问题:最开始直接用delay函数,结果经常读取失败。后来改用在定时器中断中计数的方式,稳定性大大提升。
  1. RTC初始化:忘记使能备份域访问权限,导致时间无法保存。调试了半天才发现少了PWR_BackupAccessCmd(ENABLE)这一行。
  1. 按键长按检测:最初在主循环中检测,响应不灵敏。放到定时器中断中每10ms检测一次后,效果好了很多。
  1. 显示刷新:所有数据同时刷新会导致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

代码仓库地址:[此处省略,实际项目中需填写]

下次准备加上网络对时和天气显示功能,让这个电子时钟更加实用。不过那是另一个故事了。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐