【STM32】RTC的使用和实时时钟项目

目录

1 什么是RTC?

实时时钟是一个独立的定时器。 RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历

的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后, RTC的设置

和时间维持不变。

复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下

操作可以使能对备份寄存器和RTC的访问:

  • 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
  • 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。

32位的可编程计数器,可对应Unix时间戳的秒计数器。

c 复制代码
Unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。

20位的可编程预分频器,可适配不同频率的输入时钟。

可选择三种RTC时钟源:

  • HSE时钟除以128(通常为8MHz/128)
  • LSE振荡器时钟(通常为32.768KHz)
  • LSI振荡器时钟(40KHz)

2 RTC框图

3 寄存器及库函数

4 RTC驱动步骤

注意事项:

  • 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
  • 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器

小实验1:读写RTC时间实验

完整代码

main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "rtc.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */      
    //LED初始化
    led_init();
    //串口1初始化
    uart1_init(115200);
    //RTC初始化
    rtc_init();
    
    printf("打印测试:hello world\r\n");
    
    if(rtc_read_bkr(1) != 0xA5A5){
        rtc_write_bkr(1, 0xA5A5);
        printf("读BKP的值为:%X\r\n",rtc_read_bkr(1));
        //定义时间日期结构体
        struct tm time_data;
        time_data.tm_year = 2025;
        time_data.tm_mon = 12;
        time_data.tm_mday = 7;
        time_data.tm_hour = 12;
        time_data.tm_min = 5;
        time_data.tm_sec = 30;
        
        //写入时间日期
        rtc_set_time(time_data);
    }
    
    
    
    
    while(1)
    {
        rtc_get_time();
        delay_ms(1000);
    }
}

rtc.c

c 复制代码
#include "rtc.h"
#include "stdio.h"

RTC_HandleTypeDef rtc_handle = {0};
//RTC初始化
void rtc_init(void)
{
    //使能PWR时钟
    __HAL_RCC_PWR_CLK_ENABLE();
    //使能BKP时钟
    __HAL_RCC_BKP_CLK_ENABLE();
    //使能BKP访问
    HAL_PWR_EnableBkUpAccess();
    
    //参数配置
    rtc_handle.Instance = RTC;                          //选择RTC
    rtc_handle.Init.AsynchPrediv = 32767;               //分频系数
    rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;     //不用侵入检测
    //初始化
    HAL_RTC_Init(&rtc_handle);
}

//硬件配置
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
    RCC_OscInitTypeDef osc_init_struct = {0};
    RCC_PeriphCLKInitTypeDef periphclk_init_struct = {0};
    
    //RTC时钟使能
    __HAL_RCC_RTC_ENABLE();
    //振荡器配置
    osc_init_struct.OscillatorType = RCC_OSCILLATORTYPE_LSE;        //选择低速外部时钟振荡器
    osc_init_struct.LSEState = RCC_LSE_ON;                          //打开低速外部时钟
    osc_init_struct.PLL.PLLState = RCC_PLL_NONE;                    //不适用锁相环
    HAL_RCC_OscConfig(&osc_init_struct);
    //外设时钟配置
    periphclk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_RTC; //选择外设时钟为RTC时钟
    periphclk_init_struct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; //选择RTC时钟来源为低速外部时钟
    HAL_RCCEx_GetPeriphCLKConfig(&periphclk_init_struct);
}

//读BKR
uint16_t rtc_read_bkr(uint8_t bkrx)
{
    uint32_t data = 0;
    
    data = HAL_RTCEx_BKUPRead(&rtc_handle, bkrx);
    return (uint16_t)data;
}

//写BKR
void rtc_write_bkr(uint8_t bkrx, uint16_t data)
{
    HAL_RTCEx_BKUPWrite(&rtc_handle, bkrx, data);
}

//获取RTC时间
void rtc_get_time(void)
{
    //RTC时间日期结构体
    RTC_TimeTypeDef rtc_time = {0};
    RTC_DateTypeDef rtc_date = {0};
    
    //获取时间日期
    HAL_RTC_GetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
    //打印时间日期
    printf("rtc time: %d-%02d-%02d %02d:%02d:%02d\r\n", rtc_date.Year + 2000, rtc_date.Month, rtc_date.Date,
        rtc_time.Hours,rtc_time.Minutes, rtc_time.Seconds);
}

//设置RTC时间
void rtc_set_time(struct tm time_data)
{
    //RTC时间日期结构体
    RTC_TimeTypeDef rtc_time = {0};
    RTC_DateTypeDef rtc_date = {0};
    
    //设置RTC时间日期
    rtc_time.Hours = time_data.tm_hour;
    rtc_time.Minutes = time_data.tm_min;
    rtc_time.Seconds = time_data.tm_sec;
    rtc_date.Year = time_data.tm_year - 2000;
    rtc_date.Month = time_data.tm_mon;
    rtc_date.Date = time_data.tm_mday;
    
    //设置RTC时间日期
    HAL_RTC_SetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
    HAL_RTC_SetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
    
    //等待RTC写入完成
    while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle, RTC_FLAG_RTOFF));
}

rtc.h

c 复制代码
#ifndef __RTC_H__
#define __RTC_H__

#include "sys.h"
#include "time.h"

