一般我们常用RTC外设,去显示一些日期,实时时间,当然如果用的是ESP32那就更简单了,连接到比如心知天气的API后,这直接可以获取到最新的时间。今天呢,我尝试用STM32去做这个时钟项目,突发奇想加入番茄工作法在我的单片机中(后续暂且叫做番茄钟)。我是做了USBHub和STM32的拼板,应用场景在桌面,插上电脑后,单片机的供电问题也就得到了解决,同时呢,使用了VBAT电源,所以时钟断电后依然在运行。接下来是展示环节,上图
实时时钟+闹钟
番茄钟
USBHUB(合体)
好好好 废话不多说,直接上代码
下面的是番茄钟的执行逻辑
void work1(void)
{
photo_flag =1;
if (Flag_Change == 3) {
OLED_ShowString(1, 1, "Pomodoro Timer");
OLED_ShowString(2, 1, "Working:");
OLED_ShowNum(2, 10, work_min, 2);
OLED_ShowString(3, 1, "Resting:");
OLED_ShowNum(3, 10, rest_min, 2);
OLED_ShowString(4, 1, "Cycle:");
OLED_ShowNum(4, 10, cycle_count, 2);
last_state = 0;
setting_mode = 1;
return;
}
if (setting_mode) {
// 处于设定模式,按键调整参数
switch (KeyNum) {
case 1:
// 按键一:进入计时模式
setting_mode = 0;
Pomodoro_State = 1;
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + work_min * 60);
OLED_Clear();
break;
case 2:
// 按键二:增加选中的参数
switch (selected_param) {
case 0: work_min = (work_min < 60) ? (work_min + 1) : 60; break;
case 1: rest_min = (rest_min < 30) ? (rest_min + 1) : 30; break;
case 2: cycle_count = (cycle_count < 99) ? (cycle_count + 1) : 99; break;
}
work2();
break;
case 3:
// 按键三:减少选中的参数
switch (selected_param) {
case 0: work_min = (work_min > 1) ? (work_min - 1) : 1; break;
case 1: rest_min = (rest_min > 1) ? (rest_min - 1) : 1; break;
case 2: cycle_count = (cycle_count > 1) ? (cycle_count - 1) : 1; break;
}
work2();
break;
case 4:
// 切换正在调整的参数 (0 -> 1 -> 2 -> 0)
selected_param = (selected_param + 1) % 3;
work2();
break;
default:KeyNum=4;break;
}
return;
}
// 运行中状态
if (Pomodoro_State != last_state) {
last_state = Pomodoro_State;
if(Pomodoro_State == 1)
{
OLED_ShowString(2, 1, " State: Working "); // 清除旧状态
}
else
{
OLED_ShowString(2, 1, " State: Resting ");
}
}
// 按键处理逻辑
if (KeyNum == 1) {
if (Pomodoro_State == 0) {
// **初次启动**
Pomodoro_State = 1; // 进入工作状态
cycle_counter = 0; // 从第 0 轮开始
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + work_min * 60);
} else {
// **手动停止**
Pomodoro_State = 0;
RTC_SetAlarm(0xFFFFFFFF);
}
}
else if (KeyNum == 4) {
// 返回设定界面
setting_mode = 1;
Pomodoro_State = 0;
OLED_Clear();
Msta = 0;
photo_flag = 0;
OLED_ShowString(3, 1, "Alarm:XX:XX:XX");
return;
}
// **计时逻辑**
if (Pomodoro_State != 0) {
uint32_t elapsed_time = RTC_GetCounter() - Pomodoro_StartTime;
uint32_t remain_time = 0;
if (Pomodoro_State == 1) {
// **工作时间倒计时**
remain_time = work_min * 60 - elapsed_time;
if (remain_time == 0) {
// **进入休息模式**
Pomodoro_State = 2;
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + rest_min * 60);
// **LED 闪烁 10 次**
for (int i = 0; i < 10; i++) {
LED_TURN();
Delay_ms(500); // 控制闪烁速度,200ms 只是示例
}
}
} else if (Pomodoro_State == 2) {
// **休息时间倒计时**
remain_time = rest_min * 60 - elapsed_time;
if (remain_time == 0) {
cycle_counter++; // **完成一次工作+休息**
if (cycle_counter < cycle_count) {
// **继续下一轮**
Pomodoro_State = 1;
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + work_min * 60);
} else {
// **全部循环完成,停止番茄钟**
Pomodoro_State = 0;
RTC_SetAlarm(0xFFFFFFFF);
}
}
}
// **显示倒计时**
OLED_ShowNum(3, 6, remain_time / 60, 2);
OLED_ShowString(3, 8, ":");
OLED_ShowNum(3, 10, remain_time % 60, 2);
}
}
这个是番茄钟的显示
void work2(void)
{
OLED_ShowString(1, 1, "Pomodoro Timer");
OLED_ShowString(2, 1, "Working:");
OLED_ShowNum(2, 10, work_min, 2);
OLED_ShowString(3, 1, "Resting:");
OLED_ShowNum(3, 10, rest_min, 2);
OLED_ShowString(4, 1, "Cycle:");
OLED_ShowNum(4, 10, cycle_count, 2);
OLED_ShowNum(2, 10, work_min, 2);
OLED_ShowNum(3, 10, rest_min, 2);
OLED_ShowNum(4, 10, cycle_count, 2);
}
一整个main函数程序。实时时钟的外设参考的是江科大和另一位B站UP(感谢感谢抱拳!!!)
#include "stm32f10x.h" // Device header
#include "Delay.h" //延时模块,主函数中未使用
#include "OLED.h" //OLED显示模块
#include "MyRTC.h" //RTC模块
#include "Key.h" //按键模块
#include "Buzzer.h" //蜂鸣器模块
#include <stdio.h>
#include <string.h>
/*
功能: 简易时钟 可以通过按键调整日期时间和设定闹钟,通过OLED显示日期时间等相关信息
番茄钟 可以使用番茄工作法高效学习
原理: RTC
接线: OLED显示屏:SCK接PB8,SDA接PB9
有源蜂鸣器(高电平触发):I/O接PB14
红色指示灯(高电平触发):I/O接PB14
1~4号独立按键:分别接PB11,PB10,PB1,PB0
*/
void work0(void);
void work1(void);
void work2(void);
void work3(void);
uint16_t MyRTC_Time[] = {2025,3,24,13,47,50}; //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒
uint32_t Alarm_CNT,Alarm_Time,Alarm_Time_Rest; //闹钟相关变量,单位都是秒
uint8_t Hour,Min,Sec; //用来调整闹钟时间的变量
uint8_t KeyNum; //按键键码值
uint8_t Flag_Count; //是否在计时标志,0为不在计时
uint8_t Flag_Change; //按键调节闹钟/日期/时间,0为调节闹钟,1为调节日期,2为调节时间
//work工作标志位
u8 Msta=0;
uint8_t Pomodoro_State = 0; // 0:停止 1:工作中 2:休息中
uint32_t Pomodoro_StartTime = 0;
uint8_t Pomodoro_Cycle = 0; // 完成的番茄周期数
const uint16_t WORK_MIN = 25; // 工作时间25分钟
const uint16_t REST_MIN = 5; // 休息时间5分钟
uint8_t photo_flag = 0;
uint8_t last_state = 0;
uint8_t setting_mode = 1; // 是否处于设定模式,1 代表设置界面
uint8_t work_min = 25; // 工作时间(默认 25 分钟)
uint8_t rest_min = 5; // 休息时间(默认 5 分钟)
uint8_t cycle_count = 0; // 当前循环次数
/*按键控制函数*/
void Key_Control(void)
{
KeyNum = Key_GetNum(); //读取按键键码
if(Flag_Change == 0&&photo_flag ==0) //调节闹钟
{
if(KeyNum == 1) //1号按键调整小时
{
Hour++;
if(Hour > 60)
Hour = 0;
}
else if(KeyNum == 2) //2号按键调整分钟
{
Min++;
if(Min > 60)
Min = 0;
}
else if(KeyNum == 3) //3号按键调整秒
{
Sec++;
if(Sec > 60)
Sec = 0;
}
else if(KeyNum == 4) //4号按键
{
if(Buzzer_State()==0) //若蜂鸣器没响
{
Alarm_Time = Hour*3600 + Min*60 + Sec; //计算闹钟时长,单位是秒
if(Alarm_Time > 0)
{
Alarm_CNT = RTC_GetCounter()+Alarm_Time-1; //设定闹钟值,需要-1
RTC_SetAlarm(Alarm_CNT); //写入闹钟值到RTC的ALR寄存器
Flag_Count = 1;
}
else //若闹钟时长为0,则转到按键调节日期
{
Flag_Change = 1;
}
}
else //若蜂鸣器响
{
Buzzer_OFF(); //关闭蜂鸣器
OLED_ShowString(4,1," "); //刷新oled第四行
}
}
}
else if(Flag_Change == 1) //调节日期
{
if(KeyNum == 1) //1号按键调整年
{
MyRTC_Time[0]++;
MyRTC_SetTime();
}
else if(KeyNum == 2) //2号按键调整月
{
MyRTC_Time[1]++;
MyRTC_SetTime();
}
else if(KeyNum == 3) //3号按键调整日
{
MyRTC_Time[2]++;
MyRTC_SetTime();
}
else if(KeyNum == 4) //4号按键,改为调整时间
{
Flag_Change = 2;
}
}
else if(Flag_Change == 2) //调节时间
{
if(KeyNum == 1) //1号按键调整小时
{
MyRTC_Time[3]++;
MyRTC_SetTime();
}
else if(KeyNum == 2) //2号按键调整分钟
{
MyRTC_Time[4]++;
MyRTC_SetTime();
}
else if(KeyNum == 3) //3号按键调整秒
{
MyRTC_Time[5]++;
MyRTC_SetTime();
}
else if(KeyNum == 4) //4号按键
{
Flag_Change = 3; //改为调整闹钟
}
}
else if(Flag_Change == 3) //调节时间
{
if(KeyNum == 1)
{
Msta = 1;
OLED_Clear();
}
else if(KeyNum == 4) //4号按键
{
Flag_Change = 0; //改为调整闹钟
OLED_ShowString(4,1," "); //刷新oled第四行
}
}
}
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
MyRTC_Init(); //RTC初始化
Key_Init(); //按键初始化
Buzzer_Init(); //蜂鸣器初始化
OLED_ShowString(3, 1, "Alarm:XX:XX:XX");
Msta=0;
while (1)
{
MyRTC_ReadTime(); //RTC读取时间,最新的时间存储到MyRTC_Time数组中
Key_Control(); //调用按键控制函数
switch(Msta)
{
case 0: work0();//
break;
case 1: work1();//
break;
case 2: work2();//
break;
case 3: work3();//
break;
default: Msta=0;break;
}//switch
}
}
void work0(void)
{
char buf1[30];
OLED_ShowString(1, 1, "Date:");
OLED_ShowString(2, 1, "Time:");
OLED_ShowString(3, 1, "Alarm:");
sprintf(buf1,"Date:%04d-%02d-%02d",MyRTC_Time[0],MyRTC_Time[1],MyRTC_Time[2]);
OLED_ShowString(1,1,buf1);
sprintf(buf1,"Time:%02d:%02d:%02d",MyRTC_Time[3],MyRTC_Time[4],MyRTC_Time[5]);
OLED_ShowString(2,1,buf1);
if(Flag_Count) //正在计时,则显示闹钟响起剩余时间
{
Alarm_Time_Rest = Alarm_CNT-RTC_GetCounter()+1; //计算闹钟响起剩余时间
if(Alarm_Time_Rest > Alarm_Time) //防止溢出错误
Alarm_Time_Rest = 0;
OLED_ShowNum(3,7,Alarm_Time_Rest/3600,2); //显示剩余小时
OLED_ShowNum(3,10,(Alarm_Time_Rest%3600)/60,2); //显示剩余分钟
OLED_ShowNum(3,13,(Alarm_Time_Rest%3600)%60,2); //显示剩余秒
sprintf(buf1,"Alarm:%02d:%02d:%02d",(Alarm_Time_Rest/3600),(Alarm_Time_Rest%3600)/60,(Alarm_Time_Rest%3600)%60);
OLED_ShowString(3, 1,buf1 );
if(RTC_GetFlagStatus(RTC_FLAG_ALR) == 1) //闹钟时间到,检查标志位为1
{
RTC_ClearFlag(RTC_FLAG_ALR); //清除标志位
Flag_Count = 0;Alarm_Time = 0; //重置相关参数
Hour = 0;Min = 0; Sec = 0;
Buzzer_ON(); //打开蜂鸣器
OLED_ShowString(4,1,"Time Out");
}
else //闹钟时间未到
{
OLED_ShowString(4,1,"Counting"); //显示正在计时
}
}
else //不在计时,则显示需要设定的闹钟时间
{
OLED_ShowNum(3,7,Hour,2);
OLED_ShowNum(3,10,Min,2);
OLED_ShowNum(3,13,Sec,2);
}
if(Flag_Change == 1) //显示“调节日期”
{
OLED_ShowString(4,1,"Change Date");
}
else if(Flag_Change == 2) //显示“调节时间”
{
OLED_ShowString(4,1,"Change Time");
}
else if(Flag_Change == 3) //显示“番茄时钟”
{
OLED_ShowString(4,1,"Pomodoro Time");
}
}
uint8_t selected_param = 0; // 0: 工作时间, 1: 休息时间, 2: 循环次数
// 变量定义
uint8_t cycle_counter = 0; // 记录当前是第几轮循环
void work1(void)
{
photo_flag =1;
if (Flag_Change == 3) {
OLED_ShowString(1, 1, "Pomodoro Timer");
OLED_ShowString(2, 1, "Working:");
OLED_ShowNum(2, 10, work_min, 2);
OLED_ShowString(3, 1, "Resting:");
OLED_ShowNum(3, 10, rest_min, 2);
OLED_ShowString(4, 1, "Cycle:");
OLED_ShowNum(4, 10, cycle_count, 2);
last_state = 0;
setting_mode = 1;
return;
}
if (setting_mode) {
// 处于设定模式,按键调整参数
switch (KeyNum) {
case 1:
// 按键一:进入计时模式
setting_mode = 0;
Pomodoro_State = 1;
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + work_min * 60);
OLED_Clear();
break;
case 2:
// 按键二:增加选中的参数
switch (selected_param) {
case 0: work_min = (work_min < 60) ? (work_min + 1) : 60; break;
case 1: rest_min = (rest_min < 30) ? (rest_min + 1) : 30; break;
case 2: cycle_count = (cycle_count < 99) ? (cycle_count + 1) : 99; break;
}
work2();
break;
case 3:
// 按键三:减少选中的参数
switch (selected_param) {
case 0: work_min = (work_min > 1) ? (work_min - 1) : 1; break;
case 1: rest_min = (rest_min > 1) ? (rest_min - 1) : 1; break;
case 2: cycle_count = (cycle_count > 1) ? (cycle_count - 1) : 1; break;
}
work2();
break;
case 4:
// 切换正在调整的参数 (0 -> 1 -> 2 -> 0)
selected_param = (selected_param + 1) % 3;
work2();
break;
default:KeyNum=4;break;
}
return;
}
// 运行中状态
if (Pomodoro_State != last_state) {
last_state = Pomodoro_State;
if(Pomodoro_State == 1)
{
OLED_ShowString(2, 1, " State: Working "); // 清除旧状态
}
else
{
OLED_ShowString(2, 1, " State: Resting ");
}
}
// 按键处理逻辑
if (KeyNum == 1) {
if (Pomodoro_State == 0) {
// **初次启动**
Pomodoro_State = 1; // 进入工作状态
cycle_counter = 0; // 从第 0 轮开始
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + work_min * 60);
} else {
// **手动停止**
Pomodoro_State = 0;
RTC_SetAlarm(0xFFFFFFFF);
}
}
else if (KeyNum == 4) {
// 返回设定界面
setting_mode = 1;
Pomodoro_State = 0;
OLED_Clear();
Msta = 0;
photo_flag = 0;
OLED_ShowString(3, 1, "Alarm:XX:XX:XX");
return;
}
// **计时逻辑**
if (Pomodoro_State != 0) {
uint32_t elapsed_time = RTC_GetCounter() - Pomodoro_StartTime;
uint32_t remain_time = 0;
if (Pomodoro_State == 1) {
// **工作时间倒计时**
remain_time = work_min * 60 - elapsed_time;
if (remain_time == 0) {
// **进入休息模式**
Pomodoro_State = 2;
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + rest_min * 60);
// **LED 闪烁 10 次**
for (int i = 0; i < 10; i++) {
LED_TURN();
Delay_ms(500); // 控制闪烁速度,200ms 只是示例
}
}
} else if (Pomodoro_State == 2) {
// **休息时间倒计时**
remain_time = rest_min * 60 - elapsed_time;
if (remain_time == 0) {
cycle_counter++; // **完成一次工作+休息**
if (cycle_counter < cycle_count) {
// **继续下一轮**
Pomodoro_State = 1;
Pomodoro_StartTime = RTC_GetCounter();
RTC_SetAlarm(Pomodoro_StartTime + work_min * 60);
} else {
// **全部循环完成,停止番茄钟**
Pomodoro_State = 0;
RTC_SetAlarm(0xFFFFFFFF);
}
}
}
// **显示倒计时**
OLED_ShowNum(3, 6, remain_time / 60, 2);
OLED_ShowString(3, 8, ":");
OLED_ShowNum(3, 10, remain_time % 60, 2);
}
}
void work2(void)
{
OLED_ShowString(1, 1, "Pomodoro Timer");
OLED_ShowString(2, 1, "Working:");
OLED_ShowNum(2, 10, work_min, 2);
OLED_ShowString(3, 1, "Resting:");
OLED_ShowNum(3, 10, rest_min, 2);
OLED_ShowString(4, 1, "Cycle:");
OLED_ShowNum(4, 10, cycle_count, 2);
OLED_ShowNum(2, 10, work_min, 2);
OLED_ShowNum(3, 10, rest_min, 2);
OLED_ShowNum(4, 10, cycle_count, 2);
}
void work3(void)
{
}