目录
- [1 什么是RTC?](#1 什么是RTC?)
- [2 RTC框图](#2 RTC框图)
- [3 寄存器及库函数](#3 寄存器及库函数)
- [4 RTC驱动步骤](#4 RTC驱动步骤)
- 小实验1:读写RTC时间实验
- 小实验2: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
综合项目:实时时钟
项目需求
- OLED屏幕显示当前时间、日期、闹钟等信息;
- 正常模式下,按下 KEY1 ,进入时间设置模式,此时按下 KEY2 则可以循环跳转修改秒、分、时、日、月、年;
- 时间设置模式下,KEY3 增加数值,KEY4 减小数值,再次按下 KEY1 则退出时间设置模式,并保存修改后的时间;
- 正常模式下,按下 KEY2,进入闹钟设置模式,此时按下 KEY2 则可以循环跳转修改秒、分、时;
- 闹钟设置模式下,KEY3 增加数值,KEY4 减小数值,再次按下 KEY1 则退出闹钟设置模式,并保存修改后的闹钟;
- 到达闹钟时间后,蜂鸣器响起。按下 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
项目实物图
