STM32+EMWIN电子日历

之前花了几百元买的电子时钟坏了,就用闲置的板子做了一个。

功能是显示/调整日期,时间,多个闹钟,倒计时。倒计时使用实体脉冲旋钮控制。

基本软件架构是STM32F407+UCOSIII+STEMWIN+RTC。

效果如下:

STM32+EMWIN电子日历_第1张图片

STM32+EMWIN电子日历_第2张图片

STM32+EMWIN电子日历_第3张图片

STM32+EMWIN电子日历_第4张图片

这个是定时器,依靠旋钮调节定时时间

STM32+EMWIN电子日历_第5张图片

 

1.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();
}

2.脉冲旋钮

旋钮旋转时发出两路正交的脉冲信号,使用两个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;
}

3.背光PWM

因为要在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。

4.任务分配

三个工作任务:显示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
    }
}

5.闹钟

硬件闹钟只有一两个,这里使用软件自定义的闹钟组。

#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;
}

6.UI界面 

主界面底部有三大按钮:系统配置,时间设置,闹钟设置,分别呼出配置窗口,时间设置窗口,闹铃对话框。

主界面左半部分有三个文本小工具显示时间、日期和星期。右半部分显示定时器(倒计时)无边框窗口。

代码实在太多,不一一粘贴了。

源码在下面请下载:

https://download.csdn.net/download/wangzibigan/10765408

你可能感兴趣的:(stm32,GUI)