//RTC初始化
void rtc_init(void);
//读BKR
uint16_t rtc_read_bkr(uint8_t bkrx);
//写BKR
void rtc_write_bkr(uint8_t bkrx, uint16_t data);
//获取RTC时间
void rtc_get_time(void);
//设置RTC时间
void rtc_set_time(struct tm time_data);

#endif

小实验2:RTC闹钟实验

完整代码

main.c

c 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "rtc.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */      
    //LED初始化
    led_init();
    //串口1初始化
    uart1_init(115200);
    //RTC初始化
    rtc_init();
    
    printf("打印测试:hello world\r\n");
    
    if(rtc_read_bkr(1) != 0xA5A5){
        rtc_write_bkr(1, 0xA5A5);
        printf("读BKP的值为:%X\r\n",rtc_read_bkr(1));
        
        //定义时间日期结构体
        struct tm time_data,alarm_data;
        time_data.tm_year = 2025;
        time_data.tm_mon = 12;
        time_data.tm_mday = 7;
        time_data.tm_hour = 12;
        time_data.tm_min = 5;
        time_data.tm_sec = 30;
        //写入时间日期
        rtc_set_time(time_data);
        
        //设置闹钟
        alarm_data.tm_hour = 12;
        alarm_data.tm_min = 5;
        alarm_data.tm_sec = 40;
        //写入闹钟时间
        rtc_set_alarm(alarm_data);
    }
    
    while(1)
    {
        rtc_get_time();
        delay_ms(1000);
    }
}

rtc.c

c 复制代码
#include "rtc.h"
#include "stdio.h"

RTC_HandleTypeDef rtc_handle = {0};
//RTC初始化
void rtc_init(void)
{
    //使能PWR时钟
    __HAL_RCC_PWR_CLK_ENABLE();
    //使能BKP时钟
    __HAL_RCC_BKP_CLK_ENABLE();
    //使能BKP访问
    HAL_PWR_EnableBkUpAccess();
    
    //参数配置
    rtc_handle.Instance = RTC;                          //选择RTC
    rtc_handle.Init.AsynchPrediv = 32767;               //分频系数
    rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;     //不用侵入检测
    //初始化
    HAL_RTC_Init(&rtc_handle);
}

//硬件配置
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
    //RTC时钟使能
    __HAL_RCC_RTC_ENABLE();
    
    RCC_OscInitTypeDef osc_init_struct = {0};
    RCC_PeriphCLKInitTypeDef periphclk_init_struct = {0};
    
    //振荡器配置
    osc_init_struct.OscillatorType = RCC_OSCILLATORTYPE_LSE;        //选择低速外部时钟振荡器
    osc_init_struct.LSEState = RCC_LSE_ON;                          //打开低速外部时钟
    osc_init_struct.PLL.PLLState = RCC_PLL_NONE;                    //不适用锁相环
    HAL_RCC_OscConfig(&osc_init_struct);
    //外设时钟配置
    periphclk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_RTC; //选择外设时钟为RTC时钟
    periphclk_init_struct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; //选择RTC时钟来源为低速外部时钟
    HAL_RCCEx_GetPeriphCLKConfig(&periphclk_init_struct);
    
    //设置中断优先级
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 2 ,2);
    //使能中断
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

//中断服务函数
void RTC_Alarm_IRQHandler(void)
{
    //公共处理函数
    HAL_RTC_AlarmIRQHandler(&rtc_handle);
}

//中断回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    printf("ring ring ring ...\r\n");
}

//读BKR
uint16_t rtc_read_bkr(uint8_t bkrx)
{
    uint32_t data = 0;
    
    data = HAL_RTCEx_BKUPRead(&rtc_handle, bkrx);
    return (uint16_t)data;
}

//写BKR
void rtc_write_bkr(uint8_t bkrx, uint16_t data)
{
    HAL_RTCEx_BKUPWrite(&rtc_handle, bkrx, data);
}

//获取RTC时间
void rtc_get_time(void)
{
    //RTC时间日期结构体
    RTC_TimeTypeDef rtc_time = {0};
    RTC_DateTypeDef rtc_date = {0};
    
    //获取时间日期
    HAL_RTC_GetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
    //打印时间日期
    printf("rtc time: %d-%02d-%02d %02d:%02d:%02d\r\n", rtc_date.Year + 2000, rtc_date.Month, rtc_date.Date,
        rtc_time.Hours,rtc_time.Minutes, rtc_time.Seconds);
}

//设置RTC时间
void rtc_set_time(struct tm time_data)
{
    //RTC时间日期结构体
    RTC_TimeTypeDef rtc_time = {0};
    RTC_DateTypeDef rtc_date = {0};
    
    //设置RTC时间日期
    rtc_time.Hours = time_data.tm_hour;
    rtc_time.Minutes = time_data.tm_min;
    rtc_time.Seconds = time_data.tm_sec;
    rtc_date.Year = time_data.tm_year - 2000;
    rtc_date.Month = time_data.tm_mon;
    rtc_date.Date = time_data.tm_mday;
    
    //设置RTC时间日期
    HAL_RTC_SetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
    HAL_RTC_SetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
    
    //等待RTC写入完成
    while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle, RTC_FLAG_RTOFF));
}

