之前花了几百元买的电子时钟坏了,就用闲置的板子做了一个。
功能是显示/调整日期,时间,多个闹钟,倒计时。倒计时使用实体脉冲旋钮控制。
基本软件架构是STM32F407+UCOSIII+STEMWIN+RTC。
效果如下:
这个是定时器,依靠旋钮调节定时时间
参照原子历程初始化,并且定义一个新的日期时间结构这是为了一次性把相关消息发送到UI,注意要使能后备寄存器。用于保存闹铃数据。这样关机后再次开机依然能保持原来的闹铃。
/**
* @brief RTC Date&Time structure definition
*/
typedef struct
{
RTC_TimeTypeDef Time;
RTC_DateTypeDef Date;
}RTC_DTTypeDef;
时间显示是需要每秒进行更新,所以需要一个周期性的秒中断
void xRTC_Set_WakeUp(u32 wksel,u16 cnt)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RTC_WakeUpCmd(DISABLE);//关闭WAKE UP
RTC_WakeUpClockConfig(wksel);//唤醒时钟选择
RTC_SetWakeUpCounter(cnt);//设置WAKE UP自动重装载寄存器
RTC_ClearITPendingBit(RTC_IT_WUT); //清除RTC WAKE UP的标志
EXTI_ClearITPendingBit(EXTI_Line22);//清除LINE22上的中断标志位
RTC_ITConfig(RTC_IT_WUT,ENABLE);//开启WAKE UP 定时器中断
RTC_WakeUpCmd( ENABLE);//开启WAKE UP 定时器
EXTI_InitStructure.EXTI_Line = EXTI_Line22;//LINE22 (RTC Wakeup)
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断事件
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能LINE22
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = RTC_WKUP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
}
//RTC WAKE UP中断服务函数
RTC_DTTypeDef RTC_DT;
static WM_MESSAGE RTC_Msg = {0};
void RTC_WKUP_IRQHandler(void)
{
OSIntEnter();
if(RTC_GetFlagStatus(RTC_FLAG_WUTF)==SET)//WK_UP中断?
{
RTC_ClearFlag(RTC_FLAG_WUTF); //清除中断标志
LED1=!LED1;
RTC_GetTime(RTC_Format_BIN,&RTC_DT.Time);
RTC_GetDate(RTC_Format_BIN, &RTC_DT.Date);
RTC_Msg.MsgId=UPDATA_MSG;
RTC_Msg.Data.p=&RTC_DT;
WM_SendMessage(WM_HBKWIN, &RTC_Msg);
}
EXTI_ClearITPendingBit(EXTI_Line22);//清除中断线22(RTC Wakeup event)的中断标志
OSIntExit();
}
旋钮旋转时发出两路正交的脉冲信号,使用两个GPIO,配置其中一个为外部中断模式
/*
初始化为输入脚,中断脚
*/
void xRotaryKey_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOF,ENABLE); //使能GPIOB/F时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN; //输入模式
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL; //浮空,具体看外部硬件
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin= GPIO_Pin_6;
GPIO_Init(GPIOF,&GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource10);
/* 配置EXTI_Line10 */
EXTI_InitStructure.EXTI_Line = EXTI_Line10 ;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断事件
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//中断线使能
EXTI_Init(&EXTI_InitStructure);//配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//外部中断10,11
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置
}
中断处理旋钮脉冲
/*
外部中断,处理旋钮脉冲
*/
static WM_MESSAGE keyMsg = {0};
void EXTI15_10_IRQHandler(void)
{
OSIntEnter();
if( RESET != EXTI_GetITStatus(EXTI_Line10) )
{
EXTI_ClearITPendingBit(EXTI_Line10);//清除LINE10上的中断标志位
if(PBin(11)==PBin(10))//通过两个管脚电平判断方向
{
keyMsg.Data.v=1;
}else
{
keyMsg.Data.v=-1;
}
keyMsg.MsgId=UPDATA_KNOB_MSG;
WM_SendMessage(WM_HBKWIN, &keyMsg);
}
OSIntExit();
}
旋钮本身自带按键,做按键单击双击长按处理
#define SINGLE_CLICK 1
#define DOUBLE_CLICK 2
#define LONG_PRESS 3
#define NULL_CLICK 0
/*
按键扫描,旋转按钮有按键
20ms间隔检测
return: =SINGLE_CLICK单击 =DOUBLE_CLICK双击 =LONG_PRESS长按 =NULL_CLICK无
*/
#define LONGPRESS_TIMES 35 //长按时间 50 -> 1S
#define _key_scan_PIN PFin(6)
u8 xRotaryKey_Poll(void)
{
static u8 step=0,times=0;
switch(step)
{
case 0:
if ( _key_scan_PIN == 1 ){ step=1; }
break;
case 1://按下
if ( _key_scan_PIN == 0 ){ step=2; }
break;
case 2://
times++;
if( _key_scan_PIN == 0 )
{
if( times > LONGPRESS_TIMES )
{
times=step=0;return LONG_PRESS;
}//长按
}
else //松开
{
if(times<10) { step=3;} //进入双击判断, times不清零
else{ times=0;step=1; return SINGLE_CLICK; } // 为单击1 时间为210ms
}
break;
case 3: //已松开
times++;
if ( _key_scan_PIN == 0 )
{
step=4; //
}
else //松开超时
{
if(times>13){step=1;times=0; return SINGLE_CLICK; } // 为单击2 270ms时间
}
break;
case 4://已按下
times++;
if ( _key_scan_PIN == 0 )
{
if(times>LONGPRESS_TIMES+20)
{ times=step=0;return LONG_PRESS; }//长按
}
else
{
step=1;
if(times<50){ times=0;return DOUBLE_CLICK; } //双击成功
}
break;
}
return NULL_CLICK;
}
因为要在UI中做背光调节功能,所以还要加上TIM的PWM功能
背光LED控制IO为PB15,对应定时器TIM12-CH2。
/*
说明:PWM初始化 pb15 ,TIM12-ch2
arr:自动重装值
psc:时钟预分频数
*/
void xTIM12_PWM_Init(u32 arr,u32 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM12,ENABLE); //TIM12时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); //使能PORTB时钟
GPIO_PinAFConfig(GPIOB,GPIO_PinSource15,GPIO_AF_TIM12); //GPIOB15复用为定时器12
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; //GPIOB15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化PB15
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM12,&TIM_TimeBaseStructure);//初始化定时器12
//初始化TIM12 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低
TIM_OC2Init(TIM12, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM12 OC2
TIM_OC2PreloadConfig(TIM12, TIM_OCPreload_Enable); //使能TIM12在CCR2上的预装载寄存器
TIM_ARRPreloadConfig(TIM12,ENABLE);//ARPE使能
TIM_Cmd(TIM12, ENABLE); //使能TIM
}
在LCD初始化函数末尾加上
xTIM12_PWM_Init(50-1,840-1);
TIM_SetCompare2(TIM12,50-1);
这样,通过库函数TIM_SetCompare2(TIM12,num)可调节背光。num范围为0~49。
三个工作任务:显示UI任务,触摸任务,脉冲旋钮扫描任务。显示任务比较耗时,优先级要低。
//RotaryKnob任务
//设置任务优先级
#define KNOB_TASK_PRIO 4
//任务堆栈大小
#define KNOB_STK_SIZE 512
//任务控制块
OS_TCB KNOBTaskTCB;
//任务堆栈
CPU_STK KNOB_TASK_STK[KNOB_STK_SIZE];
//led0任务
void RotaryKnob_task(void *p_arg);
//TOUCH任务
//设置任务优先级
#define TOUCH_TASK_PRIO 5
//任务堆栈大小
#define TOUCH_STK_SIZE 128
//任务控制块
OS_TCB TouchTaskTCB;
//任务堆栈
CPU_STK TOUCH_TASK_STK[TOUCH_STK_SIZE];
//touch任务
void touch_task(void *p_arg);
//EMWINDEMO任务
//设置任务优先级
#define EMWINDEMO_TASK_PRIO 6
//任务堆栈大小
#define EMWINDEMO_STK_SIZE 1024
//任务控制块
OS_TCB EmwindemoTaskTCB;
//任务堆栈
CPU_STK EMWINDEMO_TASK_STK[EMWINDEMO_STK_SIZE];
//emwindemo_task任务
void emwindemo_task(void *p_arg);
三个任务函数
//EMWINDEMO任务
void emwindemo_task(void *p_arg)
{
//更换皮肤
BUTTON_SetDefaultSkin(BUTTON_SKIN_FLEX);
CHECKBOX_SetDefaultSkin(CHECKBOX_SKIN_FLEX);
DROPDOWN_SetDefaultSkin(DROPDOWN_SKIN_FLEX);
FRAMEWIN_SetDefaultSkin(FRAMEWIN_SKIN_FLEX);
HEADER_SetDefaultSkin(HEADER_SKIN_FLEX);
MENU_SetDefaultSkin(MENU_SKIN_FLEX);
MULTIPAGE_SetDefaultSkin(MULTIPAGE_SKIN_FLEX);
PROGBAR_SetDefaultSkin(PROGBAR_SKIN_FLEX);
RADIO_SetDefaultSkin(RADIO_SKIN_FLEX);
SCROLLBAR_SetDefaultSkin(SCROLLBAR_SKIN_FLEX);
SLIDER_SetDefaultSkin(SLIDER_SKIN_FLEX);
SPINBOX_SetDefaultSkin(SPINBOX_SKIN_FLEX);
xRTC_Init(); //初始化RTC
xRTC_Set_WakeUp(RTC_WakeUpClock_CK_SPRE_16bits,0);//配置WAKE UP中断,1秒钟中断一次
MainTask();//UI处理主函数
while(1)
{
}
}
//触摸任务
void touch_task(void *p_arg)
{
OS_ERR err;
while(1)
{
GUI_TOUCH_Exec();
OSTimeDlyHMSM(0,0,0,5,OS_OPT_TIME_PERIODIC,&err);//频率5ms
}
}
//旋钮任务
void RotaryKnob_task(void *p_arg)
{
OS_ERR err;
u8 keyValue=0;
WM_MESSAGE Msg = {0,0,0,0};
while(1)
{
keyValue = xRotaryKey_Poll();
if( SINGLE_CLICK == keyValue )
{
//printf("\r\n singkey \r\n" );
Msg.MsgId=UPDATA_KEY_MSG;
Msg.Data.v=!Msg.Data.v;
WM_SendMessage(WM_HBKWIN, &Msg);
}else
if( DOUBLE_CLICK == keyValue )
{
//printf("\r\n doublekey \r\n" );
}
OSTimeDlyHMSM(0,0,0,20,OS_OPT_TIME_PERIODIC,&err);//延时20ms
}
}
硬件闹钟只有一两个,这里使用软件自定义的闹钟组。
#define ALARM_NUM_MAX 5
//闹钟
#pragma pack (1)
typedef struct
{
unsigned char en; //是否启用,最低位表示,1启用。高4位表示type, 类型:0单次, 1工作日, 2每天
unsigned char hour; //时针
unsigned char minute; //分针
unsigned char duration; //铃声时长(秒)
//unsigned char type; //类型:0单次, 1工作日, 2每天
}
xAlarm_TypeDef;
#pragma pack ()
//闹钟组
typedef struct
{
unsigned char num; //总数
unsigned char availableNum;//有效闹钟数目
union {
xAlarm_TypeDef _alarm[ALARM_NUM_MAX];//最大5个闹钟
u32 _alarmx[ALARM_NUM_MAX] ;
} alarm;
unsigned char remainingTime;//运行状况:各闹钟剩余响铃时间中,取最大者
}
xAlarms_TypeDef;
闹钟保存在后备寄存器里
/*
存储闹铃组到后备寄存器
*/
void StoreAlarmsToBkp(const xAlarms_TypeDef* pAl)
{
char i;
RTC_WriteBackupRegister(RTC_BKP_DR2,pAl->num);
RTC_WriteBackupRegister(RTC_BKP_DR3,pAl->availableNum);
for(i=0;inum;i++)
{
RTC_WriteBackupRegister(RTC_BKP_DR4+i,pAl->alarm._alarmx[i] );
}
}
/*
从后备寄存器加载到闹铃组
*/
void LoadAlarmsFromBkp(xAlarms_TypeDef* pAl)
{
char i;
if(RTC_ReadBackupRegister(RTC_BKP_DR0)==RTCBKP_KEYVALUE)
{
pAl->num= RTC_ReadBackupRegister(RTC_BKP_DR2);
if(pAl->num==0)return;
pAl->availableNum= RTC_ReadBackupRegister(RTC_BKP_DR3);
for(i=0;inum;i++)
{
pAl->alarm._alarmx[i] = RTC_ReadBackupRegister(RTC_BKP_DR4+i);
}
}
}
//判断闹钟是否响铃了,返回响铃持续时间(秒),=0代表没有
unsigned char CheakAlarm( xAlarms_TypeDef* pAl,const RTC_TimeTypeDef *pTime,unsigned char weekday)
{
unsigned char dur=0,maxdur=0;
unsigned char num = pAl->num;
for(;num!=0;num--)
{
if( pAl->alarm._alarm[num-1].en&0x01 &&
pAl->alarm._alarm[num-1].minute == pTime->RTC_Minutes &&
pAl->alarm._alarm[num-1].hour == pTime->RTC_Hours
)//判断
{
switch( pAl->alarm._alarm[num-1].en>>4 )
{
case 0://单次
dur = pAl->alarm._alarm[num-1].duration;
(pAl->alarm._alarm[num-1].en) -=1;//&=0xfe //更改闹铃,使失效
pAl->availableNum--;
StoreAlarmsToBkp(pAl);//储存到备份寄存器
break;
case 1://工作日
if(weekday<=5)dur = pAl->alarm._alarm[num-1].duration;
break;
case 2://每天w
dur = pAl->alarm._alarm[num-1].duration;
break;
}
if( maxdur < dur) maxdur =dur; //取最大的
}
}
if( maxdur > pAl->remainingTime )
pAl->remainingTime = maxdur;//剩余响铃时间
return maxdur;
}
主界面底部有三大按钮:系统配置,时间设置,闹钟设置,分别呼出配置窗口,时间设置窗口,闹铃对话框。
主界面左半部分有三个文本小工具显示时间、日期和星期。右半部分显示定时器(倒计时)无边框窗口。
代码实在太多,不一一粘贴了。
源码在下面请下载:
https://download.csdn.net/download/wangzibigan/10765408