当前位置: 首页 > news >正文

实时时钟项目设计

 项目流程图

即核心代码为修改时间部分 :①当按下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);}

相关文章:

  • 实习记录小程序|基于SSM+Vue的实习记录小程序设计与实现(源码+数据库+文档)
  • 【微信小程序 + 高德地图API 】键入关键字搜索地址,获取经纬度等
  • 【从基础到模型网络】深度学习-语义分割-基础
  • 【深度学习新浪潮】大模型在哪些垂域已经有比较好的落地?
  • OpenCV-去噪效果和评估指标方法
  • C++多线程数据错乱
  • 常见的请求头(Request Header)参数
  • SpringMVC-拦截器
  • 虚幻引擎5-Unreal Engine笔记之`GameMode`、`关卡(Level)` 和 `关卡蓝图(Level Blueprint)`的关系
  • 从0到1吃透卷积神经网络(CNN):原理与实战全解析
  • Linux安全第三章-系统安全及应用
  • vscode优化使用体验篇(快捷键)
  • 【Leetcode】取余/2的幂次方
  • elasticsearch kibana ik 各版本下载
  • Java API学习笔记
  • iOS APP启动页及广告页的实现
  • Python打卡DAY29
  • 架构设计模式:构建健壮、可扩展的 Serverless 应用
  • Seata源码—6.Seata AT模式的数据源代理一
  • buck变换器的simulink/matlab仿真和python参数设计
  • 曾毓群说未来三年重卡新能源渗透率将突破50%,宁德时代如何打好换电这张牌
  • 人民网:激发博物馆创新活力,让“过去”拥有“未来”
  • AI赋能科学红毯,机器人与科学家在虚实之间叩问“科学精神”
  • 俄媒:俄乌代表团抵达谈判会场
  • 证券时报:中美互降关税落地,订单集中补发港口将迎高峰期
  • 德国总理默茨发表首份政府声明:将提升国防能力,全力发展经济