//设置闹钟
void rtc_set_alarm(struct tm alarm_data)
{
    RTC_AlarmTypeDef alarm = {0};
    
    alarm.Alarm = RTC_ALARM_A;
    alarm.AlarmTime.Hours = alarm_data.tm_hour;
    alarm.AlarmTime.Minutes = alarm_data.tm_min;
    alarm.AlarmTime.Seconds = alarm_data.tm_sec;
    HAL_RTC_SetAlarm_IT(&rtc_handle, &alarm, RTC_FORMAT_BIN);
}

rtc.h

c 复制代码
#ifndef __RTC_H__
#define __RTC_H__

#include "sys.h"
#include "time.h"

//RTC初始化
void rtc_init(void);
//读BKR
uint16_t rtc_read_bkr(uint8_t bkrx);
//写BKR
void rtc_write_bkr(uint8_t bkrx, uint16_t data);
//获取RTC时间
void rtc_get_time(void);
//设置RTC时间
void rtc_set_time(struct tm time_data);
//设置闹钟
void rtc_set_alarm(struct tm alarm_data);

#endif

综合项目:实时时钟

项目需求

  1. OLED屏幕显示当前时间、日期、闹钟等信息;
  2. 正常模式下,按下 KEY1 ,进入时间设置模式,此时按下 KEY2 则可以循环跳转修改秒、分、时、日、月、年;
  3. 时间设置模式下,KEY3 增加数值,KEY4 减小数值,再次按下 KEY1 则退出时间设置模式,并保存修改后的时间;
  4. 正常模式下,按下 KEY2,进入闹钟设置模式,此时按下 KEY2 则可以循环跳转修改秒、分、时;
  5. 闹钟设置模式下,KEY3 增加数值,KEY4 减小数值,再次按下 KEY1 则退出闹钟设置模式,并保存修改后的闹钟;
  6. 到达闹钟时间后,蜂鸣器响起。按下 KEY3 或 KEY4 停止蜂鸣器。

硬件清单

  • OLED 屏幕
  • 蜂鸣器
  • 按键(2个)
  • 杜邦线
  • STM32
  • ST-Link
  • USB转TTL

硬件接线

项目框图

**

完整代码

main.c

复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "beep.h"
#include "key.h"
#include "oled.h"
#include "rtc.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */      
    //LED初始化
    //led_init();
    //串口1初始化
    uart1_init(115200);
    //蜂鸣器初始化
    beep_init();
    //按键初始化
    key_init();
    //OLED初始化
    oled_init();
    //RTC初始化
    rtc_init();
    
    //串口打印测试
    printf("打印测试:hello world\r\n");
    
    //定义时间日期数据,闹钟数据
    uint8_t time_data[6] = {24, 7, 4, 16, 8, 30};
    uint8_t alarm_data[3] = {16, 8, 40};
    uint8_t set_time_shift = TIME_SECOND;
    uint8_t set_alarm_shift = ALARM_SECOND;
    uint8_t set_time_flag = 0, set_alarm_flag = 0;
    
    //初始化OLED屏幕
    oled_show_init();
    
    //如果RTC读取BKR无内容则设置时间日期闹钟
    if(rtc_read_bkr(1) != 0xA5A5)
    {
        rtc_write_bkr(1, 0xA5A5);
        
        rtc_set_time(time_data);
        rtc_set_alarm(alarm_data);
    }
    
    while(1)
    {
        //获取时间和闹钟
        rtc_get_time(time_data);
        rtc_get_alarm(alarm_data);
        //并在OLED屏幕显示
        oled_show_time_alarm(time_data, alarm_data);
        
        switch(key_scan()){
            case KEY_SET:
                //进入时间设置模式
                set_time_flag = 1;
                while(set_time_flag){
                    //闪动要修改的坑位
                    oled_show_element(time_data[set_time_shift], OFF, set_time_shift);
                    delay_ms(100);
                    oled_show_element(time_data[set_time_shift], ON, set_time_shift);
                    delay_ms(100);
                    switch(key_scan()){
                        case KEY_SET:
                            //退出时间设置模式
                            set_time_flag = 0;
                            //闪烁数字重新定位到秒
                            set_time_shift = TIME_SECOND;
                            //保存修改后的时间
                            rtc_set_time(time_data);
                            break;
                        case KEY_SHIFT:
                            //跳转到下一个需要修改的元素(秒 分 时 日 月 年)
                            if(set_time_shift-- <= TIME_YEAR){
                                set_time_shift = TIME_SECOND;
                            }
                            break;
                        case KEY_UP:
                            //增加数值
                            //限制分钟和秒数值
                            if(set_time_shift == TIME_SECOND || set_time_shift == TIME_MINUTE){
                                if(time_data[set_time_shift] < 59){
                                    time_data[set_time_shift]++;
                                }
                            }
                            //限制小时数值
                            if(set_time_shift == TIME_HOUR){
                                if(time_data[set_time_shift] < 23){
                                    time_data[set_time_shift]++;
                                }
                            }
                            //限制日数值
                            if(set_time_shift == TIME_DAY){
                                if(time_data[set_time_shift] < 31){
                                    time_data[set_time_shift]++;
                                }
                            }
                            //限制月数值
                            if(set_time_shift == TIME_DAY){
                                if(time_data[set_time_shift] < 12){
                                    time_data[set_time_shift]++;
                                }
                            }
                            //限制年数值
                            if(set_time_shift == TIME_DAY){
                                if(time_data[set_time_shift] < 99){
                                    time_data[set_time_shift]++;
                                }
                            }
                            break;
                        case KEY_DOWN:
                            //减少数值
                            //限制减小的最小值  
                            if(time_data[set_time_shift] > 0){
                                    time_data[set_time_shift]--;
                            }
                            break;
                        default:
                            break;
                    }
                }
                break;
            case KEY_SHIFT:
                //进入闹钟设置模式
               set_alarm_flag = 1;
               while(set_alarm_flag){
                    //闪动要修改的坑位
                    oled_show_element(alarm_data[set_alarm_shift - ALARM_HOUR], OFF, set_alarm_shift);
                    delay_ms(100);
                    oled_show_element(alarm_data[set_alarm_shift - ALARM_HOUR], ON, set_alarm_shift);
                    delay_ms(100);
                    switch(key_scan()){
                        case KEY_SET:
                            //退出闹钟设置模式
                            set_alarm_flag = 0;
                            //闪烁数字重新定位到秒
                            set_alarm_shift = ALARM_SECOND;
                            //保存修改后的闹钟
                            rtc_set_alarm(alarm_data);
                            break;
                        case KEY_SHIFT:
                            ////跳转到下一个需要修改的元素(秒 分 时 日 月 年)
                            if(set_alarm_shift-- <= ALARM_HOUR){
                                set_alarm_shift = ALARM_SECOND;
                            }
                            break;
                        case KEY_UP:
                            //增加数值
                            //限制分钟和秒数值
                            if(set_alarm_shift == ALARM_SECOND || set_alarm_shift == ALARM_MINUTE){
                                if(alarm_data[set_alarm_shift - ALARM_HOUR] < 59){
                                    alarm_data[set_alarm_shift - ALARM_HOUR]++;
                                }
                            }
                            //限制小时数值
                            if(set_alarm_shift == ALARM_HOUR){
                                if(alarm_data[set_alarm_shift - ALARM_HOUR] < 23){
                                    alarm_data[set_alarm_shift - ALARM_HOUR]++;
                                }
                            }
                            break;
                        case KEY_DOWN:
                            //减少数值
                            //限制减小的最小值  
                            if(alarm_data[set_alarm_shift - ALARM_HOUR] > 0){
                                    alarm_data[set_alarm_shift - ALARM_HOUR]--;
                            }
                            break;
                        default:
                            break;
                    }
               }
                break;
            case KEY_UP:
            case KEY_DOWN:
                //停止蜂鸣器
                beep_off();
                break;
            default:
                break;
        }
        
//        delay_ms(1000);
    }
}

