10. 实时钟系统设计

10. 实时钟系统设计

  10.1 系统结构

  10.2 程序设计

    10.2.1 实时钟程序设计

    10.2.2 按键和显示程序设计

    10.2.3 系统程序设计

  10.3 程序实现

 

  10.1 系统结构

  • 实时钟系统包括STM32MCU、实时钟电路DS1302、2个按键、4个LED、LED显示器和UART-USB转换器CP2102

10. 实时钟系统设计

  • 实时钟电路提供实时钟计时,包括年、月、日、星期、时、分和秒等
  • 2个按键用于切换LCD显示和设置实时钟
  • 4个LED指示LCD显示的内容
  • LCD分别显示年、月、星期、日、时、分和秒等
  • UART-USB转换器CP2102通过USB实现与计算机的串行通信
  • DS1302是美国DALLAS公司推出的一种高性能、低功耗、带RAM的实时时钟电路,可以对年、月、星期、日、时、分和秒进行计时,具有闰年补偿功能
  • DS1302采用三线接口与CPU进行同步通信,可采用突发方式一次传送多个字节的时钟或RAM数据
  • DS1302的工作电压为2.5-5.5V,8引脚封装

10. 实时钟系统设计

  • DS1302控制字节

10. 实时钟系统设计

  • DS1302读写时序

10. 实时钟系统设计

  • DS1302时钟寄存器

10. 实时钟系统设计

 

  10.2 程序设计

    10.2.1 实时钟程序设计

  • 实时钟程序设计根据DS1302读写时序进行编写。DS1302读写时序包括2个基本操作:写时序包括2个写操作(写地址和写数据),读时序包括1个写操作(写地址)和1个读操作(读数据)因此,首先编写2个基本操作的子程序
  • DS1302写子程序
//DS1302写子程序
//入口参数:data-写数据

void Ds1302_Write(char data)
{
    char m;

    for(m=0; m<8; m++)
    {
        if(data & 1<<m)
            GPIOB->BSRR = 1<<7;     //PB.07(I/O)=1
        else
            GPIOB->BRR = 1<<7;      //PB.07(I/O)=0

        GPIOB->BSRR = 1<<6;         //PB.06(SCLK)=1
        GPIOB->BRR = 1<<6;          //PB.06(SCLK)=0
    }
}
  • DS1302读子程序
//DS1302读子程序
//出口参数:读数据

char DS1302_Read(void)
{
    char m,data=0;

    for(m=0;m<8;m++)
    {
        data |= (GPIOB->IDR>>7&1)<<m; //PB.07(I/O)

        GPIOB->BSRR = 1<<6;           //PB.06(SCLK)=1

        GPIOB->BRR = 1<<6;            //PB.06(SCLK)=0
    }

    return data;
}
  • DS1302写数据子程序
//DS1302写数据子程序
//入口参数:addr-地址,data-数据

void Ds1302_Write_Data(char addr,char data)
{

    RCC->APB2ENR |= 1<<3;             //开启GPIOB时钟

    GPIOB->CRL &= 0x000fffff;

    GPIOB->CRL |= 0x33300000;         //PB.07-PB.05通用推挽输出

    GPIOB->BSRR = 1<<5;               //PB.05(RST)=1

    addr = 0x80+(addr<<1);            //写数据

    Ds1302_Write(addr);

    Ds1302_Write(data);

    GPIOB->BRR = 1<<5;                //PB.05(RST)=0
}
  • DS1302读数据子程序
//DS1302读数据子程序
//入口参数:addr-地址
//出口参数:数据

char Ds1302_Read_data(char addr)
{
    char data=0;

    RCC->APB2ENR |= 1<<3;             //开启GPIOB时钟

    GPIOB->CRL &= 0x000fffff;

    GPIOB->CRL |= 0x33300000;         //PB.07-PB.05通用推挽输出

    GPIOB->BSRR = 1<<5;               //PB.05(RST)=1

    addr = 0x80+(addr<<1)+1;          //读数据

    Ds1302_Write(addr);

    GPIOB->CRL &= 0x0fffffff;

    GPIOB->CRL |= 0x40000000;         //PB.07(I/O)浮空输入

    data = Ds1302_Read();

    GPIOB->BRR 1<<5;                  //PB.05(RST)=0

    return data;
}

    10.2.2 按键和显示程序设计

  • 按键操作和LCD显示状态图

