实时时钟项目设计
项目流程图
即核心代码为修改时间部分 :①当按下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 这是为了看最高位是否为1OLED_SDA_SET();elseOLED_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~0xFFoled_write_cmd(0xA8); //设置多路复用率oled_write_cmd(0x3F); //0x0E~0x3Foled_write_cmd(0xD3); //设置显示偏移oled_write_cmd(0x00); //0x00~0x7Foled_write_cmd(0x40); //设置显示开始行,0x40~0x7Foled_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~0xFFoled_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); //2025oled_show_chinese(40,0,0); //年oled_show_string(56,0,"00",16); //05oled_show_chinese(72,0,1); //月oled_show_string(88,0,"00",16); //17oled_show_chinese(104,0,2); //日oled_show_string(26,2,"00",16); //17oled_show_char(45,2,':',16); //:oled_show_string(56,2,"00",16); //30oled_show_char(75,2,':',16); //:oled_show_string(86,2,"00",16); //30oled_show_chinese(10,4,3); //闹oled_show_chinese(26,4,4); //钟oled_show_char(42,4,':',16); //:oled_show_string(26,6,"00",16); //17oled_show_char(45,6,':',16); //:oled_show_string(56,6,"00",16); //31oled_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);}elseoled_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);}elseoled_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);}elseoled_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);}elseoled_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);}elseoled_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);}elseoled_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);}elseoled_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);}elseoled_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};//配置LSEosc_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; //配置外设时钟类型为RTCperiphclk_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);}