rtc.c

复制代码
#include "rtc.h"
#include "stdio.h"
#include "beep.h"

RTC_HandleTypeDef rtc_handle = {0};
//RTC初始化
void rtc_init(void)
{
    //使能PWR时钟
    __HAL_RCC_PWR_CLK_ENABLE();
    //使能BKP时钟
    __HAL_RCC_BKP_CLK_ENABLE();
    //使能BKP访问
    HAL_PWR_EnableBkUpAccess();
    
    //参数配置
    rtc_handle.Instance = RTC;                          //选择RTC
    rtc_handle.Init.AsynchPrediv = 32767;               //分频系数
    rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;     //不用侵入检测
    //初始化
    HAL_RTC_Init(&rtc_handle);
}

//硬件配置
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
    //RTC时钟使能
    __HAL_RCC_RTC_ENABLE();
    
    RCC_OscInitTypeDef osc_init_struct = {0};
    RCC_PeriphCLKInitTypeDef periphclk_init_struct = {0};
    
    //振荡器配置
    osc_init_struct.OscillatorType = RCC_OSCILLATORTYPE_LSE;        //选择低速外部时钟振荡器
    osc_init_struct.LSEState = RCC_LSE_ON;                          //打开低速外部时钟
    osc_init_struct.PLL.PLLState = RCC_PLL_NONE;                    //不适用锁相环
    HAL_RCC_OscConfig(&osc_init_struct);
    //外设时钟配置
    periphclk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_RTC; //选择外设时钟为RTC时钟
    periphclk_init_struct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; //选择RTC时钟来源为低速外部时钟
    HAL_RCCEx_PeriphCLKConfig(&periphclk_init_struct);
    
    //设置中断优先级
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 2 ,2);
    //使能中断
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

//中断服务函数
void RTC_Alarm_IRQHandler(void)
{
    //公共处理函数
    HAL_RTC_AlarmIRQHandler(&rtc_handle);
}

//中断回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    beep_on();
}

//读BKR
uint16_t rtc_read_bkr(uint8_t bkrx)
{
    uint32_t data = 0;
    
    data = HAL_RTCEx_BKUPRead(&rtc_handle, bkrx);
    return (uint16_t)data;
}

//写BKR
void rtc_write_bkr(uint8_t bkrx, uint16_t data)
{
    HAL_RTCEx_BKUPWrite(&rtc_handle, bkrx, data);
}

//获取RTC时间
void rtc_get_time(uint8_t *time_data)
{
    //RTC时间日期结构体
    RTC_TimeTypeDef rtc_time = {0};
    RTC_DateTypeDef rtc_date = {0};
    
    //获取时间日期
    HAL_RTC_GetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
    //时间日期存入数组
    time_data[0] = rtc_date.Year;
    time_data[1] = rtc_date.Month;
    time_data[2] = rtc_date.Date;
    time_data[3] = rtc_time.Hours;
    time_data[4] = rtc_time.Minutes;
    time_data[5] = rtc_time.Seconds;
    
    //打印时间日期
//    printf("rtc time: %d-%02d-%02d %02d:%02d:%02d\r\n", rtc_date.Year + 2000, rtc_date.Month, rtc_date.Date,
//        rtc_time.Hours,rtc_time.Minutes, rtc_time.Seconds);
}