10. 实时钟系统设计

  • 系统共有8个状态:4个显示状态(分别显示年、月日、时分和分秒)和4个设置状态(分别设置月、日、时和分)
  • 状态间用切换键(KEY1)和设置键(KEY2)转换:切换键用于切换显示和设置状态以及退出设置状态,设置键用于进入设置状态和设置月、日、时和分等
  • 4个显示状态和4个设置状态同时分别用4个LED进行指示

    1、按键程序设计(按键采用中断方式工作)

  • 按键接口初始化子程序
//按键接口初始化子程序

void Key_Init(void)
{
    RCC->APB2ENR |= 1<<3;             //开启按键接口(GPIOB)时钟

    RCC->APB2ENR |= 1;                //开启AFIO时钟

    AFIO->EXTICR[2] |= 0x0011;        //EXTI9=PB.9,EXTI8=PB.8

    EXTI->FTSR |= 0x0300;             //EXTI9和EXTI8下降沿触发

    EXTI->IMR |= 0x0300;              //允许EXTI9和EXTI8中断

    NVIC->IPR[5] |= 0x80000000;       //EXTI9-8中断抢占优先级1

    NVIC->ISER[0] |= 1<<23;           //允许EXTI9-8中断
}
  • 按键接口处理子程序
//按键中断处理子程序
void EXTI9_5_IRQHandler(void)
{
    Delay(10);                             //延时10ms消抖动

    if((EXTI->PR&1<<8)&&(~GPIOB->IDR&1<<8))//KEY1按下(PR.8=1,IDR.8=0)
    {
        if(flag<4)                         //显示状态
        {
            if(flag<4)                     //显示状态切换
                flag=0;
        }
        else //设置状态
        {
            if(++flag == 8)                //设置状态切换
            {
                flag = 2;                  //退出设置状态

                GPIOB->BRR = 0xf000;       //亮所有LED

                Delay(200);                //延时200ms

                Ds1302_Write_Data(4,month);//写数据到DS1302

                Ds1302_Write_Data(3,date);

                Ds1302_Write_Data(2,hour);

                Ds1302_Write_Data(1,min);

                Ds1302_Write_Data(0,0);
            }
        }
    }

    if((EXTI->PR & 1<<9) && (~GPIOB->IDR & 1<<9)) //KEY2按下(PR.9=1,IDR.9=0)
    {
        if(flag<4)

        flag = 4;                         //进入设置状态

        if(flag == 4)                     //设置月
        {
            month = Bin_Bcd(++month);     //2-10进制调整

            if(month > 0x12)
                month = 1;
        }

        if(flag == 5)                     //设置日
        {
            date = Bin_Bcd(++date);       //2-10进制调整

            if(date>0x31)
                date = 1;
        }

        if(flag == 6)                     //设置时
        {
            hour = Bin_Bcd(++hour);       //2-10 进制调整

            if(hour>0x24)
                hour = 0;
        }

        if(flag == 7)                     //设置分
        {
            min = Bin_Bcd(++min);         //2-10进制调整

            if(min>0x60)
                min = 0;
        }
    }
    EXTI->PR |= 0x0300;                   //清除中断触发请求
}
  • 延时子程序Delay()和2-10进制调整子程序Bin_Bcd()
//延时子程序
void Delay(char ms)
{
    msec = 0;

    while(msec < ms);
}

//2-10进制调整子程序
char Bin_Bcd(char data)
{
    if((data & 0xf) >9)
        data += 6;

    return data;
}
  • 延时子程序中的msec计时用SysTick中断实现,相应的SysTick初始化子程序和SysTick中断处理子程序
