完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
蓝桥杯嵌入式设计与开发项目模拟赛题““电子定时器”的程序设计与调试”
(我也才备赛,可能会有写的或者说得不太对的地方。如果有错误的地方或者有什么好的建议,大家可以评论一下——集思广益,共同进步。)该题功能要求:通过按键设置定时时间,启动定时器后,开始倒计时;计时过程中,可以暂停、取消定时器。在定时时间内,按要求输出 PWM 信号和控制 LED 指示灯。 题目系统框图 设计任务以及要求简要说明 1.LCD显示 LCD 显示存储位置、定时时间和当前状态。停止状态显示为: Standby;设置时间时:Setting;运行时:Running;暂停时:Pause——并且系统预留 5 个存储位置用于存储常用的定时时间。 2.电子定时器设定——按键部分 使用 4 个按键,B1、B2、B3 和 B4——超过 0.8 秒为长按判定。①按键 B1 为存储位置切换键——每按一次,存储位置依次切换——1,2,3,4,5。②按键 B2 为时间位置(时、分、秒)切换键和存储键——短按切换,长按退出设置存储当前设定时间到当前存储位置。③按键 B3 为时、分、秒(按键 B2 确定当前位置)数字增加键——短按加一,长按快速增加——超出范围则从头循环。④按键 B4 为定时器启动键——第一次短按下运行启动,再短按下暂停,长按就退出——回到Standy。 3.电子定时器设定——PWM输出 定时器运行时,PA6 口输出 PWM 信号—— 信号频率为 1KHz,占空比为 80%——定时器停止或暂停时,停止输入 PWM 信号。 B2 按键:切换参数选项。B3 按键:增加当前参数项,增值为5——最高到95。B4 按键:减小当前参数项,减值为5——最低到5。 4.电子定时器设定—— LED控制 定时器运行时——LED 灯(LD1)以 0.5 秒的频率闪烁。定时器停止或暂停时,LED灯灭。 5.电子定时器设定——定时时间存储 设定(B2)好的定时时间存储在 EEPROM 中。掉电重启后,显示存储位置 1 的定时时间。 程序流程图·设计 解读: 在题目要求下,将必要的基本配置配置好。(主要是:按键的初始化配置,LED灯的初始化配置,24C02的初始化配置,定时器的初始化配置。) LCD显示部分的思路: 主要工作界面上的设计思路:然后首先处理的就是24c02中存储的值的读取和显示。然后判断按键进入不同的工作界面——运行、设置或切换存储界面。定时开始前后刷新问题可能在写的时候要注意一下——尽可能使用同一行的显示——如果可以就避免经常使用刷新吧。 PWM部分的思路: 没有运行时:我们就不输出,但是在程序一开始可以直接打开IO这些的使能。在运行时,我们将通道打开,输出指定PWM波。 LED部分的思路 在运行时时,LED灯按要求闪烁,指示当前运行情况。不运行时,我们就关闭LED灯。 状态图·设计 解读: 由LCD主持工作界面的显示,由按键控制存储界面切换、运行界面以及定时时间的设定。整个过程程序始终运行——当运行时,需要打开PWM通道输出和LED灯的闪烁;当没在运行时,要及时关闭相关操作。(LED在这个事件过程中属于指定指示项,要对应拼接在指定的那一个触发函数中——进行嵌套。) 主要代码分析 1.通用初始化部分 ps:我写的代码是按模块完成的——尽量使得主函数.c部分更简单。 1 ①按键初始化: ps:按键初始化很简单,主要是对后边按键的读取处理,才算重点。(每一套题中,按键基础配置.c部分基本都是一样的) 1 (.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灯全部打开。 1 (.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输出与否就好。 1 2 (.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**进行控制。 1 (.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 2.主要功能函数部分 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); } 3.中断部分 1.滴答中断部分 ps:这个在stm32f10x_it.c中,我也是直接将一些延时和标志位引用过来,实现延时或者等待一定时长的周期性动作。 1 //在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;//当前长按确定 } } 4.LCD显示部分 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行 } } 5.主函数体部分 (这次的主函数部分,我主要是想在延时部分偷个懒(实际并没能偷到/(ㄒ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); } 练习总结 1.模块总结 (对使用的模块的一个回顾)
(对一些逻辑思考的简单回顾) 从程序启动,显示第一存储位置的定时数据——接着受到按键的功能控制——进行设置或存储切换——甚至电子定时器的启动。关于LED和PWM输出,就是简单的嵌入指定状态就好——使用标志位,实时引用状态来控制开关就好。基于定时器启动,分俩钟情况:①一种由停止界面开始的存储值为定时时长的定时;②另外一种是,在设置界面临时设置定时值来启动定时。 结束 我知道,我才开始写blog,可能有很多地方做的不好,不过呢本意就是让自己学习到的东西和一些学习经验分享给大家,同时也是换一种方式记录自己的经验和成长。如果大家在阅读时发现任何问题,都可以评论或者其他方式联系我。集思广益,共同进步。 |
|
|
|
只有小组成员才能发言,加入小组>>
813 浏览 0 评论
1162 浏览 1 评论
2535 浏览 5 评论
2871 浏览 9 评论
移植了freeRTOS到STMf103之后显示没有定义的原因?
2719 浏览 6 评论
keil5中manage run-time environment怎么是灰色,不可以操作吗?
1110浏览 3评论
198浏览 2评论
465浏览 2评论
379浏览 2评论
M0518 PWM的电压输出只有2V左右,没有3.3V是怎么回事?
461浏览 1评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-27 15:19 , Processed in 0.989988 second(s), Total 78, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (威廉希尔官方网站 图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号