//设置RTC时间
void rtc_set_time(uint8_t *time_data)
{
    //RTC时间日期结构体
    RTC_TimeTypeDef rtc_time = {0};
    RTC_DateTypeDef rtc_date = {0};
    
    //设置RTC时间日期
    rtc_time.Hours = time_data[3];
    rtc_time.Minutes = time_data[4];
    rtc_time.Seconds = time_data[5];
    //设置RTC时间日期
    HAL_RTC_SetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
    
    rtc_date.Year = time_data[0];
    rtc_date.Month = time_data[1];
    rtc_date.Date = time_data[2];
    //设置RTC时间日期
    HAL_RTC_SetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
    
    //等待RTC写入完成
    while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle, RTC_FLAG_RTOFF));
}

//获取闹钟时间
void rtc_get_alarm(uint8_t *alarm_data)
{
    RTC_AlarmTypeDef alarm = {0};
    
    HAL_RTC_GetAlarm(&rtc_handle,&alarm, RTC_ALARM_A, RTC_FORMAT_BIN);
    alarm_data[0] = alarm.AlarmTime.Hours;
    alarm_data[1] = alarm.AlarmTime.Minutes;
    alarm_data[2] = alarm.AlarmTime.Seconds;
}

//设置闹钟
void rtc_set_alarm(uint8_t *alarm_data)
{
    RTC_AlarmTypeDef alarm = {0};
    
    alarm.Alarm = RTC_ALARM_A;
    alarm.AlarmTime.Hours = alarm_data[0];
    alarm.AlarmTime.Minutes = alarm_data[1];
    alarm.AlarmTime.Seconds = alarm_data[2];
    HAL_RTC_SetAlarm_IT(&rtc_handle, &alarm, RTC_FORMAT_BIN);
}

rtc.h

复制代码
#ifndef __RTC_H__
#define __RTC_H__

#include "sys.h"
#include "time.h"

//RTC初始化
void rtc_init(void);
//读BKR
uint16_t rtc_read_bkr(uint8_t bkrx);
//写BKR
void rtc_write_bkr(uint8_t bkrx, uint16_t data);
//获取RTC时间
void rtc_get_time(uint8_t *time_data);
//设置RTC时间
void rtc_set_time(uint8_t *time_data);
//获取闹钟时间
void rtc_get_alarm(uint8_t *alarm_data);
//设置闹钟
void rtc_set_alarm(uint8_t *alarm_data);

#endif

oled.c

复制代码
#include "oled.h"
#include "delay.h"
#include "font.h"

//OLED相关GPIO初始化
void oled_gpio_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    
    //使能SCL和SDA引脚时钟
    OLED_I2C_SCL_CLK();
    OLED_I2C_SDA_CLK();
    
    //GPIO初始化配置
    gpio_initstruct.Pin = OLED_I2C_SCL_PIN;         //SCL对应引脚
    gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;     //推挽输出
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   //高速
    gpio_initstruct.Pull = GPIO_PULLUP;             //上拉
    HAL_GPIO_Init(OLED_I2C_SCL_PORT, &gpio_initstruct);
    
    gpio_initstruct.Pin = OLED_I2C_SDA_PIN;         //SDA对应引脚
    HAL_GPIO_Init(OLED_I2C_SDA_PORT, &gpio_initstruct);
}
//I2C起始信号
void oled_i2c_start(void)
{
    OLED_SCL_SET();
    OLED_SDA_SET();
    OLED_SDA_RESET();
    OLED_SCL_RESET();
}
//I2C停止信号
void oled_i2c_stop(void)
{
    OLED_SCL_SET();
    OLED_SDA_RESET();
    OLED_SDA_SET();
}
//I2C应答信号
void oled_i2c_ack(void)
{
    OLED_SCL_SET();
    OLED_SCL_RESET();
}
//I2C写字节
void oled_i2c_write_byte(uint8_t data)
{
    uint8_t i, tmp;
    tmp = data;
    //往SDA总线上循环写数据位,高位先行
    for(i = 0; i <8 ;i++){
        //取出最高位
        if((tmp & 0x80) == 0x80){
            OLED_SDA_SET();
        }else{
            OLED_SDA_RESET();
        }
        //逻辑左移一位,去除次高位
        tmp = tmp << 1;
        OLED_SCL_SET();
        OLED_SCL_RESET();
    }
}
//OLED写命令
void oled_write_cmd(uint8_t cmd)
{
    oled_i2c_start();
    oled_i2c_write_byte(0x78);
    oled_i2c_ack();
    oled_i2c_write_byte(0x00);
    oled_i2c_ack();
    oled_i2c_write_byte(cmd);
    oled_i2c_ack();
    oled_i2c_stop();
}
//OLED写数据
void oled_write_data(uint8_t data)
{
    oled_i2c_start();
    oled_i2c_write_byte(0x78);
    oled_i2c_ack();
    oled_i2c_write_byte(0x40);
    oled_i2c_ack();
    oled_i2c_write_byte(data);
    oled_i2c_ack();
    oled_i2c_stop();
}
//OLED初始化
void oled_init(void)
{
    oled_gpio_init();
    
    delay_ms(100);
    
    oled_write_cmd(0xAE);    //设置显示开启/关闭,0xAE关闭,0xAF开启

    oled_write_cmd(0xD5);    //设置显示时钟分频比/振荡器频率
    oled_write_cmd(0x80);    //0x00~0xFF

    oled_write_cmd(0xA8);    //设置多路复用率
    oled_write_cmd(0x3F);    //0x0E~0x3F

    oled_write_cmd(0xD3);    //设置显示偏移
    oled_write_cmd(0x00);    //0x00~0x7F

    oled_write_cmd(0x40);    //设置显示开始行,0x40~0x7F

    oled_write_cmd(0xA1);    //设置左右方向,0xA1正常,0xA0左右反置

    oled_write_cmd(0xC8);    //设置上下方向,0xC8正常,0xC0上下反置

    oled_write_cmd(0xDA);    //设置COM引脚硬件配置
    oled_write_cmd(0x12);

    oled_write_cmd(0x81);    //设置对比度
    oled_write_cmd(0xCF);    //0x00~0xFF

    oled_write_cmd(0xD9);    //设置预充电周期
    oled_write_cmd(0xF1);

    oled_write_cmd(0xDB);    //设置VCOMH取消选择级别
    oled_write_cmd(0x30);

    oled_write_cmd(0xA4);    //设置整个显示打开/关闭

    oled_write_cmd(0xA6);    //设置正常/反色显示,0xA6正常,0xA7反色

    oled_write_cmd(0x8D);    //设置充电泵
    oled_write_cmd(0x14);

    oled_write_cmd(0xAF);    //开启显示
    
    oled_fill(0x00);         //清空屏幕
}

