项目流程图
即核心代码为修改时间部分 :①当按下key1时,进入到修改时间的部分,此时再按下key2会偏移修改的位置,按下key3和key4时会增加数值和减少数值(有范围限制)按下key1会保存和退出当前模式,②当按下key2时会进入到闹钟的修改部分,同时key2偏移 key1 保存和退出,key3 4 增加数值或者减少数值③按下key 3 key4 停止蜂鸣器报警
rtc内置时钟运行规则,可以直接通过RTC寄存器获取时间,无需手动计算进位。
main.c函数如下
#include "sys.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_init();
uart1_init(115200);
beep_init();
KEY_init();
oled_init();
rtc_init();
printf("hello world!\r\n");
uint8_t time_data[6] = {25 , 5, 17, 19, 40, 30};
uint8_t alarm_data[3] = {17, 50, 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_show_init();
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);
//delay_ms(1000);
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_MONTH ) // 月
if(time_data[set_time_shift] <23)
time_data[set_time_shift]++;
if(set_time_shift ==TIME_YEAR ) // 年
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(alarm_data[set_alarm_shift -ALARM_HOUR] <59)
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;
}
}
}
oled.c代码如下
#include "oled.h"
#include "font.h"
#include "delay.h"
void oled_gpio_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
OLED_I2C_SCL_CLK();
OLED_I2C_SDA_CLK();
gpio_initstruct.Pin = OLED_I2C_SCL_PIN; //LED1、LED2对应的引脚
gpio_initstruct.Pull = GPIO_PULLUP; //上拉
gpio_initstruct.Mode =GPIO_MODE_OUTPUT_PP; //开漏输出
gpio_initstruct.Speed =GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(OLED_I2C_SCL_PORT,&gpio_initstruct);
gpio_initstruct.Pin = OLED_I2C_SDA_PIN; //LED1、LED2对应的引脚
HAL_GPIO_Init(OLED_I2C_SDA_PORT,&gpio_initstruct);
}
void oled_i2c_start(void)
{
OLED_SCL_SET();
OLED_SDA_SET();
OLED_SDA_RESET();
OLED_SCL_RESET();
}
void oled_i2c_stop(void)
{
OLED_SCL_SET();
OLED_SDA_RESET();
OLED_SDA_SET();
}
void oled_i2c_ack(void)
{
OLED_SCL_SET();
OLED_SCL_RESET();
}
void oled_i2c_write_byte(uint8_t data) //发送一个字节8位并保存数据 保存数据是偏移后的第一位如果是1则置SDA为高电平 反之则置低电平
{
uint8_t i,tmp;
tmp= data;
for(i=0;i<8;i++)
{
if((tmp & 0x80) == 0x80) //“与” AND的运算1&0=0 0&0=0 1&1=1 这是为了看最高位是否为1
OLED_SDA_SET();
else
OLED_SDA_RESET();
tmp=tmp << 1;
OLED_SCL_SET();
OLED_SCL_RESET();
}
}
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();
}
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();
}
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) //设置写的位置在哪里 y为page x为列
{
oled_write_cmd(0xB0 + y); //设置page
/*设置哪一列*/
oled_write_cmd(( x & 0x0F) | 0x00);
oled_write_cmd((x & 0xF0) >> 4 | 0x10 );
}
void oled_fill(uint8_t data) //注意oled_set_cursor(0, i); 的位置是因为这里的寻址模式采用的是页地址的寻址模式
{
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);
}
}
}
void oled_show_char(uint8_t x,uint8_t y ,uint8_t num,uint8_t size) //y为哪一行 x为哪一列,num为ascii值,size为高度 设置了三个高度 12 16 24
{
uint8_t i,j,page;
num=num-' ';
page =size / 8;
if(size % 8 != 0)
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]);
}
}
}
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++;
}
}
//void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N, uint8_t size) //N为汉字的位数
//{
// uint16_t i, j;
// for(j = 0; j < size/8; 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]);
// }
// }
//}
void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N) //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]);
}
}
}
void oled_show_init(void)
{
oled_fill(0x00);
oled_show_string(8,0,"2000",16); //2025
oled_show_chinese(40,0,0); //年
oled_show_string(56,0,"00",16); //05
oled_show_chinese(72,0,1); //月
oled_show_string(88,0,"00",16); //17
oled_show_chinese(104,0,2); //日
oled_show_string(26,2,"00",16); //17
oled_show_char(45,2,':',16); //:
oled_show_string(56,2,"00",16); //30
oled_show_char(75,2,':',16); //:
oled_show_string(86,2,"00",16); //30
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); //17
oled_show_char(45,6,':',16); //:
oled_show_string(56,6,"00",16); //31
oled_show_char(75,6, ':',16); //:
oled_show_string(86,6, "00",16); //30
}
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) //display_flag的效果是当置1时存在 置0时不存在
{
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);
}
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],ON);
oled_show_month(time[1],ON);
oled_show_day(time[2],ON);
oled_show_hour(time[3],ON);
oled_show_minute(time[4],ON);
oled_show_second(time[5],ON);
oled_show_alarm_hour(alarm[0],ON);
oled_show_alarm_minute(alarm[1],ON);
oled_show_alarm_second(alarm[2],ON);
}
void oled_show_image(uint8_t x,uint8_t y,uint8_t width,uint8_t height,uint8_t *bmp) //其中参数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]);
}
}
rtc.c代码如下
#include "rtc.h"
#include "stdio.h"
#include "beep.h"
RTC_HandleTypeDef rtc_handle ={0};
void rtc_init(void)
{
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
rtc_handle.Instance =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)
{
__HAL_RCC_RTC_ENABLE();
RCC_OscInitTypeDef osc_initstruct = {0};
RCC_PeriphCLKInitTypeDef periphclk_initstruct ={0};
//配置LSE
osc_initstruct.OscillatorType =RCC_OSCILLATORTYPE_LSE; //设置的振荡器的类型 低速外部振荡器
osc_initstruct.LSEState = RCC_LSE_ON; //设置LSE为开启状态
osc_initstruct.PLL.PLLState =RCC_PLL_NONE; //关闭锁相环 即不能将低频时钟信号转换为高频时钟信号
HAL_RCC_OscConfig(&osc_initstruct);
//配置RTC时钟源
periphclk_initstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC; //配置外设时钟类型为RTC
periphclk_initstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; //使用外部低速晶振
HAL_RCCEx_PeriphCLKConfig(&periphclk_initstruct);
HAL_NVIC_SetPriority(RTC_Alarm_IRQn,2,2);
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}
void RTC_Alarm_IRQHandle(void) //中断服务函数
{
HAL_RTC_AlarmIRQHandler(&rtc_handle); //公共服务函数
}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
printf("ring ring ring ... \r\n");
beep_on();
}
uint16_t rtc_read_bkr(uint8_t bkrx) //其中参数bkrx为指定哪个寄存器
{
uint32_t data =0;
data = HAL_RTCEx_BKUPRead(&rtc_handle,bkrx);
return (uint16_t)data;
}
void rtc_write_bkr(uint8_t bkrx,uint16_t data) //其中参数bkrx为指定哪个寄存器 data为写什么东西
{
HAL_RTCEx_BKUPWrite(&rtc_handle,bkrx,data);
}
void rtc_get_time(uint8_t *time_data) //获取时间
{
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 );
}
void rtc_set_time(uint8_t *time_data) //设置时间
{
RTC_TimeTypeDef rtc_time ={0};
RTC_DateTypeDef rtc_date ={0};
rtc_time.Hours = time_data[3];
rtc_time.Minutes = time_data[4];
rtc_time.Seconds = time_data[5];
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];
HAL_RTC_SetDate(&rtc_handle,&rtc_date,RTC_FORMAT_BIN);
while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle,RTC_FLAG_RTOFF)); //对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行,可以通过查询RTC_CR寄存器中的RTOFF转台为,判断RTC寄存器是否处于更新中,仅当RTOFF状态位是1时,才可以写入RTC寄存器
}
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);
}