//SysTick初始化子程序
void SysTick_Init(void)
{
    SysTick->LOAD = 1E3;     //设置1ms重装值(时钟频率为8MHz/8)

    SysTick->CTRL = 2;       //计数到0时中断

    SysTick->CTRL = 1;       //启动定时器
}

//SysTick中断处理子程序
void SysTick_Handler(void)
{
    msec += 1;               //毫秒计时
}
  • 由于SysTick中断嵌套在按键中断中,而STM32的中断嵌套是通过抢占优先级实现。因此,为了保证程序正常工作,必须用下列语句设置抢占优先级
SCB->AIRCR = 0x05FA0600;         //1位抢占优先级,3位响应优先级

NVIC->IPR[5] |= 0x80000000;      //EXTI9-8中断抢占优先级1
  • SysTick的抢占优先级默认为0,比EXTI9-8抢占优先级高可以在按键中断中嵌套SysTick中断,实现延时功能

    2、显示程序设计(包括LED状态指示和LCD数据显示)

  • 显示处理子程序
//显示处理子程序
void Disp_Proc(void)
{
    switch(flag)
    {
        case 0:                         //显示年
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x8000;        //亮LED4

            Lcd_Proc(0x20,year,0);      //显示年

            break;
        }

        case 1:                         //显示月日
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x4000;        //亮LED3

            Lcd_Proc(month,date,2);     //显示月,日

            break;
        }

        case 2:                         //显示时分
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x2000;        //亮LED2

            Lcd_Proc(hour,min,8);       //显示时:分

            Delay(200);

            Lcd_Proc(hour,min,0);       //冒号(:)闪烁

            break;
        }

        case 3:                         //显示分秒
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x1000;        //亮LED1

            Lcd_Proc(min,sec,0);        //显示分秒

            break;
        }

        case 4:                         //设置月
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x8000;        //亮LED4

            Lcd_Proc(0xff,date,2);      //月闪烁

            Delay(200);

            Lcd_Proc(month,date,2);

            break;
        }

        case 5:                         //设置日
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x4000;        //亮LED3

            Lcd_Proc(month,0xff,2);     //日闪烁

            Delay(200);

            Lcd_Proc(month,date,2);

            break;
        }

        case 6:                         //设置时
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x2000;        //亮LED2

            Lcd_Proc(hour,min,8);       //时闪烁

            Delay(200);

            Lcd_Proc(hour,min,8);

            break;

        }

        case 7:                         //设置分
        {
            GPIOB->BSRR = 0xf000;       //灭所有LED

            GPIOB->BRR = 0x1000;        //亮LED1

            Lcd_Proc(hour,0xff,0);      //分闪烁

            Delay(200);

            Lcd_Proc(hour,min,8);

            break;

        }
    }
}

  • 为了使设置的数据闪烁,需要对Lcd_Proc()中的lcd_code[]数据定义做如下修改
char lcd_code[16]={0xeb,0x60,0xad,0xe5,0x66,0xc70xcf,0x61,

                    0xef,0xe7,0x6f,0xce,0x8b,0xec,0x8f,0x00};

 

  10.2.3 系统程序设计

  • 实时钟系统主程序和部分初始化子程序流程图

10. 实时钟系统设计

  • 主程序首先对系统进行初始化,包括SCB(系统控制模块)初始化、SysTick初始化、按键接口初始化、LED接口初始化、LCD接口初始化和USART1接口初始化等
  • 其中SCB初始化设置中断优先级组,SysTick初始化允许计数到0时中断,按键接口初始化允许按键接口中断,并设置按键中断的抢占优先级低于SysTick中断的抢占优先级,以允许在按键中断嵌套SysTick中断,实现延时功能
  • 系统初始化后写实时钟数据到DS1302作为实时钟的初始数据,然后进入主循环
  • 主循环的主要操作(包括显示处理和串口发送数据)每1s执行1次。显示状态下从DS1302读取实时钟数据显示并通过串口进行发送,设置状态下不从DS1302读取实时钟数据,只显示当前数据并等待通过按键进行设置
  • 按键程序和延时程序通过中断机制进行调用,可以实时响应按键和延时操作,显示程序和串口发送程序则通过主程序进行调用