(我也才备赛,可能会有写的或者说得不太对的地方。如果有错误的地方或者有什么好的建议,大家可以评论一下
——集思广益,共同进步。)
该题功能要求:
通过按键设置定时时间,启动定时器后,开始倒计时;计时过程中,可以暂停、取消定时器。在定时时间内,按要求输出
PWM 信号和控制 LED 指示灯。
LCD 显示存储位置、定时时间和当前状态。停止状态显示为: Standby;设置时间时:Setting;运行时:Running;暂
停时:Pause——并且系统预留 5 个存储位置用于存储常用的定时时间。
使用 4 个按键,B1、B2、B3 和 B4——超过 0.8 秒为长按判定。
①按键 B1 为存储位置切换键——每按一次,存储位置依次切换——1,2,3,4,5。
②按键 B2 为时间位置(时、分、秒)切换键和存储键——短按切换,长按退出设置存储当前设定时间到当前存储位置。
③按键 B3 为时、分、秒(按键 B2 确定当前位置)数字增加键——短按加一,长按快速增加——超出范围则从头循环。
④按键 B4 为定时器启动键——第一次短按下运行启动,再短按下暂停,长按就退出——回到Standy。
定时器运行时,PA6 口输出 PWM 信号—— 信号频率为 1KHz,占空比为 80%——定时器停止或暂停时,停止输入 PWM
信号。
B2 按键:切换参数选项。
B3 按键:增加当前参数项,增值为5——最高到95。
B4 按键:减小当前参数项,减值为5——最低到5。
定时器运行时——LED 灯(LD1)以 0.5 秒的频率闪烁。
定时器停止或暂停时,LED灯灭。
设定(B2)好的定时时间存储在 EEPROM 中。
掉电重启后,显示存储位置 1 的定时时间。
在题目要求下,将必要的基本配置配置好。
(主要是:按键的初始化配置,LED灯的初始化配置,24C02的初始化配置,定时器的初始化配置。)
LCD显示部分的思路:
主要工作界面上的设计思路:
然后首先处理的就是24c02中存储的值的读取和显示。
然后判断按键进入不同的工作界面——运行、设置或切换存储界面。
定时开始前后刷新问题可能在写的时候要注意一下——尽可能使用同一行的显示——如果可以就避免经常使用刷新吧。
PWM部分的思路:
没有运行时:我们就不输出,但是在程序一开始可以直接打开IO这些的使能。
在运行时,我们将通道打开,输出指定PWM波。
LED部分的思路
在运行时时,LED灯按要求闪烁,指示当前运行情况。
不运行时,我们就关闭LED灯。
由LCD主持工作界面的显示,由按键控制存储界面切换、运行界面以及定时时间的设定。整个过程程序始终运行——当运行时,需要打开PWM通道输出和LED灯的闪烁;当没在运行时,要及时关闭相关操作。
(LED在这个事件过程中属于指定指示项,要对应拼接在指定的那一个触发函数中——进行嵌套。)
ps:我写的代码是按模块完成的——尽量使得主函数.c部分更简单。
①按键初始化:
ps:按键初始化很简单,主要是对后边按键的读取处理,才算重点。(每一套题中,按键基础配置.c部分基本都是一样的)
(.c部分)
#include "key.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
(.h部分)
#ifndef _KEY_H
#define _KEY_H
#include "stm32f10x.h"
#include "lcd.h"
#define KEY1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
#define KEY2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)
#define KEY3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)
#define KEY4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)
void KEY_Init(void);
//按键扫描
u8 key_scan(void);
void key_read(void);
//按键状态处理函数
//按键读取操作,返回指定状态
void key_cunchu_adress(void);//功能:改变存储位置/读取某一位置的存储的时间数据
void key_data_on_time_order(void);//功能:调整定时时间——操作映射值
void time_work(void);
#endif
②LED初始化
ps:此处的灯由于LCD共用引脚,所以呢,需要在每次使用LCD后,都要及时将LED全部拉高,不然会导致LED灯全部打开。
(.c部分)(每一套题中,按键基础配置.c部分基本都是一样的)
#include "led.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);//锁存器的引脚初始化
GPIO_InitStructure.GPIO_Pin = 0XFF00;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//8个LED灯引脚的初始化
GPIOD-> ODR |= (1<<2);//锁存器使能引脚控制
GPIOC-> ODR = 0XFFFF;//初始使能引脚拉高,灯灭
GPIOD-> ODR &=~(1<<2);
}
(.h部分)
#ifndef _LED_H
#define _LED_H
#include "stm32f10x.h"
//LED宏定义——引自GPIO.h
#define LED1 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define LED2 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define LED3 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define LED4 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define LED5 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define LED6 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define LED7 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define LED8 ((uint16_t)0x8000) /*!< Pin 15 selected */
void LED_Init(void);//LED初始化
void LED_Control(u16 LEDx,u8 state);//LED控制
void LED_Control_liangmie(void);//LED用于检测BUG——这里可以用来控制闪烁
void LED_SWITCH(void);//LED开关——也就是运行时启动闪烁,不运行时灯关
#endif
③PWM初始化:
ps:初始化部分都是固定的,需要注意的是后期的入口参数控制频率等,这里这次使用了固定频率(1000Hz)——所以
我就仅仅设置一个状态入口——控制PWM输出与否就好。
(.c部分)
(我就简单解释下,PWM初始化部分的含义)
#include "pwm.h"
//频率1000hz,占空比80%
void TIM3_PWM_Init(u8 state)
{
GPIO_InitTypeDef GPIO_InitStructure;//IO口初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//定时器初始化配置结构体
TIM_OCInitTypeDef TIM_OCInitStructure;//输出通道初始化结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//时钟使能
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//输出引脚配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//下拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//
TIM_TimeBaseInitStructure.TIM_Period = 999;//1ms一周期/1000hz
TIM_TimeBaseInitStructure.TIM_Prescaler = 71;
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0x0;//不分割
TIM_TimeBaseInitStructure.TIM_CounterMode = 0x0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;//通道一打开,模式二
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//有效电平低
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//通道使能
TIM_OCInitStructure.TIM_Pulse = 999*80/100;//占空比80%
TIM_OC1Init(TIM3, &TIM_OCInitStructure);//通道一初始化
if(state)
{
TIM_Cmd(TIM3,ENABLE);//定时器使能
}
else
{
TIM_Cmd(TIM3,DISABLE);//定时器关闭使能
}
}
(.h部分)
#ifndef _PWM_H
#define _PWM_H
#include "stm32f10x.h"
void TIM3_PWM_Init(u8 state);//频率1000hz,占空比80%
void PWM_Switch(void);//PWM开关
#endif
④24C02初始化
ps:在蓝桥杯比赛的开发板中,存储数据需要用到**i2c**总线下的24c02,所以需要使用**i2c**对**24c02**进行控制。
(.c部分)
#include"24c02.h"
//写入24c02的指定地址的指定数据的命令
void _24c02_Write(u8 address,u8 data)
{
I2CStart();//i2c开始
I2CSendByte(0xa0);//发送写入命令
I2CWaitAck();//等待任务完成
I2CSendByte(address);//发送地址
I2CWaitAck();
I2CSendByte(data);//发送数据
I2CWaitAck();
I2CStop();//i2c结束
}
//读取24c02的指定地址的数据的命令
u8 _24c02_Read(u8 address)
{
u8 temp;
I2CStart();
I2CSendByte(0xa0);//发送写入命令
I2CWaitAck();
I2CSendByte(address);//写入地址
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);//发送读取指令
I2CWaitAck();
temp = I2CReceiveByte();//读取指定数据
I2CWaitAck();
I2CStop();
return temp;//返回读取值
}
(.h部分)
#ifndef _24C02_H
#define _24C02_H
#include "stm32f10x.h"
#include "i2c.h"
void _24c02_Write(u8 adress,u8 data);
u8 _24c02_Read(u8 adress);
#endif
1.按键功能部分:
(功能放在对应的.c里边)
(我这部分写得比较仔细——有的部分是逻辑必然,但是却不一定是需要的标志位。这部分主要是在按键状态那里有体现。但是我想的是,将一个动作下所有状态进行一次类似遍历的方式处理下,这样会更容易理解吧。)
//800ms检测长按
u8 KEY2_on_state=0;//key2按下标志,只有按下才开始长按判断计时
u8 KEY2_CHECK=0;
u8 KEY3_on_state=0;//key3按下标志,只有按下才开始长按判断计时
u8 KEY3_CHECK=0;
u8 KEY4_on_state=0;//key4按下标志,只有按下才开始长按判断计时
u8 KEY4_CHECK=0;
u16 KEY_LONG_DELAY=800;
//短按扫描
u8 key_flag=0;//按键扫描标志
u16 key_short_delay=5;//按键扫描周期
u8 key_flag_down=0;//按键按下标志
//按键扫描
u8 key_scan(void)
{
static u8 key_back=1;//按键弹起标志
if(key_back&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//单次按键
{
if(key_flag)//允许扫描时
{
key_short_delay=5;key_flag=0;//扫描标志初始化
key_back=0;//按键按下
if(KEY1==0) {key_flag_down=1;return 1;}//按下标志一下
if(KEY2==0) {key_flag_down=1;KEY2_on_state=1;return 2;}//按下同时产生长按判断
if(KEY3==0) {key_flag_down=1;KEY3_on_state=1;return 3;}
if(KEY4==0) {key_flag_down=1;KEY4_on_state=1;return 4;}
}
}else if(key_back==0&&KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1)
{ //当没有按键按下(松开)时,停止长按判断,并执行反弹确认
key_back=1;//反弹确认,可再次按下
//key_flag_down=0;//没有按下
KEY2_on_state=0;//长按标志清除
KEY3_on_state=0;
KEY4_on_state=0;
KEY_LONG_DELAY=800;//长按判断时长——0.8s
}
if(KEY2_CHECK)//长按KEY2标志
{
if(KEY2==0) {KEY2_CHECK=1;return 2;}
//满足长按后,继续返回有效值——不再执行像上边单按一样需要弹起再按下才有新输出
else {KEY2_CHECK=0;KEY_LONG_DELAY=800;}//重新计数
}
if(KEY3_CHECK)//长按KEY3标志
{
if(KEY3==0) {KEY3_CHECK=1;return 3;}
else {KEY3_CHECK=0;KEY_LONG_DELAY=800;}
}
if(KEY4_CHECK)//长按KEY4标志
{
if(KEY4==0) {KEY4_CHECK=1;return 4;}
else {KEY4_CHECK=0;KEY_LONG_DELAY=800;}
}
return 0;
}
/************************************/
/****** 实际状态参数表 ********/
/************************************/
u8 data_exchange_flag=0;//存储切换标志
u8 data_cunchu_flag=0;//当前数据将存储到当前存储位置的标志——长按状态下
u8 time_set_flag=0;//时间设置标志
u8 time_set_ups_flag=0;//时间快速上调单位的标志——长按状态下
u8 time_set_Oneup_flag=0;//时间上调1个单位的标志
u8 time_end_flag=1;//定时结束标志——长按状态下
u8 time_start_flag=0;//定时开始标志
u8 time_wait_flag=0;//定时暂停标志
u8 time_set_exchange_flag=0;//时分秒切换标志
u8 time_run_setpage_flag=0;//判定此时是不是由设置界面直接启动定时器的标志
/******************状态设置函数********************/
//
//状态参数表
/************************************/
//按键状态处理函数
//按键读取操作,返回指定状态
void key_read(void)
{
static u8 time_open_off_flag=0;//定时器开始暂停的转换标志
u8 key_return=0;//按键值读取位
key_return=key_scan();
if(time_end_flag)//定时结束时
time_open_off_flag=0;//定时器不再转换开始——定时器状态为暂停,对应停止位
switch(key_return)
{
case 1://按键1
{
//仅仅在停止界面(存储界面)时有效
if(time_end_flag&&time_set_flag==0&&time_start_flag==0&&time_wait_flag==0){
//time_end_flag=1;//此时为定时停止状态
data_exchange_flag=1;//存储位置具体在后边处理,这里仅仅标记允许存储位置切换
time_set_exchange_flag=0;//时分秒切换标志——初始化参数项切换标志
data_cunchu_flag=0;//不标志数据存储——当前按键下仅仅是移动存储位置
//每按一次,存储位置依次以 1、2、3、4、5 循环切
//换,切换后定时时间设定为当前位置存储的时间。
}
}break;
case 2:
{
if(KEY2_CHECK&&time_set_flag&&time_end_flag==0&&time_start_flag==0&&time_wait_flag==0)//长按2——//设置界面才反应
{
time_end_flag=1;//此时为定时停止状态
data_cunchu_flag=1;//存储标志——允许存储
time_set_flag=0;//存储数据——退出设置界面
time_set_exchange_flag=0;//时分秒切换标志——初始化
time_run_setpage_flag=0;//判定此时不会由设置界面直接启动定时器
}
if((KEY2_CHECK==0)&&time_start_flag==0&&time_wait_flag==0)//短按——进入设置界面——停止界面有效
{
time_run_setpage_flag=1;//判定此时可以由设置界面直接启动定时器——即未储存直接定时
time_end_flag=0;//此时定时停止状态关闭(改为其它状态)
data_cunchu_flag=0;//只是设置并不会存储
data_exchange_flag=0;//存储切换标志
time_set_exchange_flag=1;//时分秒切换标志
time_set_flag=1;
//时间设置标志:后边处理,第一次按下进入设置界面的秒设置
//然后置零该标志,下一次按下,移动到分设置……
}
}break;
case 3:
{
if(KEY3_CHECK&&time_set_flag&&time_end_flag==0&&time_start_flag==0&&time_wait_flag==0)
//长按2——//设置界面才反应
{
time_run_setpage_flag=1;//保持可直接运行标志
time_set_exchange_flag=0;//时分秒切换标志——在按下增加值的按键时,不可切换
data_exchange_flag=0;//存储切换标志——置零,不可切换
data_cunchu_flag=0;//不存储数据
time_set_flag=1;//时间设置标志
time_set_ups_flag=1;//快速增加
time_set_Oneup_flag=0;//关闭单次增加
}
if((KEY3_CHECK==0)&&time_set_flag&&time_end_flag==0&&time_start_flag==0&&time_wait_flag==0)
//短按2——//设置界面才反应
{
time_run_setpage_flag=1;//保持可直接运行标志
time_set_exchange_flag=0;//时分秒切换标志——在按下增加值的按键时,不可切换
data_exchange_flag=0;//存储切换标志——置零,不可切换
data_cunchu_flag=0;//不存储数据
time_set_flag=1;//时间设置标志
time_set_ups_flag=0;//快速增加——关闭
//快速增加不开启,即打断按键计时,不能继续块加
time_set_Oneup_flag=1;//按一下增加一次
}
}break;
case 4:
{
if(KEY4_CHECK)//长按2
{
time_set_exchange_flag=0;//时分秒切换关闭
data_exchange_flag=0;//存储切换标志
data_cunchu_flag=0;
time_set_flag=0;//时间设置标志
time_open_off_flag=0;//转换标志也要清零
time_end_flag=1;//定时器结束运行//此时为定时停止状态
time_start_flag=0;//定时结束,必然不再有开始和暂停
time_wait_flag=0;//
}
else
{
if(time_open_off_flag==0)//第一次按下是暂停——之后按下翻转状态
{
time_set_exchange_flag=0;//时分秒切换关闭
time_open_off_flag=1;//定时器正在运行——允许暂停
data_exchange_flag=0;//存储切换标志——关闭
data_cunchu_flag=0;//存储标志——不存储时间
time_set_flag=0;//时间设置标志
time_end_flag=0;//此时为定时运行状态——不是停止状态
time_start_flag=1;//开始状态,暂停状态关闭
time_wait_flag=0;}
else
{
time_open_off_flag=0;//定时器已暂停——允许启动
data_exchange_flag=0;//存储切换标志——关闭
data_cunchu_flag=0;//存储标志——不存储时间
time_set_flag=0;//时间设置标志
time_end_flag=0;//此时为定时暂停状态——不是停止状态
time_start_flag=0;//暂停状态,启动状态关闭
time_wait_flag=1;//暂停标志位
}//再按一下是暂停,开始状态关闭
}
}break;
default:
break;
}
}
/************************************/
//按键状态识别具体操作函数
//按键状态识别 1
/*当前函数主要引用的参数——来源lcd.h*/
extern u8 data_xuhao;//1~5->对应存储位置
//功能:改变存储位置/读取某一位置的存储的时间数据
void key_cunchu_adress(void)
{
if(data_exchange_flag&&time_end_flag)
{
data_exchange_flag=0;//每次切换后,将标志位置0
data_xuhao++;//进入下一个存储位置
}
if(data_xuhao==6)
data_xuhao=1;//返回第一个存储位置
}
//按键状态识别 2
/*当前函数主要引用的参数——来源lcd.h*/
extern int data_on_time_yinshe[4];//映射数据存储位置——0-2时分秒
//暂存时间值的地方——引自lcd.h
extern int data_time[5][3];//1~5->对应存储位置
u8 up_lianxu_falg=0;//连续增加的状态
u8 up_lianxu_delay=3;//连续增加的周期
u8 linshi_hh_val=0;//当前显示时间值的存储位
u8 linshi_mm_val=0;
u8 linshi_ss_val=0;
u32 time_runnig=5;//略微设一点值,除去启动时,导致的界面替换
u8 time_running_flag=0;//定时器运行标志
u8 time_run_ok_flag=0;//定时器运行完成标志
//功能:调整定时时间——操作映射值
//时间数据处理部分
void key_data_on_time_order(void)
{
static int key_on_time_set_shunxu=4;//设置顺序——3-秒,2-分,1-时,这样的设置顺序
static int val_shuhao=0;//存储位置——序号
static int midle_val_hh=0;//当前位置的hour值
static int midle_val_mm=0;//当前位置的min值
static int midle_val_ss=0;//当前位置的second值
if(time_set_flag==0)//退出设置界面时
key_on_time_set_shunxu=4;//初始化设置顺序
if(time_set_flag&&time_set_exchange_flag)//在设置界面时/存储位置切换时进入
//该部分放在LCD显示前,LCD中会置零time_set_exchange_flag
//所以这里无需置零
{
val_shuhao=data_xuhao;//得到当前存储序号(位置)
midle_val_hh=data_time[data_xuhao-1][0];
//暂存配置值,将当前位置的时间预先存下来,等确定后边的时间数据需要存储再改变
//否则,后边就直接将该值又赋给当前位置的data_time中
midle_val_mm=data_time[data_xuhao-1][1];
midle_val_ss=data_time[data_xuhao-1][2];
key_on_time_set_shunxu--;//顺序值减一,满足调用设置——1-时位,2-分位,3-秒位
if(key_on_time_set_shunxu==0)//如果到零了,回到3-秒位,而不是4
key_on_time_set_shunxu=3;
}
switch(key_on_time_set_shunxu)
{
case 1:
//时设置
if(time_set_Oneup_flag&&time_set_ups_flag==0)//单按条件下,当前值直接加一就好
{time_set_Oneup_flag=0;data_time[data_xuhao-1][0]++;}
if(time_set_Oneup_flag==0&&time_set_ups_flag)
//连续按键,就要进入其中——同时置零快加标志,反复操作,直到不再有快加标志出现
{
time_set_ups_flag=0;//快加标志清零
if(up_lianxu_falg)//快速加,每3ms加一次
{
up_lianxu_falg=0;//取消标记
up_lianxu_delay=3;
data_time[data_xuhao-1][0]++;//暂存值加一
}
}
if(data_time[data_xuhao-1][0]==24)//小时位置数字归零,其余位置值不变
data_time[data_xuhao-1][0]=0;
break;
case 2://分设置
if(time_set_Oneup_flag&&time_set_ups_flag==0)//单按条件下,当前值直接加一就好
{data_time[data_xuhao-1][1]++;time_set_Oneup_flag=0;}
if(time_set_Oneup_flag==0&&time_set_ups_flag)
//连续按键,就要进入其中——同时置零快加标志,反复操作,直到不再有快加标志出现
{
time_set_ups_flag=0;//快加标志清零
if(up_lianxu_falg)//快速加,每20ms加一次
{
up_lianxu_falg=0;//取消标记
up_lianxu_delay=3;
data_time[data_xuhao-1][1]++;//暂存值加一
}
}
if(data_time[data_xuhao-1][1]==60)//分归零,其余位置值不变
data_time[data_xuhao-1][1]=0;
break;
case 3://秒设置
if(time_set_flag&&time_set_Oneup_flag&&time_set_ups_flag==0)//单按条件下,当前值直接加一就好
{data_time[data_xuhao-1][2]++;time_set_Oneup_flag=0;}
if(time_set_flag&&time_set_Oneup_flag==0&&time_set_ups_flag)
//连续按键,就要进入其中——同时置零快加标志,反复操作,直到不再有快加标志出现
{
time_set_ups_flag=0;//快加标志清零
if(up_lianxu_falg)//快速加,每20ms加一次
{
up_lianxu_falg=0;//取消标记
up_lianxu_delay=3;
data_time[data_xuhao-1][2]++;//暂存值加一
}
}
if(data_time[data_xuhao-1][2]==60)//秒归零,其余位置值不变
data_time[data_xuhao-1][2]=0;
break;
default:
break;
}
linshi_hh_val=data_time[data_xuhao-1][0];
//暂存时间设置值,这里的数据为定时器在设置界面留下的不保存的时间数据
//以备在设置界面未保存就启动定时的操作
linshi_mm_val=data_time[data_xuhao-1][1];
linshi_ss_val=data_time[data_xuhao-1][2];
//功能:映射值存储到实际存储位置
if(time_end_flag&&data_cunchu_flag==0&&val_shuhao==data_xuhao)
//当存储位置和当前留下的序号一致,位置确定无误后,设置结束,但不需要存储时
//将之前的值覆盖当前设置的值
//也就是简单点——保持原来存储位置本身的值
{
data_time[data_xuhao-1][0]=midle_val_hh;//本来的小时数据
data_time[data_xuhao-1][1]=midle_val_mm;
data_time[data_xuhao-1][2]=midle_val_ss;
}
}
u8 look_string[20];
void time_work(void)
{
static u8 first_in=1;//判断是否是一次新的定时,1——为新定时
if(time_set_flag)//在设置界面时,就先预存短期未存储值为计时数。
time_runnig=(linshi_hh_val*3600+linshi_mm_val*60+linshi_ss_val)*1000;//该值用以在滴答中断中计时——1下1ms
//先存未保存的调整值,如果直接计时,就直接用
//如果要用存储值,就在后边调储存数据data_time来计算实际运行时间
if(time_run_setpage_flag)//在设置界面直接进入定时运行的情况判定
{
if(time_end_flag)first_in=1;
//每回到停止界面就初始化标志位
if(time_start_flag&&time_running_flag==0)//是一次新的运行,而不是暂停后又开启
{
time_running_flag=1;//运行标志
if(first_in)//将新定时标志置0
{
first_in=0;
}
}
if(time_wait_flag)//暂停模式下
{
time_running_flag=0;//置零运行标志
}
if(time_run_ok_flag)//一次完整的定时运行完成
{
time_end_flag=1;//回到停止界面——所以标志置1
time_start_flag=0;//停止态,启动标志关闭——置0
time_wait_flag=0;//停止态,暂停标志也关闭
time_run_ok_flag=0;//进入运行完成判断了——所以完成标志置0
time_run_setpage_flag=0;//停止界面自然没有在设置界面中了——所以置0
first_in=1;//再次进入,就是一次新的运行
}
}
else//由停止界面进入定时模式,也就是开始界面进入的定时
{
if(time_end_flag)first_in=1;//回到停止界面,都是新的定时
if(time_start_flag&&time_running_flag==0)//开始运行,是一次新的运行——之前运行标志为0
{
time_running_flag=1;//运行标志
if(first_in)//停止界面进入——计时值为存储值
{
first_in=0;//当前本次定时之后的操作不再属于新运行
time_runnig=(data_time[data_xuhao-1][0]*3600+data_time[data_xuhao-1][1]*60+data_time[data_xuhao-1][2])*1000;
//获取当前存储的定时值
}
}
if(time_wait_flag)//暂停情况
{
time_running_flag=0;
}
if(time_run_ok_flag)//运行完成情况
{
time_end_flag=1;//结束标志——开始界面的标志
time_start_flag=0;
time_wait_flag=0;
time_run_ok_flag=0;
time_run_setpage_flag=0;
first_in=1;//下一次运行为新运行
}
}
}
2.LED功能部分
extern u8 time_start_flag;//定时器启动标志
//LED控制函数,1点亮
void LED_Control(u16 LEDx,u8 state)
{
if(state){
GPIOD-> ODR |= (1<<2);
GPIO_ResetBits(GPIOC,LEDx);
GPIOD-> ODR &=~(1<<2);}
else{
GPIOD-> ODR |= (1<<2);
GPIO_SetBits(GPIOC,LEDx);
GPIOD-> ODR &=~(1<<2);}
}
//检查BUG用——但这里直接使用来实现灯亮灭(闪烁)
void LED_Control_liangmie(void)
{
static u8 led_flag_shan=0;
led_flag_shan=!led_flag_shan;//状态翻转
if(led_flag_shan)
{
GPIOD-> ODR |= (1<<2);
GPIOC-> ODR |=0xff00;
GPIOD-> ODR &=~(1<<2);
GPIOD-> ODR |= (1<<2);
GPIO_ResetBits(GPIOC,LED4);
GPIOD-> ODR &=~(1<<2);
}
else
{
GPIOD-> ODR |= (1<<2);
GPIOC-> ODR |=0xff00;
GPIOD-> ODR &=~(1<<2);
}
}
u16 led_delay=500;//指示灯闪烁延时
u8 led_fflag=0;//亮灭状态切换标志——也就是闪烁标志
//将被引用到滴答中断中使用
void LED_SWITCH(void)//LED指示灯开关
{
if(time_start_flag)
{
if(led_fflag)//切换标志产生
{
led_fflag=0;//置零标志,等待下一次标志来到
led_delay=500;//初始化闪烁周期
LED_Control_liangmie();//闪烁
}
}
else
{
led_delay=500;//延时归零
GPIOC-> ODR = 0XFFFF;//熄灯
GPIOD-> ODR |= (1<<2);
GPIOD-> ODR &=~(1<<2);
}
}
3.PWM功能部分
extern u8 time_start_flag;//定时器启动标志
//PWM使能开关
void PWM_Switch(void)
{
if(time_start_flag)//启动时打开
TIM3_PWM_Init(1);
else//不启动时关闭
TIM3_PWM_Init(0);
}
1.滴答中断部分
ps:这个在stm32f10x_it.c中,我也是直接将一些延时和标志位引用过来,实现延时或者等待一定时长的周期性动作。
//在stm32f10x_it.c中添加引用的.h文件
#include "key.h"
#include "lcd.h"
/***********************滴答中断的修改**************************/
/*******参数引用部分********/
//800ms检测长按——以及状态
extern u8 KEY2_on_state;//key2按下标志,只有按下才开始长按判断计时
extern u8 KEY2_CHECK;//确定当前键长按
extern u8 KEY3_on_state;//key3按下标志,只有按下才开始长按判断计时
extern u8 KEY3_CHECK;//确定当前键长按
extern u8 KEY4_on_state;//key4按下标志,只有按下才开始长按判断计时
extern u8 KEY4_CHECK;//确定当前键长按
extern u16 KEY_LONG_DELAY;//长按判断周期
//短按扫描-状态
extern u8 key_flag;//按键扫描标志
extern u16 key_short_delay;//按键扫描周期
//key状态实际操作部分参数引用
extern u8 up_lianxu_delay;//连续按键——读取周期
extern u8 up_lianxu_falg;//连续按键——读取标志
/***************************/
//定时变量参数引用
extern u32 time_runnig;//当前运行时间
extern u8 time_running_flag;//运行标志
extern u8 time_run_ok_flag;//运行完成标志
extern u16 led_delay;//闪烁周期
extern u8 led_fflag;//闪烁标志
/***************************************/
void SysTick_Handler(void)
{
TimingDelay--;
if(--key_short_delay==0)//按键扫描
key_flag=1;
if(--up_lianxu_delay==0)//按键长按后连续读取扫描
up_lianxu_falg=1;
if(time_running_flag)//正在运行的标志
{
time_runnig--;//用这个显示定时效果——定时时长
}
if(time_runnig==0)//运行标志为0,则说明运行完成
{
time_run_ok_flag=1;//完成标志
}
if(--led_delay==0)//LED闪烁标志操作
led_fflag=1;//开启LED闪烁变化
key_long();//长按计时,返回状态值
}
// 1
//功能:对长按的判定
void key_long(void)
{
if(KEY2_on_state&&(--KEY_LONG_DELAY==0))
//满足长按所有条件——该键按下,且持续800ms未弹起,执行判决
{
KEY2_CHECK=1;//当前长按确定
}
if(KEY3_on_state&&(--KEY_LONG_DELAY==0))
{
KEY3_CHECK=1;//当前长按确定
}
if(KEY4_on_state&&(--KEY_LONG_DELAY==0))
{
KEY4_CHECK=1;//当前长按确定
}
}
ps:我把显示部分写在lcd.c中的,所以写完之后需要该函数添加声明到lcd.h中。
/**********************************自己定义的状态参数引用***************************************/
extern u8 time_end_flag;//定时结束标志——长按状态下,LCD状态改变——Standby
extern u8 time_set_flag;//时间设置标志,LCD高亮显示变化——Setting
extern u8 time_start_flag;//定时开始标志——LCD状态改变——Running
extern u8 time_wait_flag;//定时暂停标志——LCD状态改变——Pause
extern u8 time_set_exchange_flag;////时分秒切换标志
/**************************************LCD模块所定义参数**************************************/
u8 data_xuhao=1;//1~5->对应存储位置data_time中的序列值:0~4
int data_time[5][3]={{20,10,20},{13,0,10},{15,0,25},{11,0,15},{8,0,30}};//存储定时数据,[3]=0~3对应时分秒
extern u32 time_runnig;//定时时长
u8 lcd_string[6][20];//存储需要输出的格式——状态,时间,存储位置
/*********************************************************************************************/
void LCD_SHOW_SET_TIME_S(void)
{
static int set_falg=4;//设置显示标志位
if(time_end_flag)//开始界面——停止(存储)界面
{
time_runnig=(data_time[data_xuhao-1][0]*3600+data_time[data_xuhao-1][1]*60+data_time[data_xuhao-1][2])*1000;
//当处在定时器开始界面时,当前定时时长要初始化为当前存储的定时总时长
set_falg=4;//设置显示标志位置
sprintf((char*)lcd_string[0]," NUM: %d ",data_xuhao);//当前存储位置
sprintf((char*)lcd_string[1]," %d: %d :%d ",data_time[data_xuhao-1][0],data_time[data_xuhao-1][1],data_time[data_xuhao-1][2]);//当前定时值
sprintf((char*)lcd_string[2]," Standby ");//当前状态
LCD_SetTextColor(White);//文字颜色
LCD_DisplayStringLine(Line1,lcd_string[0]);//显示存储位置在2行
LCD_DisplayStringLine(Line5,lcd_string[1]);//显示时间在5行
LCD_DisplayStringLine(Line8,lcd_string[2]);//显示状态在6行
}
//************设置状态下,执行映射数据显示**********************
if(time_set_flag)//设置界面
{
if(time_set_exchange_flag==1)//满足设置位置(当前设置项)的切换
{
time_set_exchange_flag=0;
set_falg--;//设置项序号——3对应秒,2对应分,1对应时
if(set_falg==0) //不允许存在0,而是回到3位置
{
set_falg=3;
}
LCD_ClearLine(Line5);//清除一下显示
}//按下设置,减一,换一个高显示
switch(set_falg)
//设置高显的选择——我这里是做标志,实在要高显可以用字符显示函数,我的博客里有,有需要可以看下
{
case 3://秒高显示
sprintf((char*)lcd_string[3]," __ ");
LCD_SetTextColor(Green);
LCD_DisplayStringLine(Line6,lcd_string[3]);//高显秒
break;
case 2://分高显
sprintf((char*)lcd_string[4]," __ ");
LCD_SetTextColor(Green);
LCD_DisplayStringLine(Line6,lcd_string[4]);//高显分
break;
case 1://时高显
sprintf((char*)lcd_string[5]," __ ");
LCD_SetTextColor(Green);
LCD_DisplayStringLine(Line6,lcd_string[5]);//高显时
break;
default:
break;
}
sprintf((char*)lcd_string[1]," %d: %d :%d ",data_time[data_xuhao-1][0],data_time[data_xuhao-1][1],data_time[data_xuhao-1][2]);//当前时间显示内容——实时设置值
sprintf((char*)lcd_string[0]," NUM: %d ",data_xuhao);//存储序号
sprintf((char*)lcd_string[2]," Setting ");//当前状态
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line5,lcd_string[1]);//显示时间在4行
LCD_DisplayStringLine(Line1,lcd_string[0]);//显示存储位置在2行
LCD_DisplayStringLine(Line8,lcd_string[2]);//显示状态在6行
}
if(time_start_flag)//************开始状态下,执行映射数据显示**********************
{
set_falg=4;//设置显示标志位置零,不开启
//在设置界面以外的部分,设置显示位标志要设置满
//以方便下一次的进入
LCD_ClearLine(Line6);
sprintf((char*)lcd_string[0]," NUM: %d ",data_xuhao);
sprintf((char*)lcd_string[1]," %d: %d :%d ",time_runnig/3600000,time_runnig%3600000/60000,time_runnig%3600000%60000/1000);
sprintf((char*)lcd_string[2]," Running ");//显示剩余计时值
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line1,lcd_string[0]);//显示存储位置在2行
LCD_DisplayStringLine(Line5,lcd_string[1]);//显示时间在4行
LCD_DisplayStringLine(Line8,lcd_string[2]);//显示状态在6行
}
if(time_wait_flag)//暂停状态下,也是执行映射数据显示
{
set_falg=4;//设置显示标志位置零,不开启
//在设置界面以外的部分,设置显示位标志要设置满
//以方便下一次的进入
LCD_ClearLine(Line6);
sprintf((char*)lcd_string[0]," NUM: %d ",data_xuhao);
sprintf((char*)lcd_string[1]," %d: %d :%d ",time_runnig/3600000,time_runnig%3600000/60000,time_runnig%3600000%60000/1000);
sprintf((char*)lcd_string[2]," Pause ");//显示剩余计时值
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line1,lcd_string[0]);//显示存储位置在2行
LCD_DisplayStringLine(Line5,lcd_string[1]);//显示时间在4行
LCD_DisplayStringLine(Line8,lcd_string[2]);//显示状态在6行
}
}
(这次的主函数部分,我主要是想在延时部分偷个懒(实际并没能偷到/(ㄒoㄒ)/~~)。这次我就是在主函数钟直接进行数据存储和读取的函数体的设计,但是我还是觉得大家分模块的,有联系的写会好些——因为那样会更容易理清思路,哪个哪个在哪儿,它们怎么用的。)
#include "stm32f10x.h"
#include "lcd.h"
#include "24c02.h"
#include "led.h"
#include "key.h"
#include "pwm.h"
#include "stdio.h"
u32 TimingDelay = 0;
extern u8 data_time[5][3]; //存储位置的时间数据
extern u8 data_xuhao;//存储地址——序号
extern u8 data_cunchu_flag;//存储标志位
void _24c02_time_set_cunchu_now_page(void);//写入当前设置的存储位置的数据
void _24c02_time_set_cunchu_all_page(void);//存储当前所有数据
//读取端电存储的所有数据
void _24c02_Read_time_set_cunchu_all_page(void);//读取所有存储位置的数据
void Delay_Ms(u32 nTime);
//Main Body
int main(void)
{
STM3210B_LCD_Init();
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
SysTick_Config(SystemCoreClock/1000);
i2c_init();//IIC初始化
TIM3_PWM_Init(0);//频率1000hz,占空比80%,开始不使能
KEY_Init();//按键使能
LED_Init();//LED初始化
_24c02_Read_time_set_cunchu_all_page();//读取所有存储位置的值
while(1)
{
key_read();//按键状态获取
LED_SWITCH();//LED指示灯动作
PWM_Switch();//PWM指示动作
key_cunchu_adress();//改变存储位置/读取某一位置的存储的时间数据
key_data_on_time_order();//调整定时时间——操作映射值
_24c02_time_set_cunchu_now_page();//写入当前正在设置的存储位置的存储数据
time_work();//定时器的定时工作部分
LCD_SHOW_SET_TIME_S();//LCD显示
if(data_cunchu_flag)//如果没有执行存储,那断电不存储——相当于,每一次存储都检查两遍是否存储完成
//这里是由于我考虑的时候,担心存储异常又加了1次,当然,如果你们在思考的时候觉得这部分没必要
//是完全可以剔除的
{
_24c02_time_set_cunchu_all_page();//存储/更新当前可存储的数据
}
}
}
//写入当前设置的存储数据 4
//要在映射数据存储之后执行该函数才有意义
void _24c02_time_set_cunchu_now_page(void)
{
if(data_cunchu_flag)
{
data_cunchu_flag=0;
switch(data_xuhao)
{
case 1:
_24c02_Write(0x00,data_time[0][0]);//写入第一存储位置的数据
Delay_Ms(5);
_24c02_Write(0x01,data_time[0][1]);//
Delay_Ms(5);
_24c02_Write(0x02,data_time[0][2]);//
Delay_Ms(5);
break;
case 2:
_24c02_Write(0x03,data_time[1][0]);//写入第二存储位置的数据
Delay_Ms(5);
_24c02_Write(0x04,data_time[1][1]);//
Delay_Ms(5);
_24c02_Write(0x05,data_time[1][2]);//
Delay_Ms(5);
break;
case 3:
_24c02_Write(0x06,data_time[2][0]);//写入第三存储位置的数据
Delay_Ms(5);
_24c02_Write(0x07,data_time[2][1]);//
Delay_Ms(5);
_24c02_Write(0x08,data_time[2][2]);//
Delay_Ms(5);
break;
case 4:
_24c02_Write(0x09,data_time[3][0]);//写入第四存储位置的数据
Delay_Ms(5);
_24c02_Write(0x0A,data_time[3][1]);//
Delay_Ms(5);
_24c02_Write(0x0B,data_time[3][2]);//
Delay_Ms(5);
break;
case 5:
_24c02_Write(0x0C,data_time[4][0]);//写入第五存储位置的数据
Delay_Ms(5);
_24c02_Write(0x0D,data_time[4][1]);//
Delay_Ms(5);
_24c02_Write(0x0E,data_time[4][2]);//
Delay_Ms(5);
break;
default:
break;
}
}
}
//存储所有数据
void _24c02_time_set_cunchu_all_page(void)
{
_24c02_Write(0x00,data_time[0][0]);//写入第一存储位置的数据
Delay_Ms(5);
_24c02_Write(0x01,data_time[0][1]);//
Delay_Ms(5);
_24c02_Write(0x02,data_time[0][2]);//
Delay_Ms(5);
_24c02_Write(0x03,data_time[1][0]);//写入第二存储位置的数据
Delay_Ms(5);
_24c02_Write(0x04,data_time[1][1]);//
Delay_Ms(5);
_24c02_Write(0x05,data_time[1][2]);//
Delay_Ms(5);
_24c02_Write(0x06,data_time[2][0]);//写入第三存储位置的数据
Delay_Ms(5);
_24c02_Write(0x07,data_time[2][1]);//
Delay_Ms(5);
_24c02_Write(0x08,data_time[2][2]);//
Delay_Ms(5);
_24c02_Write(0x09,data_time[3][0]);//写入第四存储位置的数据
Delay_Ms(5);
_24c02_Write(0x0A,data_time[3][1]);//
Delay_Ms(5);
_24c02_Write(0x0B,data_time[3][2]);//
Delay_Ms(5);
_24c02_Write(0x0C,data_time[4][0]);//写入第五存储位置的数据
Delay_Ms(5);
_24c02_Write(0x0D,data_time[4][1]);//
Delay_Ms(5);
_24c02_Write(0x0E,data_time[4][2]);//
Delay_Ms(5);
}
//读取端电存储的所有数据
void _24c02_Read_time_set_cunchu_all_page(void)
{
data_time[0][0]=_24c02_Read(0x00);
Delay_Ms(5);
data_time[0][1]=_24c02_Read(0x01);
Delay_Ms(5);
data_time[0][2]=_24c02_Read(0x02);
Delay_Ms(5);
data_time[1][0]=_24c02_Read(0x03);
Delay_Ms(5);
data_time[1][1]=_24c02_Read(0x04);
Delay_Ms(5);
data_time[1][2]=_24c02_Read(0x05);
Delay_Ms(5);
data_time[2][0]=_24c02_Read(0x06);
Delay_Ms(5);
data_time[2][1]=_24c02_Read(0x07);
Delay_Ms(5);
data_time[2][2]=_24c02_Read(0x08);
Delay_Ms(5);
data_time[3][0]=_24c02_Read(0x09);
Delay_Ms(5);
data_time[3][1]=_24c02_Read(0x0A);
Delay_Ms(5);
data_time[3][2]=_24c02_Read(0x0B);
Delay_Ms(5);
data_time[4][0]=_24c02_Read(0x0C);
Delay_Ms(5);
data_time[4][1]=_24c02_Read(0x0D);
Delay_Ms(5);
data_time[4][2]=_24c02_Read(0x0E);
Delay_Ms(5);
}
//延时函数部分
void Delay_Ms(u32 nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
(对使用的模块的一个回顾)
按键:
初始化配置后,就是*按键扫描*配置,在基本功能函数写完——就要开始具体功能函数部分的设计。按键主体部分
就是对应按键按下后,对相应的状态进行标记 (我是用状态来执行的,所以呢,大家如果对状态标志不是很
喜欢的,可以直接对应把具体的功能动作写在该按键按下的部分。)。
在标记完成后,执行相应的*定时器存储位置的切换、定时值的设置/存储、以及定时器的启动*——当然这些个函数
也是可以写在任意部分的,只是呢,我习惯于把逻辑相关放在一 起, 由于要设置时和界面切换、以及运行会用
到按键,所以就放在按键这里。
LED:
LED部分除了初始化,就是对LED的控制函数的设计,实现LED闪烁——闪烁是按照定时开关实现的(LED_SWITCH)。
PWM:
在初始化配置结束之后,主要就是对PWM输出的控制——是按照程序运行与否——关闭定时器/打开定时器实现的。
24C02:
这个部分就没什么东西了,有的只是将一些具体存储的函数的设计和定义,比较简单,基本都是固定的,需要好好
记住基本功能的配置——写入和读取指定位置的数据就好了。
(对一些逻辑思考的简单回顾)
从程序启动,显示第一存储位置的定时数据——接着受到按键的功能控制——进行设置或存储切换——甚至电子定时器的启动。
关于LED和PWM输出,就是简单的嵌入指定状态就好——使用标志位,实时引用状态来控制开关就好。
基于定时器启动,分俩钟情况:
①一种由停止界面开始的存储值为定时时长的定时;
②另外一种是,在设置界面临时设置定时值来启动定时。
我知道,我才开始写blog,可能有很多地方做的不好,不过呢本意就是让自己学习到的东西和一些学习经验分享给
大家,同时也是换一种方式记录自己的经验和成长。
如果大家在阅读时发现任何问题,都可以评论或者其他方式联系我。
集思广益,共同进步。