//设置坐标
void oled_set_cursor(uint8_t x, uint8_t y)
{
    //指定待写入页
    oled_write_cmd(0xB0 + y);
    //指定待写入列
    oled_write_cmd((x & 0x0F) | 0x00);
    oled_write_cmd((x & 0xF0) >> 4 | 0x10);
}
//循环填充
void oled_fill(uint8_t data)
{
    uint8_t i,j;
    
    for(i = 0;i < 8;i++){
        oled_set_cursor(0, i);      //指定一次页,写一次列自动往后偏移
        for(j = 0;j < 128;j++){
            oled_write_data(data);
        }
    }
}
//OLED显示一个字符
void oled_show_char(uint8_t x, uint8_t y, uint8_t num, uint8_t size)
{
    uint8_t i,j, page;
    //ASCII码相对第一个空格字符偏移
    num = num - ' ';
    //确定字符所占页数
    page = size / 8;
    if(size % 8){
        page++;
    }
    //循环刷新屏幕
    for(j = 0;j < page;j++){
        //设定字符坐标
        oled_set_cursor(x, y + j);
        //分行写数据
        for(i = size/2 * j;i < size/2 * (j+1);i++){
            if(size == 12){
                oled_write_data(ascii_6X12[num][i]);
            }else if(size == 16){
                oled_write_data(ascii_8X16[num][i]);
            }else if(size == 24){
                oled_write_data(ascii_12X24[num][i]);
            }
        }
    }
}
//OLED显示字符串
void oled_show_string(uint8_t x, uint8_t y, char *p, uint8_t size)
{
    while(*p != '\0'){
        oled_show_char(x, y, *p, size);
        x += size/2;
        p++;
    }
}
//OLED显示汉字
//void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N, uint8_t size)
//{
//    uint8_t i,j, page;
//    
//    //确定汉字所占页数
//    page = size / 8;
//    if(size % 8){
//        page++;
//    }
//    //循环刷新屏幕
//    for(j = 0;j < page;j++){
//        //设定字符坐标
//        oled_set_cursor(x, y + j);
//        //分行写数据
//        for(i = size * j;i < size * (j+1);i++){
//            if(size == 16){
//                oled_write_data(chinese_16x16[N][i]);
//            }else if(size == 24){
//                oled_write_data(chinese_24x24[N][i]);
//            }
//        }
//    }
//}
//OLED显示汉字
void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N)
{
    uint16_t i,j;
    for(j = 0;j < 2;j++){
        oled_set_cursor(x, y + j);
        for(i = 16 * j;i < 16 * (j + 1);i++){
            oled_write_data(chinese_time[N][i]);
        }
    }
}

//OLED显示初始化
void oled_show_init(void)
{
    oled_fill(0x00);
    oled_show_string(8, 0, "2000", 16);             //2024
    oled_show_chinese(40, 0, 0);                    //年
    oled_show_string(56, 0, "00", 16);              //07
    oled_show_chinese(72, 0, 1);                    //月
    oled_show_string(88, 0, "00", 16);              //03
    oled_show_chinese(104, 0, 2);                   //日
    
    oled_show_string(26, 2, "00", 16);              //12
    oled_show_char(45, 2, ':', 16);                 //:
    oled_show_string(56, 2, "00", 16);              //35
    oled_show_char(75, 2, ':', 16);                 //:
    oled_show_string(86, 2, "00", 16);              //20
    
    oled_show_chinese(10, 4, 3);                    //闹
    oled_show_chinese(26, 4, 4);                    //钟
    oled_show_char(42, 4, ':', 16);                 //:
    
    oled_show_string(26, 6, "00", 16);              //12
    oled_show_char(45, 6, ':', 16);                 //:
    oled_show_string(56, 6, "00", 16);              //35
    oled_show_char(75, 6, ':', 16);                 //:
    oled_show_string(86, 6, "00", 16);              //20
}

