在对分层思想、时间片轮转和状态机思想进行[简单应用]
## 二、主函数
主函数如下:
整个主函数的中心任务为功能选择切换任务,负责切换显示内容,控制ui变化等,其余任务函数除提醒任务外都是通过全局变量的形式给功能选择切换任务提供资源或从该任务获取内容。
```c
#include "sys.h"
#include "task.h"
//系统时钟为12MHZ
void main()
{
u8 key = 0;
//各类初始化任务
TimerInit();
DS1302_Init();
DS18B20_ConvertT();
while(1)
{
LCD_Task();//显示任务,按照给定的内容进行显示
SHOW_CTRl_Task();//功能选择切换任务
TEMP_Task();//温度读取任务
ReadKey_Task();//按键读取任务
ALM_Task();//闹钟提醒任务
TMR_Task();//倒计时提醒任务
}
}
```
## 三、显示任务
由于显示任务涉及到了多个层级的函数,从最底层写命令、写数据,到中间层显示和初始化等函数。再到最顶层控制多行的显示。故使用了多级状态机的形式来完成lcd任务的状态机内容。由于C语言顺序执行的特性。规定同一层级使用同一个状态机,可以有效减少状态机的数量同时也能保证系统的稳定运行。
### 顶层代码如下
```c
void LCD_Task()
{ //显示初始化状态
if(LCD_STATE == 0)
{
if(LCD_Init() == 1)
{
LCD_STATE =1;
}
}
//显示第一行状态 可能还需细分,视需求而定
if(LCD_STATE == 1)
{
if(LCD_ShowString(1,Column1,LCD_SHOW_1) == 1)
{
LCD_STATE =2;
}
}
if(LCD_STATE == 2)
{
if(LCD_ShowString(2,Column2,LCD_SHOW_2) == 1)
{
LCD_STATE =3;
}
}
//显示暂停状态
if(LCD_STATE == 3)
{
}
}
```
### 中间层代码如下:
```c
u8 LCD_Init()
{
if(LCD_STATE_1 == 0)
{
//八位数据接口,两行显示,5*7点阵
if(LCD_WriteCommand(0x38) == 1)
{
LCD_STATE_1 = 1;
}
}
if(LCD_STATE_1 == 1)
{
//显示开,光标关,闪烁关
if(LCD_WriteCommand(0x0c) == 1)
{
LCD_STATE_1 = 2;
}
}
if(LCD_STATE_1 == 2)
{
//数据读写操作后,光标自动加一,画面不动
if(LCD_WriteCommand(0x06) == 1)
{
LCD_STATE_1 = 3;
}
}
if(LCD_STATE_1 == 3)
{
//光标复位,清屏
if(LCD_WriteCommand(0x01) == 1)
{
LCD_STATE_1 = 0;
return 1;
}
}
return 0;
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 0:显示字符未完成
* 1:显示字符完成
*/
u8 LCD_ShowChar(u8 Line,u8 Column,char Char)
{
if(LCD_STATE_1 == 0)
{
if(LCD_SetCursor(Line,Column) == 1)
LCD_STATE_1 = 1;
}
if(LCD_STATE_1 == 1)
{
if(LCD_WriteData(Char) == 1)
{
LCD_STATE_1 = 0;
return 1;
}
}
return 0;
}
```
### 底层代码如下:
```c
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
u8 LCD_WriteCommand(u8 Command)
{
if(LCD_STATE_0==0)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 1;
}
if(LCD_STATE_0 == 1)
{
if(TimeRef >= LCD_DELAY)
{
LCD_EN=0;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 2;
}
}
if(LCD_STATE_0 == 2)
{
if(TimeRef >= LCD_DELAY)
{
LCD_STATE_0 = 0;
return 1;
}
}
return 0;
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 0:未写入完成
* 1:写入完成
*/
u8 LCD_WriteData(u8 Data)
{
if(LCD_STATE_0==0)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 1;
}
if(LCD_STATE_0 == 1)
{
if(TimeRef >= LCD_DELAY)
{
LCD_EN=0;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 2;
}
}
if(LCD_STATE_0 == 2)
{
if(TimeRef >= LCD_DELAY)
{
LCD_STATE_0 = 0;
return 1;
}
}
return 0;
}
```
## 四、温度获取任务
温度获取任务主要分为初始化获取和循环获取。初始化获取用于立刻获取温度值防止出现刚开机时不显示温度的情况。循环获取用于间隔一段时间获取一次温度,实现较为实时的温度显示。基本代码如下
```c
//函数定义:
/**
* @brief 温度获取任务
* @param 无
* @retval 无
*/
void TEMP_Task()
{ //初始化状态
if(TEMP_STATE == 0)
{
if(DS18B20_ConvertT() == 1)
{
TEMP_DELAY = TimeRef + NUM_1000MS;
TEMP_STATE = 1;
}
}
//延时状态
if(TEMP_STATE == 1)
{
if(TimeRef >= TEMP_DELAY)
{
TEMP_STATE=2;
}
}
//转换状态
if(TEMP_STATE == 2)
{
if(DS18B20_ConvertT() == 1)
{
TEMP_STATE = 3;
}
}
if(TEMP_STATE == 3)
{
Temp = DS18B20_ReadT();
TEMP_DELAY = TimeRef + (u32)NUM_1000MS*1800;
TEMP_STATE =1;
}
}
```
该线程所需的驱动并未实现完全的状态机化由于单总线所需的延时较为精准在尝试了进行状态机化后发现会出现读取乱码的情况,且部分函数所需的延时时间太短甚至需要关闭定时器中断以对该延时进行保护,可对标操作系统的临界区保护。
## 五、按键获取任务
按键获取任务基本架构、分层与[按键控制数码管项目](51单片机按键数码管显示 时间片轮转+状态机 源程序 - 51单片机)类似,但由于本项目的功能较多且针对于项目的显示内容使用了现态的方式进行控制,即按键任务无法得知完成显示任务前的状态(功能),故引入过去态和未来态,前者供需要在现态中进行状态转换的任务标记其源头,后者用于在使用完相应的资源后跳转到相应的任务上。故按键对于当前功能界面的判断使用了过去态的方式,同时配合行号和列号完成相应功能。具体代码如下:
```c
void ReadKey_Task()
{
if(ReadKeyEN)
{
ReadKeyValue = ReadKeyDat();
//记录状态机的次态、现态、下态,在根据次态确认按下按键执行的内容
if(SHOW_CTRl_P_STATE == 4)//功能选择页面的按键选择
{
switch(ReadKeyValue)
{
case 0:;break;
case 1:{
Key_F_STATE_C = 6;
FSD_Line = 1;
FSD_Colu = 1;
}break;
case 2:Key_F_STATE_C = 7;break;
case 3:Key_F_STATE_C = 8;break;
case 4:Key_F_STATE_C = 9;break;
case 5:SHOW_CTRl_N_STATE = Key_F_STATE_C ;break;
}
}
if(SHOW_CTRl_P_STATE == 6)//闹钟设置页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;
case 1:{
switch(ALM_STA)
{
case 0:{
FSD_Line==1?FSD_Line=1:FSD_Line--;
SHOW_CTRl_N_STATE = 6;
}break;
case 1:{
ALM_TIME[FSD_Line-1][FSD_Colu-1]--;
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]<48)
{
ALM_TIME[FSD_Line-1][FSD_Colu-1]=48;
}
SHOW_CTRl_N_STATE = 6;
}break;
}
}break;
case 2:{
switch(ALM_STA)
{
case 0:{
FSD_Line==2?FSD_Line=2:FSD_Line++;
SHOW_CTRl_N_STATE = 6;
}break;
case 1:{
ALM_TIME[FSD_Line-1][FSD_Colu-1]++;
if((FSD_Colu-1)==0)
{
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]>=50)
{
ALM_TIME[FSD_Line-1][FSD_Colu-1]=50;
if(ALM_TIME[FSD_Line-1][1] >= 52)
ALM_TIME[FSD_Line-1][1]=52;
}
}else if((FSD_Colu-1)==1)
{
if(ALM_TIME[FSD_Line-1][0]==50)
{
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]>52)
ALM_TIME[FSD_Line-1][FSD_Colu-1]=52;
}
}else if((FSD_Colu-1)==2)
{
if(ALM_TIME[FSD_Line-1][2]>=53)
{
ALM_TIME[FSD_Line-1][2]=53;
}
}
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]>=57)
{
ALM_TIME[FSD_Line-1][FSD_Colu-1]=57;
}
SHOW_CTRl_N_STATE = 6;
}break;
}
}break;
case 3:{
switch(ALM_STA)
{
case 0:{
}break;
case 1:{
FSD_Colu == 1?FSD_Colu=1:FSD_Colu--;
}break;
}
}break;
case 4:{
switch(ALM_STA)
{
case 0:{
ALM_STA = 1;
}break;
case 1:{
FSD_Colu >= 5?FSD_Colu=5:FSD_Colu++;
}break;
}
}break;
case 5:{
switch(ALM_STA)
{
case 0:{
SHOW_CTRl_N_STATE = 2;
}break;
case 1:{
ALM_STA = 0;
}break;
}
}break;
}
}
if(SHOW_CTRl_P_STATE == 7)//倒计时页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;//无按键按下
case 1:{//up
switch(TMR_STA)
{
case 0:{//切换倒计时
FSD_Line==1?FSD_Line=1:FSD_Line--;
SHOW_CTRl_N_STATE = 7;
}break;
case 1:{//设置倒计时状态
switch(FSD_Colu)
{
case 0:;break;
case 1:{
if(TMR_SHOW_NUM[FSD_Line-1]>=600)
{
TMR_SHOW_NUM[FSD_Line-1]-=600;
TMR_SHOW_NUM_RST[FSD_Line-1]-=600;
}
}break;
case 2:{
if(TMR_SHOW_NUM[FSD_Line-1]>=60)
{
TMR_SHOW_NUM[FSD_Line-1]-=60;
TMR_SHOW_NUM_RST[FSD_Line-1]-=60;
}
}break;
case 3:{
if(TMR_SHOW_NUM[FSD_Line-1]>=10)
{
TMR_SHOW_NUM[FSD_Line-1]-=10;
TMR_SHOW_NUM_RST[FSD_Line-1]-=10;
}
}break;
case 4:{
if(TMR_SHOW_NUM[FSD_Line-1]>=1)
{
TMR_SHOW_NUM[FSD_Line-1]-=1;
TMR_SHOW_NUM_RST[FSD_Line-1]-=1;
}
}break;
case 5:{
TMR_S_S|=(0x01<<(FSD_Line-1));
TMR_STA = 2;
}break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
case 2:{//倒计时中状态
switch(FSD_Colu)
{
case 4:{//切换第几个倒计时
FSD_Line==1?FSD_Line=1:FSD_Line--;
SHOW_CTRl_N_STATE = 7;
}break;
case 5:TMR_S_S|=(0x01<<(FSD_Line-1));break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
}
}break;
case 2:{//down
switch(TMR_STA)
{
case 0:{//切换倒计时
FSD_Line==2?FSD_Line=2:FSD_Line++;
SHOW_CTRl_N_STATE = 7;
}break;
case 1:{//设置倒计时状态
switch(FSD_Colu)
{
case 0:;break;
case 1:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5399)
{
TMR_SHOW_NUM[FSD_Line-1]+=600;
TMR_SHOW_NUM_RST[FSD_Line-1]+=600;
}
}break;
case 2:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5939)
{
TMR_SHOW_NUM[FSD_Line-1]+=60 ;
TMR_SHOW_NUM_RST[FSD_Line-1]+=60;
}
}break;
case 3:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5989)
{
TMR_SHOW_NUM[FSD_Line-1]+=10 ;
TMR_SHOW_NUM_RST[FSD_Line-1]+=10;
}
}break;
case 4:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5998)
{
TMR_SHOW_NUM[FSD_Line-1]+=1 ;
TMR_SHOW_NUM_RST[FSD_Line-1]+=1;
}
}break;
case 5:TMR_S_S&=(!(0x01<<(FSD_Line-1)));break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
case 2:{//倒计时中状态
switch(FSD_Colu)
{
case 4:{
FSD_Line==2?FSD_Line=2:FSD_Line++;
SHOW_CTRl_N_STATE = 7;
}break;
case 5:TMR_S_S&=(!(0x01<<(FSD_Line-1)));break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
}
}break;
case 3:{//left
switch(TMR_STA)
{
case 0:{//切换倒计时
}break;
case 1:{//设置倒计时状态
FSD_Colu == 1?FSD_Colu=1:FSD_Colu--;
}break;
case 2:{//倒计时中状态
FSD_Colu == 5?FSD_Colu=5:FSD_Colu--;
}break;
}
}break;
case 4:{//right
switch(TMR_STA)
{
case 0:{//切换倒计时
if(TMR_S_S &(0x01<<(FSD_Line-1)))//判断该倒计时是否开启中
{
TMR_STA = 2;//从切换倒计时状态变为设置倒计时状态
FSD_Colu =5;
}else
{
TMR_STA = 1;//从切换倒计时状态变为设置倒计时状态
FSD_Colu =1;
}
}break;
case 1:{//设置倒计时状态
if(FSD_Colu >= 6)
{
FSD_Colu=6;
SHOW_CTRl_N_STATE = 7;
}else
{
FSD_Colu++;
}
}break;
case 2:{//倒计时中状态
if(FSD_Colu >= 6)
{
FSD_Colu=6;
SHOW_CTRl_N_STATE = 7;
}else
{
FSD_Colu++;
}
}break;
}
}break;
case 5:{//confirm
switch(TMR_STA)
{
case 0:{ //切换倒计时
SHOW_CTRl_P_STATE = 2;//切换为时间显示态
SHOW_CTRl_N_STATE = 2;//切换为时间显示态
}break;
case 1:{//设置倒计时状态
TMR_STA = 0;//切换为切换状态
}break;
case 2:{//倒计时中状态
if(FSD_Colu == 6)
{
TMR_SHOW_NUM[FSD_Line-1] = TMR_SHOW_NUM_RST[FSD_Line-1];
}else
{
TMR_STA = 0;//切换为切换状态
}
}break;
}
}break;
}
}
if(SHOW_CTRl_P_STATE == 8)//秒表页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;//无按键按下
case 1:{//上
switch(STW_STA)
{
case 0:{//计时中
switch(FSD_Colu)
{
case 1:{
STW_STA = 1;
STW_SPT_NUM_i = 0;
STW_SHOW_NUM_SPT[0] = STW_SHOW_NUM;
SHOW_CTRl_N_STATE = 8;
}break;
case 2:{
}break;
case 3:{
}break;
}
}break;
case 1:{//计时暂停
switch(FSD_Colu)
{
case 1:{//不变
}break;
case 2:{//清除
}break;
case 3:{//翻看记录
if(STW_SPT_NUM_i>0)//限位
STW_SPT_NUM_i--;
STW_SHOW_NUM = STW_SHOW_NUM_SPT[STW_SPT_NUM_i];
SHOW_CTRl_N_STATE = 8;
}break;
}
}break;
}
};break;
case 2:{//下
switch(STW_STA)
{
case 0:{//计时中
switch(FSD_Colu)
{
case 1:{
}break;
case 2:{//记录
if(STW_SPT_NUM<STWSetNum-1)
{
STW_SPT_NUM++;
}
STW_SHOW_NUM_SPT[STW_SPT_NUM] = STW_SHOW_NUM;
}break;
case 3:{
}break;
}
}break;
case 1:{//计时暂停
switch(FSD_Colu)
{
case 1:{
STW_STA = 0;
SHOW_CTRl_N_STATE = 8;
}break;
case 2:{//清除
STW_SHOW_NUM = 0;
STW_SPT_NUM = 0;
SHOW_CTRl_N_STATE = 8;
//若后台有倒计时会对其有一定影响
TH1=0x3c;//50ms中断一次
TL1=0xAF;
}break;
case 3:{//翻看记录
STW_SPT_NUM_i++;
if(STW_SPT_NUM_i>=STW_SPT_NUM)//限位
STW_SPT_NUM_i=STW_SPT_NUM;
STW_SHOW_NUM = STW_SHOW_NUM_SPT[STW_SPT_NUM_i];
SHOW_CTRl_N_STATE = 8;
}break;
}
}break;
}
};break;
case 3:{//左
if(FSD_Colu>0)
{
FSD_Colu--;
}
};break;
case 4:{//右
if(FSD_Colu<3)
{
FSD_Colu++;
}
};break;
case 5:{//确认
SHOW_CTRl_P_STATE = 2;//切换为时间显示态
SHOW_CTRl_N_STATE = 2;//切换为时间显示态
};break;
}
}
if(SHOW_CTRl_P_STATE == 9)//24/12时页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;
case 1:Tim_12_24_Set = 0;SHOW_CTRl_N_STATE=9;break;
case 2:Tim_12_24_Set = 1;SHOW_CTRl_N_STATE=9;break;
case 5:SHOW_CTRl_N_STATE = 2; ;break;
}
}
}
}
```
## 六、闹钟任务
闹钟任务钟主要使用闹钟的有效值判断闹钟是否开启,若开启则将设置的闹钟值与当前时间进行比较,当当前时间与设置的闹钟时间一致时则led灯亮。具体代码如下:
```c
/**函数定义:
* @brief 闹钟任务
* @param 无
* @retval 无
*/
void ALM_Task()
{
if(ALM_TIME[0][4]%2)
{
if(DS1302_Time[3]==((ALM_TIME[0][0]-48)*10+ALM_TIME[0][1]-48) && DS1302_Time[4]==((ALM_TIME[0][2]-48)*10+ALM_TIME[0][3]-48))
{
led=0;
return;
}
}
if(ALM_TIME[1][4]%2)
{
if(DS1302_Time[3]==((ALM_TIME[1][0]-48)*10+ALM_TIME[1][1]-48) && DS1302_Time[4]==((ALM_TIME[1][2]-48)*10+ALM_TIME[1][3]-48))
{
led=0;
return;
}
}
led =1;
}
```
## 七、倒计时任务
倒计时任务与闹钟任务类似,也是比较后灯亮具体代码如下
```c
/**函数定义:
* @brief 倒计时任务
* @param 无
* @retval 无
*/
idata u8 TMR_P_TIME=0;
idata u8 TMR_FLAG =0;
void TMR_Task()
{
if(TMR_FLAG == 1)
{
if(DS1302_Time[5]<TMR_P_TIME)
{
led=0;
return;
}
}
led =1;
TMR_FLAG=0;
}
```
## 八、功能选择任务
功能选择任务为整个项目的核心。它控制着整个项目的状态机变换方向,同时针对外部按键做出的状态赋值。完成相应的显示内容切换和状态转换。具体代码如下:
```c
//根据页面设计相应的状态机,需要统一延时态返回到上一状态
void SHOW_CTRl_Task()
{
//初始化状态
if(SHOW_CTRl_N_STATE == 0)
{
if(LCD_STATE == 3)
{
SHOW_CTRl_DELAY = TimeRef + NUM_1000MS;
SHOW_CTRl_N_STATE = 1;//切换到延时态
SHOW_CTRl_F_STATE = 2;//延时态结束后切换到时间显示态
}
}
//延时态
if(SHOW_CTRl_N_STATE == 1)
{
if(TimeRef >= SHOW_CTRl_DELAY)
{
SHOW_CTRl_N_STATE = SHOW_CTRl_F_STATE;
}
}
//时间显示态
if(SHOW_CTRl_N_STATE == 2)
{
LCD_ShowBase();
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE =2;
}
//等待显示完成
if(SHOW_CTRl_N_STATE == 3)
{
if(LCD_STATE == 3)
{
SHOW_CTRl_N_STATE = SHOW_CTRl_F_STATE;
}
}
//外部触发动作,功能选择态
if(SHOW_CTRl_N_STATE == 4)
{
//出现需要按下两次功能选择按键才能正常显示的现象,疑似与lcd状态机有关,需要进行修改
strcpy(LCD_SHOW_1,"U:RLM L:STW ");
strcpy(LCD_SHOW_2,"D:TMR R:24/12 ");
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
//初始化该变量以防在多个功能使用时出现问题。
FSD_Line=1;//功能设置显示行
FSD_Colu=1;//功能设置显示列
STW_SPT_NUM = 0;
//状态设置
SHOW_CTRl_P_STATE = 4;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
ReadKeyEN = 1;
Key_F_STATE_C = SHOW_CTRl_F_STATE;//初始化按键状态防止状态机被意外改变
}
//显示暂停态
if(SHOW_CTRl_N_STATE == 5)
{
SHOW_CTRl_N_STATE = SHOW_CTRl_F_STATE;
}
//闹钟设置状态
if(SHOW_CTRl_N_STATE == 6)
{
strcpy(LCD_SHOW_1," RLMSet ");
strcpy(LCD_SHOW_2,"A 00:00 ");
LCD_SHOW_2[1] = FSD_Line +'0';
LCD_SHOW_2[3] = ALM_TIME[FSD_Line-1][0];
LCD_SHOW_2[4] = ALM_TIME[FSD_Line-1][1];
LCD_SHOW_2[6] = ALM_TIME[FSD_Line-1][2];
LCD_SHOW_2[7] = ALM_TIME[FSD_Line-1][3];
if(ALM_TIME[FSD_Line-1][4]%2)
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'N';
LCD_SHOW_2[11] = ' ';
}else
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'F';
LCD_SHOW_2[11] = 'F';
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 6;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
//倒计时状态(开启关闭重置)
if(SHOW_CTRl_N_STATE == 7)
{
strcpy(LCD_SHOW_1," TMRSet ");
strcpy(LCD_SHOW_2,"T : R ");
//只负责显示
LCD_SHOW_2[1] = FSD_Line +'0';
LCD_SHOW_2[3] = TMR_SHOW_NUM[FSD_Line-1]/600 +'0';
LCD_SHOW_2[4] = TMR_SHOW_NUM[FSD_Line-1]/60%10+'0';
LCD_SHOW_2[6] = TMR_SHOW_NUM[FSD_Line-1]%60/10+'0';
LCD_SHOW_2[7] = TMR_SHOW_NUM[FSD_Line-1]%10+'0';
//判断倒计时是否开启
if(TMR_S_S &(0x01<<(FSD_Line-1)))//开启
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'N';
LCD_SHOW_2[11] = ' ';
if(TMR_S_S==0x01||TMR_S_S==0x02)//第一次开启
{
TH1=0x3c;//50ms 中断一次
TL1=0xAF;
TR1=1;
}
}else
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'F';
LCD_SHOW_2[11] = 'F';
if(TMR_S_S==0)//最后一次关闭
{
TR1=0;
}
}
//倒计时是否重置显示内容
if(FSD_Colu == 6)
{
LCD_SHOW_2[15] = '?';
}else
{
LCD_SHOW_2[15] = ' ';
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 7;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
//秒表设置状态
if(SHOW_CTRl_N_STATE == 8)
{
strcpy(LCD_SHOW_1," STWSet ");
strcpy(LCD_SHOW_2," : : OFF RES");
//只负责显示
LCD_SHOW_2[1] = STW_SHOW_NUM/6000+'0';
LCD_SHOW_2[2] = STW_SHOW_NUM/600%10+'0';
LCD_SHOW_2[4] = STW_SHOW_NUM/100%10+'0';
LCD_SHOW_2[5] = STW_SHOW_NUM/10%10+'0';
LCD_SHOW_2[7] = STW_SHOW_NUM%10+'0';
if(STW_STA)
{
LCD_SHOW_2[9] ='O';
LCD_SHOW_2[10]='F';
LCD_SHOW_2[11]='F';
LCD_SHOW_2[13]='R';
LCD_SHOW_2[14]='S';
LCD_SHOW_2[15]='T';
//在按下RST键时重新写入定时器值
}else
{
LCD_SHOW_2[9] ='O';
LCD_SHOW_2[10]='N';
LCD_SHOW_2[11]=' ';
LCD_SHOW_2[13]='S';
LCD_SHOW_2[14]='P';
LCD_SHOW_2[15]='T';
TR1=1;
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 8;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
//12/24设置状态
if(SHOW_CTRl_N_STATE == 9)
{
strcpy(LCD_SHOW_1," 24/12Set ");
strcpy(LCD_SHOW_2," ");
if(Tim_12_24_Set)
{
LCD_SHOW_2[4] = '1';
LCD_SHOW_2[5] = '2';
LCD_SHOW_2[6] = 'H';
}else
{
LCD_SHOW_2[4] = '2';
LCD_SHOW_2[5] = '4';
LCD_SHOW_2[6] = 'H';
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 9;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
}
```
## 九、总结
本项目使用了分层、时间片轮、状态机思想完成了51电子钟的设计。其主要功能包含基本时间显示、温度显示、倒计时、秒表、闹钟、24h/12h选择,使用按键选择和设置相应的功能。本项目的代码对于状态机思想进行了比较深入的运用希望各位能够在其中有所收获。```c
void LCD_Task()
{ //显示初始化状态
if(LCD_STATE == 0)
{
if(LCD_Init() == 1)
{
LCD_STATE =1;
}
}
//显示第一行状态 可能还需细分,视需求而定
if(LCD_STATE == 1)
{
if(LCD_ShowString(1,Column1,LCD_SHOW_1) == 1)
{
LCD_STATE =2;
}
}
if(LCD_STATE == 2)
{
if(LCD_ShowString(2,Column2,LCD_SHOW_2) == 1)
{
LCD_STATE =3;
}
}
//显示暂停状态
if(LCD_STATE == 3)
{
}
}
```
### 中间层代码如下:
```c
u8 LCD_Init()
{
if(LCD_STATE_1 == 0)
{
//八位数据接口,两行显示,5*7点阵
if(LCD_WriteCommand(0x38) == 1)
{
LCD_STATE_1 = 1;
}
}
if(LCD_STATE_1 == 1)
{
//显示开,光标关,闪烁关
if(LCD_WriteCommand(0x0c) == 1)
{
LCD_STATE_1 = 2;
}
}
if(LCD_STATE_1 == 2)
{
//数据读写操作后,光标自动加一,画面不动
if(LCD_WriteCommand(0x06) == 1)
{
LCD_STATE_1 = 3;
}
}
if(LCD_STATE_1 == 3)
{
//光标复位,清屏
if(LCD_WriteCommand(0x01) == 1)
{
LCD_STATE_1 = 0;
return 1;
}
}
return 0;
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 0:显示字符未完成
* 1:显示字符完成
*/
u8 LCD_ShowChar(u8 Line,u8 Column,char Char)
{
if(LCD_STATE_1 == 0)
{
if(LCD_SetCursor(Line,Column) == 1)
LCD_STATE_1 = 1;
}
if(LCD_STATE_1 == 1)
{
if(LCD_WriteData(Char) == 1)
{
LCD_STATE_1 = 0;
return 1;
}
}
return 0;
}
```
### 底层代码如下:
```c
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
u8 LCD_WriteCommand(u8 Command)
{
if(LCD_STATE_0==0)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 1;
}
if(LCD_STATE_0 == 1)
{
if(TimeRef >= LCD_DELAY)
{
LCD_EN=0;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 2;
}
}
if(LCD_STATE_0 == 2)
{
if(TimeRef >= LCD_DELAY)
{
LCD_STATE_0 = 0;
return 1;
}
}
return 0;
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 0:未写入完成
* 1:写入完成
*/
u8 LCD_WriteData(u8 Data)
{
if(LCD_STATE_0==0)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 1;
}
if(LCD_STATE_0 == 1)
{
if(TimeRef >= LCD_DELAY)
{
LCD_EN=0;
LCD_DELAY = TimeRef + NUM_1MS;
LCD_STATE_0 = 2;
}
}
if(LCD_STATE_0 == 2)
{
if(TimeRef >= LCD_DELAY)
{
LCD_STATE_0 = 0;
return 1;
}
}
return 0;
}
```
## 四、温度获取任务
温度获取任务主要分为初始化获取和循环获取。初始化获取用于立刻获取温度值防止出现刚开机时不显示温度的情况。循环获取用于间隔一段时间获取一次温度,实现较为实时的温度显示。基本代码如下
```c
//函数定义:
/**
* @brief 温度获取任务
* @param 无
* @retval 无
*/
void TEMP_Task()
{ //初始化状态
if(TEMP_STATE == 0)
{
if(DS18B20_ConvertT() == 1)
{
TEMP_DELAY = TimeRef + NUM_1000MS;
TEMP_STATE = 1;
}
}
//延时状态
if(TEMP_STATE == 1)
{
if(TimeRef >= TEMP_DELAY)
{
TEMP_STATE=2;
}
}
//转换状态
if(TEMP_STATE == 2)
{
if(DS18B20_ConvertT() == 1)
{
TEMP_STATE = 3;
}
}
if(TEMP_STATE == 3)
{
Temp = DS18B20_ReadT();
TEMP_DELAY = TimeRef + (u32)NUM_1000MS*1800;
TEMP_STATE =1;
}
}
```
该线程所需的驱动并未实现完全的状态机化由于单总线所需的延时较为精准在尝试了进行状态机化后发现会出现读取乱码的情况,且部分函数所需的延时时间太短甚至需要关闭定时器中断以对该延时进行保护,可对标操作系统的临界区保护。
## 五、按键获取任务
按键获取任务基本架构、分层与[按键控制数码管项目](51单片机按键数码管显示 时间片轮转+状态机 源程序 - 51单片机)类似,但由于本项目的功能较多且针对于项目的显示内容使用了现态的方式进行控制,即按键任务无法得知完成显示任务前的状态(功能),故引入过去态和未来态,前者供需要在现态中进行状态转换的任务标记其源头,后者用于在使用完相应的资源后跳转到相应的任务上。故按键对于当前功能界面的判断使用了过去态的方式,同时配合行号和列号完成相应功能。具体代码如下:
```c
void ReadKey_Task()
{
if(ReadKeyEN)
{
ReadKeyValue = ReadKeyDat();
//记录状态机的次态、现态、下态,在根据次态确认按下按键执行的内容
if(SHOW_CTRl_P_STATE == 4)//功能选择页面的按键选择
{
switch(ReadKeyValue)
{
case 0:;break;
case 1:{
Key_F_STATE_C = 6;
FSD_Line = 1;
FSD_Colu = 1;
}break;
case 2:Key_F_STATE_C = 7;break;
case 3:Key_F_STATE_C = 8;break;
case 4:Key_F_STATE_C = 9;break;
case 5:SHOW_CTRl_N_STATE = Key_F_STATE_C ;break;
}
}
if(SHOW_CTRl_P_STATE == 6)//闹钟设置页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;
case 1:{
switch(ALM_STA)
{
case 0:{
FSD_Line==1?FSD_Line=1:FSD_Line--;
SHOW_CTRl_N_STATE = 6;
}break;
case 1:{
ALM_TIME[FSD_Line-1][FSD_Colu-1]--;
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]<48)
{
ALM_TIME[FSD_Line-1][FSD_Colu-1]=48;
}
SHOW_CTRl_N_STATE = 6;
}break;
}
}break;
case 2:{
switch(ALM_STA)
{
case 0:{
FSD_Line==2?FSD_Line=2:FSD_Line++;
SHOW_CTRl_N_STATE = 6;
}break;
case 1:{
ALM_TIME[FSD_Line-1][FSD_Colu-1]++;
if((FSD_Colu-1)==0)
{
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]>=50)
{
ALM_TIME[FSD_Line-1][FSD_Colu-1]=50;
if(ALM_TIME[FSD_Line-1][1] >= 52)
ALM_TIME[FSD_Line-1][1]=52;
}
}else if((FSD_Colu-1)==1)
{
if(ALM_TIME[FSD_Line-1][0]==50)
{
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]>52)
ALM_TIME[FSD_Line-1][FSD_Colu-1]=52;
}
}else if((FSD_Colu-1)==2)
{
if(ALM_TIME[FSD_Line-1][2]>=53)
{
ALM_TIME[FSD_Line-1][2]=53;
}
}
if(ALM_TIME[FSD_Line-1][FSD_Colu-1]>=57)
{
ALM_TIME[FSD_Line-1][FSD_Colu-1]=57;
}
SHOW_CTRl_N_STATE = 6;
}break;
}
}break;
case 3:{
switch(ALM_STA)
{
case 0:{
}break;
case 1:{
FSD_Colu == 1?FSD_Colu=1:FSD_Colu--;
}break;
}
}break;
case 4:{
switch(ALM_STA)
{
case 0:{
ALM_STA = 1;
}break;
case 1:{
FSD_Colu >= 5?FSD_Colu=5:FSD_Colu++;
}break;
}
}break;
case 5:{
switch(ALM_STA)
{
case 0:{
SHOW_CTRl_N_STATE = 2;
}break;
case 1:{
ALM_STA = 0;
}break;
}
}break;
}
}
if(SHOW_CTRl_P_STATE == 7)//倒计时页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;//无按键按下
case 1:{//up
switch(TMR_STA)
{
case 0:{//切换倒计时
FSD_Line==1?FSD_Line=1:FSD_Line--;
SHOW_CTRl_N_STATE = 7;
}break;
case 1:{//设置倒计时状态
switch(FSD_Colu)
{
case 0:;break;
case 1:{
if(TMR_SHOW_NUM[FSD_Line-1]>=600)
{
TMR_SHOW_NUM[FSD_Line-1]-=600;
TMR_SHOW_NUM_RST[FSD_Line-1]-=600;
}
}break;
case 2:{
if(TMR_SHOW_NUM[FSD_Line-1]>=60)
{
TMR_SHOW_NUM[FSD_Line-1]-=60;
TMR_SHOW_NUM_RST[FSD_Line-1]-=60;
}
}break;
case 3:{
if(TMR_SHOW_NUM[FSD_Line-1]>=10)
{
TMR_SHOW_NUM[FSD_Line-1]-=10;
TMR_SHOW_NUM_RST[FSD_Line-1]-=10;
}
}break;
case 4:{
if(TMR_SHOW_NUM[FSD_Line-1]>=1)
{
TMR_SHOW_NUM[FSD_Line-1]-=1;
TMR_SHOW_NUM_RST[FSD_Line-1]-=1;
}
}break;
case 5:{
TMR_S_S|=(0x01<<(FSD_Line-1));
TMR_STA = 2;
}break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
case 2:{//倒计时中状态
switch(FSD_Colu)
{
case 4:{//切换第几个倒计时
FSD_Line==1?FSD_Line=1:FSD_Line--;
SHOW_CTRl_N_STATE = 7;
}break;
case 5:TMR_S_S|=(0x01<<(FSD_Line-1));break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
}
}break;
case 2:{//down
switch(TMR_STA)
{
case 0:{//切换倒计时
FSD_Line==2?FSD_Line=2:FSD_Line++;
SHOW_CTRl_N_STATE = 7;
}break;
case 1:{//设置倒计时状态
switch(FSD_Colu)
{
case 0:;break;
case 1:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5399)
{
TMR_SHOW_NUM[FSD_Line-1]+=600;
TMR_SHOW_NUM_RST[FSD_Line-1]+=600;
}
}break;
case 2:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5939)
{
TMR_SHOW_NUM[FSD_Line-1]+=60 ;
TMR_SHOW_NUM_RST[FSD_Line-1]+=60;
}
}break;
case 3:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5989)
{
TMR_SHOW_NUM[FSD_Line-1]+=10 ;
TMR_SHOW_NUM_RST[FSD_Line-1]+=10;
}
}break;
case 4:{
if(TMR_SHOW_NUM[FSD_Line-1]<=5998)
{
TMR_SHOW_NUM[FSD_Line-1]+=1 ;
TMR_SHOW_NUM_RST[FSD_Line-1]+=1;
}
}break;
case 5:TMR_S_S&=(!(0x01<<(FSD_Line-1)));break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
case 2:{//倒计时中状态
switch(FSD_Colu)
{
case 4:{
FSD_Line==2?FSD_Line=2:FSD_Line++;
SHOW_CTRl_N_STATE = 7;
}break;
case 5:TMR_S_S&=(!(0x01<<(FSD_Line-1)));break;
case 6:;break;
}
SHOW_CTRl_N_STATE = 7;
}break;
}
}break;
case 3:{//left
switch(TMR_STA)
{
case 0:{//切换倒计时
}break;
case 1:{//设置倒计时状态
FSD_Colu == 1?FSD_Colu=1:FSD_Colu--;
}break;
case 2:{//倒计时中状态
FSD_Colu == 5?FSD_Colu=5:FSD_Colu--;
}break;
}
}break;
case 4:{//right
switch(TMR_STA)
{
case 0:{//切换倒计时
if(TMR_S_S &(0x01<<(FSD_Line-1)))//判断该倒计时是否开启中
{
TMR_STA = 2;//从切换倒计时状态变为设置倒计时状态
FSD_Colu =5;
}else
{
TMR_STA = 1;//从切换倒计时状态变为设置倒计时状态
FSD_Colu =1;
}
}break;
case 1:{//设置倒计时状态
if(FSD_Colu >= 6)
{
FSD_Colu=6;
SHOW_CTRl_N_STATE = 7;
}else
{
FSD_Colu++;
}
}break;
case 2:{//倒计时中状态
if(FSD_Colu >= 6)
{
FSD_Colu=6;
SHOW_CTRl_N_STATE = 7;
}else
{
FSD_Colu++;
}
}break;
}
}break;
case 5:{//confirm
switch(TMR_STA)
{
case 0:{ //切换倒计时
SHOW_CTRl_P_STATE = 2;//切换为时间显示态
SHOW_CTRl_N_STATE = 2;//切换为时间显示态
}break;
case 1:{//设置倒计时状态
TMR_STA = 0;//切换为切换状态
}break;
case 2:{//倒计时中状态
if(FSD_Colu == 6)
{
TMR_SHOW_NUM[FSD_Line-1] = TMR_SHOW_NUM_RST[FSD_Line-1];
}else
{
TMR_STA = 0;//切换为切换状态
}
}break;
}
}break;
}
}
if(SHOW_CTRl_P_STATE == 8)//秒表页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;//无按键按下
case 1:{//上
switch(STW_STA)
{
case 0:{//计时中
switch(FSD_Colu)
{
case 1:{
STW_STA = 1;
STW_SPT_NUM_i = 0;
STW_SHOW_NUM_SPT[0] = STW_SHOW_NUM;
SHOW_CTRl_N_STATE = 8;
}break;
case 2:{
}break;
case 3:{
}break;
}
}break;
case 1:{//计时暂停
switch(FSD_Colu)
{
case 1:{//不变
}break;
case 2:{//清除
}break;
case 3:{//翻看记录
if(STW_SPT_NUM_i>0)//限位
STW_SPT_NUM_i--;
STW_SHOW_NUM = STW_SHOW_NUM_SPT[STW_SPT_NUM_i];
SHOW_CTRl_N_STATE = 8;
}break;
}
}break;
}
};break;
case 2:{//下
switch(STW_STA)
{
case 0:{//计时中
switch(FSD_Colu)
{
case 1:{
}break;
case 2:{//记录
if(STW_SPT_NUM<STWSetNum-1)
{
STW_SPT_NUM++;
}
STW_SHOW_NUM_SPT[STW_SPT_NUM] = STW_SHOW_NUM;
}break;
case 3:{
}break;
}
}break;
case 1:{//计时暂停
switch(FSD_Colu)
{
case 1:{
STW_STA = 0;
SHOW_CTRl_N_STATE = 8;
}break;
case 2:{//清除
STW_SHOW_NUM = 0;
STW_SPT_NUM = 0;
SHOW_CTRl_N_STATE = 8;
//若后台有倒计时会对其有一定影响
TH1=0x3c;//50ms中断一次
TL1=0xAF;
}break;
case 3:{//翻看记录
STW_SPT_NUM_i++;
if(STW_SPT_NUM_i>=STW_SPT_NUM)//限位
STW_SPT_NUM_i=STW_SPT_NUM;
STW_SHOW_NUM = STW_SHOW_NUM_SPT[STW_SPT_NUM_i];
SHOW_CTRl_N_STATE = 8;
}break;
}
}break;
}
};break;
case 3:{//左
if(FSD_Colu>0)
{
FSD_Colu--;
}
};break;
case 4:{//右
if(FSD_Colu<3)
{
FSD_Colu++;
}
};break;
case 5:{//确认
SHOW_CTRl_P_STATE = 2;//切换为时间显示态
SHOW_CTRl_N_STATE = 2;//切换为时间显示态
};break;
}
}
if(SHOW_CTRl_P_STATE == 9)//24/12时页面的按键
{
switch(ReadKeyValue)
{
case 0:;break;
case 1:Tim_12_24_Set = 0;SHOW_CTRl_N_STATE=9;break;
case 2:Tim_12_24_Set = 1;SHOW_CTRl_N_STATE=9;break;
case 5:SHOW_CTRl_N_STATE = 2; ;break;
}
}
}
}
```
## 六、闹钟任务
闹钟任务钟主要使用闹钟的有效值判断闹钟是否开启,若开启则将设置的闹钟值与当前时间进行比较,当当前时间与设置的闹钟时间一致时则led灯亮。具体代码如下:
```c
/**函数定义:
* @brief 闹钟任务
* @param 无
* @retval 无
*/
void ALM_Task()
{
if(ALM_TIME[0][4]%2)
{
if(DS1302_Time[3]==((ALM_TIME[0][0]-48)*10+ALM_TIME[0][1]-48) && DS1302_Time[4]==((ALM_TIME[0][2]-48)*10+ALM_TIME[0][3]-48))
{
led=0;
return;
}
}
if(ALM_TIME[1][4]%2)
{
if(DS1302_Time[3]==((ALM_TIME[1][0]-48)*10+ALM_TIME[1][1]-48) && DS1302_Time[4]==((ALM_TIME[1][2]-48)*10+ALM_TIME[1][3]-48))
{
led=0;
return;
}
}
led =1;
}
```
## 七、倒计时任务
倒计时任务与闹钟任务类似,也是比较后灯亮具体代码如下
```c
/**函数定义:
* @brief 倒计时任务
* @param 无
* @retval 无
*/
idata u8 TMR_P_TIME=0;
idata u8 TMR_FLAG =0;
void TMR_Task()
{
if(TMR_FLAG == 1)
{
if(DS1302_Time[5]<TMR_P_TIME)
{
led=0;
return;
}
}
led =1;
TMR_FLAG=0;
}
```
## 八、功能选择任务
功能选择任务为整个项目的核心。它控制着整个项目的状态机变换方向,同时针对外部按键做出的状态赋值。完成相应的显示内容切换和状态转换。具体代码如下:
```c
//根据页面设计相应的状态机,需要统一延时态返回到上一状态
void SHOW_CTRl_Task()
{
//初始化状态
if(SHOW_CTRl_N_STATE == 0)
{
if(LCD_STATE == 3)
{
SHOW_CTRl_DELAY = TimeRef + NUM_1000MS;
SHOW_CTRl_N_STATE = 1;//切换到延时态
SHOW_CTRl_F_STATE = 2;//延时态结束后切换到时间显示态
}
}
//延时态
if(SHOW_CTRl_N_STATE == 1)
{
if(TimeRef >= SHOW_CTRl_DELAY)
{
SHOW_CTRl_N_STATE = SHOW_CTRl_F_STATE;
}
}
//时间显示态
if(SHOW_CTRl_N_STATE == 2)
{
LCD_ShowBase();
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE =2;
}
//等待显示完成
if(SHOW_CTRl_N_STATE == 3)
{
if(LCD_STATE == 3)
{
SHOW_CTRl_N_STATE = SHOW_CTRl_F_STATE;
}
}
//外部触发动作,功能选择态
if(SHOW_CTRl_N_STATE == 4)
{
//出现需要按下两次功能选择按键才能正常显示的现象,疑似与lcd状态机有关,需要进行修改
strcpy(LCD_SHOW_1,"U:RLM L:STW ");
strcpy(LCD_SHOW_2,"D:TMR R:24/12 ");
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
//初始化该变量以防在多个功能使用时出现问题。
FSD_Line=1;//功能设置显示行
FSD_Colu=1;//功能设置显示列
STW_SPT_NUM = 0;
//状态设置
SHOW_CTRl_P_STATE = 4;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
ReadKeyEN = 1;
Key_F_STATE_C = SHOW_CTRl_F_STATE;//初始化按键状态防止状态机被意外改变
}
//显示暂停态
if(SHOW_CTRl_N_STATE == 5)
{
SHOW_CTRl_N_STATE = SHOW_CTRl_F_STATE;
}
//闹钟设置状态
if(SHOW_CTRl_N_STATE == 6)
{
strcpy(LCD_SHOW_1," RLMSet ");
strcpy(LCD_SHOW_2,"A 00:00 ");
LCD_SHOW_2[1] = FSD_Line +'0';
LCD_SHOW_2[3] = ALM_TIME[FSD_Line-1][0];
LCD_SHOW_2[4] = ALM_TIME[FSD_Line-1][1];
LCD_SHOW_2[6] = ALM_TIME[FSD_Line-1][2];
LCD_SHOW_2[7] = ALM_TIME[FSD_Line-1][3];
if(ALM_TIME[FSD_Line-1][4]%2)
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'N';
LCD_SHOW_2[11] = ' ';
}else
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'F';
LCD_SHOW_2[11] = 'F';
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 6;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
//倒计时状态(开启关闭重置)
if(SHOW_CTRl_N_STATE == 7)
{
strcpy(LCD_SHOW_1," TMRSet ");
strcpy(LCD_SHOW_2,"T : R ");
//只负责显示
LCD_SHOW_2[1] = FSD_Line +'0';
LCD_SHOW_2[3] = TMR_SHOW_NUM[FSD_Line-1]/600 +'0';
LCD_SHOW_2[4] = TMR_SHOW_NUM[FSD_Line-1]/60%10+'0';
LCD_SHOW_2[6] = TMR_SHOW_NUM[FSD_Line-1]%60/10+'0';
LCD_SHOW_2[7] = TMR_SHOW_NUM[FSD_Line-1]%10+'0';
//判断倒计时是否开启
if(TMR_S_S &(0x01<<(FSD_Line-1)))//开启
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'N';
LCD_SHOW_2[11] = ' ';
if(TMR_S_S==0x01||TMR_S_S==0x02)//第一次开启
{
TH1=0x3c;//50ms 中断一次
TL1=0xAF;
TR1=1;
}
}else
{
LCD_SHOW_2[9] = 'O';
LCD_SHOW_2[10] = 'F';
LCD_SHOW_2[11] = 'F';
if(TMR_S_S==0)//最后一次关闭
{
TR1=0;
}
}
//倒计时是否重置显示内容
if(FSD_Colu == 6)
{
LCD_SHOW_2[15] = '?';
}else
{
LCD_SHOW_2[15] = ' ';
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 7;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
//秒表设置状态
if(SHOW_CTRl_N_STATE == 8)
{
strcpy(LCD_SHOW_1," STWSet ");
strcpy(LCD_SHOW_2," : : OFF RES");
//只负责显示
LCD_SHOW_2[1] = STW_SHOW_NUM/6000+'0';
LCD_SHOW_2[2] = STW_SHOW_NUM/600%10+'0';
LCD_SHOW_2[4] = STW_SHOW_NUM/100%10+'0';
LCD_SHOW_2[5] = STW_SHOW_NUM/10%10+'0';
LCD_SHOW_2[7] = STW_SHOW_NUM%10+'0';
if(STW_STA)
{
LCD_SHOW_2[9] ='O';
LCD_SHOW_2[10]='F';
LCD_SHOW_2[11]='F';
LCD_SHOW_2[13]='R';
LCD_SHOW_2[14]='S';
LCD_SHOW_2[15]='T';
//在按下RST键时重新写入定时器值
}else
{
LCD_SHOW_2[9] ='O';
LCD_SHOW_2[10]='N';
LCD_SHOW_2[11]=' ';
LCD_SHOW_2[13]='S';
LCD_SHOW_2[14]='P';
LCD_SHOW_2[15]='T';
TR1=1;
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 8;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
//12/24设置状态
if(SHOW_CTRl_N_STATE == 9)
{
strcpy(LCD_SHOW_1," 24/12Set ");
strcpy(LCD_SHOW_2," ");
if(Tim_12_24_Set)
{
LCD_SHOW_2[4] = '1';
LCD_SHOW_2[5] = '2';
LCD_SHOW_2[6] = 'H';
}else
{
LCD_SHOW_2[4] = '2';
LCD_SHOW_2[5] = '4';
LCD_SHOW_2[6] = 'H';
}
Column1 = 1;
LCD_STATE = 1;
Column2 = 1;
SHOW_CTRl_P_STATE = 9;
SHOW_CTRl_N_STATE = 3;
SHOW_CTRl_F_STATE = 5;
}
}
```
## 九、总结
本项目使用了分层、时间片轮、状态机思想完成了51电子钟的设计。其主要功能包含基本时间显示、温度显示、倒计时、秒表、闹钟、24h/12h选择,使用按键选择和设置相应的功能。本项目的代码对于状态机思想进行了比较深入的运用希望各位能够在其中有所收获。
下载链接:https://download.csdn.net/download/tang2010up/89467902