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
- 实时钟电路提供实时钟计时,包括年、月、日、星期、时、分和秒等
- 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读取实时钟数据,只显示当前数据并等待通过按键进行设置
- 按键程序和延时程序通过中断机制进行调用,可以实时响应按键和延时操作,显示程序和串口发送程序则通过主程序进行调用