//指定位置清空函数
void oled_clear_2char(uint8_t x, uint8_t y)
{
    uint8_t i = 0;
    //清空上半部分
    oled_set_cursor(x, y);
    for(i = 0; i < 16; i++){
        oled_write_data(0x00);
    }
    //清空下半部分
    oled_set_cursor(x, y + 1);
    for(i = 0; i < 16; i++){
        oled_write_data(0x00);
    }
}

//填充年
void oled_show_year(uint8_t num, uint8_t display_flag)
{
    if(display_flag){
        //显示年份的坑位
        oled_show_char(24, 0, num/10 + '0', 16);
        oled_show_char(32, 0, num%10 + '0', 16);
    }else{
        //不显示年份的坑位
        oled_clear_2char(24, 0);
    }
}

//月
void oled_show_month(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(56, 0, num/10 + '0', 16);
        oled_show_char(64, 0, num%10 + '0', 16);
    }
    else
        oled_clear_2char(56, 0);
}

//日
void oled_show_day(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(88, 0, num/10 + '0', 16);
        oled_show_char(96, 0, num%10 + '0', 16);
    }
    else
        oled_clear_2char(88, 0);
}

//时
void oled_show_hour(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(26, 2, num/10 + '0', 16);
        oled_show_char(34, 2, num%10 + '0', 16);
    }
    else
        oled_clear_2char(26, 2);
}

//分
void oled_show_minute(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(56, 2, num/10 + '0', 16);
        oled_show_char(64, 2, num%10 + '0', 16);
    }
    else
        oled_clear_2char(56, 2);
}

//秒
void oled_show_second(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(86, 2, num/10 + '0', 16);
        oled_show_char(94, 2, num%10 + '0', 16);
    }
    else
        oled_clear_2char(86, 2);
}

//闹钟:时
void oled_show_alarm_hour(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(26, 6, num/10 + '0', 16);
        oled_show_char(34, 6, num%10 + '0', 16);
    }
    else
        oled_clear_2char(26, 6);
}

//闹钟:分
void oled_show_alarm_minute(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(56, 6, num/10 + '0', 16);
        oled_show_char(64, 6, num%10 + '0', 16);
    }
    else
        oled_clear_2char(56, 6);
}

//闹钟:秒
void oled_show_alarm_second(uint8_t num, uint8_t display_flag)
{
    if(display_flag)
    {
        oled_show_char(86, 6, num/10 + '0', 16);
        oled_show_char(94, 6, num%10 + '0', 16);
    }
    else
        oled_clear_2char(86, 6);
}

//OLED显示各个日期时间参数
void oled_show_element(uint8_t num, uint8_t display_flag, uint8_t element)
{
    switch(element)
    {
        case TIME_YEAR:
            oled_show_year(num, display_flag);
            break;
        case TIME_MONTH:
            oled_show_month(num, display_flag);
            break;
        case TIME_DAY:
            oled_show_day(num, display_flag);
            break;
        case TIME_HOUR:
            oled_show_hour(num, display_flag);
            break;
        case TIME_MINUTE:
            oled_show_minute(num, display_flag);
            break;
        case TIME_SECOND:
            oled_show_second(num, display_flag);
            break;
        case ALARM_HOUR:
            oled_show_alarm_hour(num, display_flag);
            break;
        case ALARM_MINUTE:
            oled_show_alarm_minute(num, display_flag);
            break;
        case ALARM_SECOND:
            oled_show_alarm_second(num, display_flag);
            break;
        default:
            break;
    }
}

//显示时间日期闹钟
void oled_show_time_alarm(uint8_t *time, uint8_t *alarm)
{
    oled_show_year(time[0], 1);
    oled_show_month(time[1], 1);
    oled_show_day(time[2], 1);
    oled_show_hour(time[3], 1);
    oled_show_minute(time[4], 1);
    oled_show_second(time[5], 1);
    oled_show_alarm_hour(alarm[0], 1);
    oled_show_alarm_minute(alarm[1], 1);
    oled_show_alarm_second(alarm[2], 1);
}

//OLED显示图片
void oled_show_image(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t *bmp)
{
    uint8_t i, j;
    for(j = 0; j < height; j++)
    {
        oled_set_cursor(x, y + j);
        for(i = 0; i < width; i++)
            oled_write_data(bmp[width * j + i]);
    }
}

oled.h

复制代码
#ifndef __OLED_H__
#define __OLED_H__

#include "sys.h"

#define OLED_I2C_SCL_CLK()      __HAL_RCC_GPIOB_CLK_ENABLE()
#define OLED_I2C_SCL_PORT       GPIOB
#define OLED_I2C_SCL_PIN        GPIO_PIN_8

#define OLED_I2C_SDA_CLK()      __HAL_RCC_GPIOB_CLK_ENABLE()
#define OLED_I2C_SDA_PORT       GPIOB
#define OLED_I2C_SDA_PIN        GPIO_PIN_9

