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


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


* DS1302控制字节


* DS1302读写时序


* DS1302时钟寄存器


 

  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显示状态图


* 系统共有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 系统程序设计

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


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