#define OLED_SCL_RESET()        HAL_GPIO_WritePin(OLED_I2C_SCL_PORT,OLED_I2C_SCL_PIN,GPIO_PIN_RESET)
#define OLED_SCL_SET()        HAL_GPIO_WritePin(OLED_I2C_SCL_PORT,OLED_I2C_SCL_PIN,GPIO_PIN_SET)

#define OLED_SDA_RESET()        HAL_GPIO_WritePin(OLED_I2C_SDA_PORT,OLED_I2C_SDA_PIN,GPIO_PIN_RESET)
#define OLED_SDA_SET()        HAL_GPIO_WritePin(OLED_I2C_SDA_PORT,OLED_I2C_SDA_PIN,GPIO_PIN_SET)

#define ON      1
#define OFF     0

enum elements
{
    TIME_YEAR = 0,
    TIME_MONTH,
    TIME_DAY,
    TIME_HOUR,
    TIME_MINUTE,
    TIME_SECOND,
    ALARM_HOUR,
    ALARM_MINUTE,
    ALARM_SECOND,
};

//OLED初始化
void oled_init(void);
//I2C写命令
void oled_write_cmd(uint8_t cmd);
//I2C写数据
void oled_write_data(uint8_t data);
//循环填充
void oled_fill(uint8_t data);
//设置坐标
void oled_set_cursor(uint8_t x, uint8_t y);
//OLED显示一个字符
void oled_show_char(uint8_t x, uint8_t y, uint8_t num, uint8_t size);
//OLED显示字符串
void oled_show_string(uint8_t x, uint8_t y, char *p, uint8_t size);
//OLED显示汉字
void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N);
//OLED显示图片
void oled_show_image(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t *bmp);
//OLED显示初始化
void oled_show_init(void);
//指定位置清空函数
void oled_clear_2char(uint8_t x, uint8_t y);
//OLED显示各个日期时间参数
void oled_show_element(uint8_t num, uint8_t display_flag, uint8_t element);
//显示时间日期闹钟
void oled_show_time_alarm(uint8_t *time, uint8_t *alarm);
    
#endif

key.c

复制代码
#include "key.h"
#include "delay.h"

//初始化GPIO
void key_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    
    //使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;  //KEY1,KEY2,KEY3,KEY4对应引脚
    gpio_initstruct.Mode = GPIO_MODE_INPUT;         //输入
    gpio_initstruct.Pull = GPIO_PULLUP;             //上拉
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;   //高速
    HAL_GPIO_Init(GPIOA, &gpio_initstruct);
}

//按键扫描函数
uint8_t key_scan(void)
{
    //检测按键1是否按下
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
        //消抖
        delay_ms(10);
        //再次判断按键是否按下
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
            //如果确实按下,那么等待按键松开
            while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
            //返回按键值
            return KEY_SET;
        }
    }
    
    //检测按键2是否按下
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){
        //消抖
        delay_ms(10);
        //再次判断按键是否按下
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){
            //如果确实按下,那么等待按键松开
            while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
            //返回按键值
            return KEY_SHIFT;
        }
    }
    
    //检测按键3是否按下
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2) == GPIO_PIN_RESET){
        //消抖
        delay_ms(10);
        //再次判断按键是否按下
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2) == GPIO_PIN_RESET){
            //如果确实按下,那么等待按键松开
            while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2) == GPIO_PIN_RESET);
            //返回按键值
            return KEY_UP;
        }
    }
    
    //检测按键4是否按下
    if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3) == GPIO_PIN_RESET){
        //消抖
        delay_ms(10);
        //再次判断按键是否按下
        if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3) == GPIO_PIN_RESET){
            //如果确实按下,那么等待按键松开
            while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3) == GPIO_PIN_RESET);
            //返回按键值
            return KEY_DOWN;
        }
    }
    
    //返回默认值
    return 0;
}

key.h

复制代码
#ifndef __KEY_H__
#define __KEY_H__

#include "sys.h"

enum key_num
{
    KEY_SET = 1,
    KEY_SHIFT,
    KEY_UP,
    KEY_DOWN
};

void key_init(void);
uint8_t key_scan(void);

#endif 

项目实物图

相关推荐
国科安芯2 小时前
车规MCU在农业无人机电机驱动中的可靠性分析
单片机·嵌入式硬件·性能优化·无人机·安全威胁分析·安全性测试
TangDuoduo00052 小时前
【UART控制器HAL库常用接口】
单片机·嵌入式硬件
List<String> error_P2 小时前
STM32数据手册速查
stm32·单片机·嵌入式硬件
dlz08363 小时前
GPIO的通用初始化流程
单片机·嵌入式硬件
boneStudent4 小时前
Day18:系统滴答定时器 (SysTick)
stm32·单片机·嵌入式硬件
d111111111d4 小时前
再STM32F103C8T6中小端存储和大端存储有什么不同,STM32F103C8T6用的是那个,为什么要这么使用?
笔记·stm32·单片机·嵌入式硬件·学习
云山工作室4 小时前
基于物联网的体温心率监测系统设计(论文+源码)
stm32·单片机·嵌入式硬件·物联网·课程设计
猫猫的小茶馆4 小时前
【ARM】VSCode和IAR工程创建
c语言·开发语言·arm开发·ide·vscode·stm32·嵌入式硬件
zephyr_zeng4 小时前
CubeMX项目轻松导入Vscode+EIDE编译
c语言·ide·vscode·stm32·mcu·物联网·编辑器