这套题也是我当你省赛时候做的题,也是通过这套题进入拿到省一进入国赛。总体来说,这套题是比较简单的,尤其是在配置上基本没涉及一些比较难的模块,考的方向偏向逻辑,大部分人都在长短按键处被卡死,这套题也是蓝桥杯第一次考长短按键;并且我去参加国赛的时候以为省赛考过长短按键国赛应该就不会再考了,想不到国赛又考了一次长短按键,也算是蓝桥杯对于赛题的一个创新吧。
1.1 使用 CT117E 嵌入式竞赛板,完成试题功能的程序设计与调试;
1.2 设计与调试过程中,可参考组委会提供的“资源数据包”;
1.3 Keil 工程文件以准考证号命名,完成设计后,提交完整、可编译的 Keil工程文件到服务器。
通过按键设置定时时间,启动定时器后,开始倒计时;计时过程中,可以暂停、取消定时器。在定时时间内,按要求输出 PWM 信号和控制 LED 指示灯。系统框图如图 1 所示:
1、LCD 显示
LCD 显示存储位置、定时时间和当前状态。系统预留 5 个存储位置用于存储常用的定时时间。当定时器停止时,当前状态为 Standby;当系统正在设置时间时,当前状态为 Setting;当定时器运行时,当前状态为 Running,定时器暂停时,当前状态为 Pause。
2、按键功能
系统使用 4 个按键,B1、B2、B3 和 B4。
按键 B1 为存储位置切换键。每按一次,存储位置依次以 1、2、3、4、5循环切换,切换后定时时间设定为当前位置存储的时间。
按键 B2 为时间位置(时、分、秒)切换键和存储键。短按 B2 键进入时间设置状态。每次短按 B2 键,设置位置以时、分、秒循环切换,并突出显示(高亮)当前位置;设置完后,长按 B2 键(超过 0.8 秒)把设置的时间存储到当前的存储位置,并推出设置状态。如果是临时设置定时时间,则不需存储,直接按定时器启动按键。
按键 B3 为时、分、秒(按键 B2 确定当前位置)数字增加键。每短按B3 一次,数字递增一次;按住 B3 超过 0.8 秒,则数字快速递增,直到松开B3 按键。数字递增时,超出范围则从头循环。
按键 B4 为定时器启动键。短按 B4,定时器启动,开始运行;运行期间短按 B4,暂停定时器,再短按 B4,恢复定时器运行;长按 B4(超过 0.8 秒),则取消定时器运行,回到 Standby 状态。
3、PWM 输出和 LED 显示
定时器运行时,PA6 口输出 PWM 信号,同时 LED 灯(LD1)以 0.5 秒的频率闪烁。PWM 信号频率为 1KHz,占空比为 80%。
定时器停止或暂停时,停止输入 PWM 信号,LED 灯灭。
4、定时时间存储
设定好的定时时间存储在 EEPROM 中。
掉电重启后,显示存储位置 1 的定时时间。
建议在做这题的时候,在变量的使用上多写点注释,负责自己也会弄混乱。因为这道题目注重于在逻辑上的处理,而不在于模块的配置。
1、初始化部分
本题目需要使用到AT24C02,那么在最开始应该做一个iic的初始化工作;同时初始化LED、按键、定时器3;这里我的定时器3用的是输出比较的方式来产生pwm的,也建议大家多练习一下输出比较的方式。本题目用PWM模式也是ok的。同时还得读取存在24c02中5个定时时间值。 由于AT24C02的一个地址只能存入0-255的数,因此我们在储存的时候要把时间拆分成时、分、秒进行储存,同时读的时候也一样,按照时、分、秒分别读取,然后再进行合并。
STM3210B_LCD_Init();
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
SysTick_Config(SystemCoreClock/1000);
i2c_init();
LED_Init();
KEY_Init();
TIM3_PWM_OUTPUT(1000,80,1,0);
Timer[0] = _24c02_Read(0x00) * 3600 + _24c02_Read(0x01) * 60 + _24c02_Read(0x02);
Delay_Ms(10);
Timer[1] = _24c02_Read(0x10) * 3600 + _24c02_Read(0x11) * 60 + _24c02_Read(0x12);
Delay_Ms(10);
Timer[2] = _24c02_Read(0x20) * 3600 + _24c02_Read(0x21) * 60 + _24c02_Read(0x22);
Delay_Ms(10);
Timer[3] = _24c02_Read(0x30) * 3600 + _24c02_Read(0x31) * 60 + _24c02_Read(0x32);
Delay_Ms(10);
Timer[4] = _24c02_Read(0x40) * 3600 + _24c02_Read(0x41) * 60 + _24c02_Read(0x42);
Delay_Ms(10);
2、按键处理部分
每50ms扫描一次按键判断按键是否有操作,同时也需要根据题目的需求设定各个按键所对应的功能。
if(KEY_Flag)
{
KEY_Flag = 0;
KEY_Read();
}
void KEY_Read(void)
{
static u16 key1_sum = 0,key2_sum = 0,key3_sum = 0,key4_sum = 0;
// KEY1
if(KEY1 == 0)
{
key1_sum++;
if(key1_sum == 1) // 单次触发
{
if(system_mode == 0 || system_mode == 1)//在stanby模式和setting模式下进行通道的切换
{
Channel_num++;
if(Channel_num > 4) Channel_num = 0; //一共5个通道
}
}
}else
{
key1_sum = 0;
}
// KEY2
if(KEY2 == 0)
{
key2_sum++;
if(key2_sum == 1) // 短按
{
if(system_mode == 0) //在stanby模式下单次按下进入setting模式
{
system_mode = 1; //进入 Setting
Timer_Buf = Timer[Channel_num];//把当前相应的定时值读取到缓冲区
LCD_ClearLine(Line0); //清屏操作
LCD_ClearLine(Line1);
LCD_ClearLine(Line2);
LCD_ClearLine(Line3);
LCD_ClearLine(Line4);
LCD_ClearLine(Line5);
LCD_ClearLine(Line6);
LCD_ClearLine(Line7);
LCD_ClearLine(Line8);
LCD_ClearLine(Line9);
}
if(system_mode == 1)//如果当前模式是在setting模式下那么就进行切换时、分、秒的操作
{
Set_Select++; // 2、1、0 对应时、分、秒
if(Set_Select > 2) Set_Select = 0;
}
}
if(key2_sum == 16) // 800ms 长按触发
{
if(system_mode == 1) //在setting模式下长按代表保存数据
{
system_mode = 0;//模式切换回stanby
Timer[Channel_num] = Timer_Buf; //保存当前设置的定时值
switch(Channel_num) //根据通道情况保存当前时、分、秒数据到AT24C02响应的位置
{
case 0: _24c02_Write(0x00,hour);
Delay_Ms(10);
_24c02_Write(0x01,min);
Delay_Ms(10);
_24c02_Write(0x02,sec);
break;
case 1: _24c02_Write(0x10,hour);
Delay_Ms(10);
_24c02_Write(0x11,min);
Delay_Ms(10);
_24c02_Write(0x12,sec);
break;
case 2: _24c02_Write(0x20,hour);
Delay_Ms(10);
_24c02_Write(0x21,min);
Delay_Ms(10);
_24c02_Write(0x22,sec);
break;
case 3: _24c02_Write(0x30,hour);
Delay_Ms(10);
_24c02_Write(0x31,min);
Delay_Ms(10);
_24c02_Write(0x32,sec);
break;
case 4: _24c02_Write(0x40,hour);
Delay_Ms(10);
_24c02_Write(0x41,min);
Delay_Ms(10);
_24c02_Write(0x42,sec);
break;
}
LCD_ClearLine(Line0);//清屏
LCD_ClearLine(Line1);
LCD_ClearLine(Line2);
LCD_ClearLine(Line3);
LCD_ClearLine(Line4);
LCD_ClearLine(Line5);
LCD_ClearLine(Line6);
LCD_ClearLine(Line7);
LCD_ClearLine(Line8);
LCD_ClearLine(Line9);
}
}
}else
{
key2_sum = 0;
}
// KEY3
if(KEY3 == 0)
{
key3_sum++;
if(key3_sum == 1) // 短按
{
if(system_mode == 1) // Setting模式
{
switch(Set_Select) //判断当前操作的位置
{
case 0: sec++;
if(sec > 59) sec = 0;
break;
case 1: min++;
if(min > 59) min = 0;
break;
case 2: hour++;
if(hour > 23) hour = 0;
break;
}
Timer_Buf = hour * 3600 + min * 60 + sec; //计算完成还把时分秒还原回总的秒数存到缓冲区中
}
}
if(key3_sum == 16) // 长按 快速增加
{
if(system_mode == 1) // Setting 模式
{
switch(Set_Select)
{
case 0: sec++;
if(sec > 59) sec = 0;
break;
case 1: min++;
if(min > 59) min = 0;
break;
case 2: hour++;
if(hour > 23) hour = 0;
break;
}
Timer_Buf = hour * 3600 + min * 60 + sec;
key3_sum = 15;
}
}
}else
{
key3_sum = 0;
}
// KEY4
if(KEY4 == 0)
{
key4_sum++;
if(key4_sum == 1) //短按
{
if(system_mode == 0) //在stanby模式下短按定时器启动
{
DingShi = Timer[Channel_num]; //获取定时值进行定时
}else
if(system_mode == 1) //在setting模式下则从缓冲区获取定时值
{
DingShi = hour * 3600 + min * 60 + sec;
}
system_mode = 2; // 进入Running,pause 模式
if(system_mode == 2) //判断当前是running还是pause模式
{
switch(Run_Stop)
{
case 0: Run_Stop = 1;break;
case 1: Run_Stop = 0;break;
}
}
}
if(key4_sum == 16) //长按触发
{
system_mode = 0; //返回stanby模式
DingShi = 0; //清除定时
Run_Stop = 0; //回到pause模式
}
}else
{
key4_sum = 0;
}
}
3、显示部分
显示部分还是直接看代码吧,在代码里特意添加了比较多的注释辅助网友们理解。
if(Display_Flag)
{
Display_Flag = 0;//清除标志位
if(system_mode == 0) // Stanby 模式
{
sprintf((char*)string," NO:%d ",Channel_num + 1);
LCD_DisplayStringLine(Line1,string);//LCD显示数据
hour = Timer[Channel_num] / 3600; //时
min = Timer[Channel_num] % 3600 / 60; //分
sec = Timer[Channel_num] % 3600 % 60; //秒
sprintf((char*)string," %.2d:%.2d:%.2d ",hour,min,sec);
LCD_DisplayStringLine(Line4,string); //显示时间信息
LCD_DisplayStringLine(Line7,(u8*)" Standby ");
}
if(system_mode == 1) // Setting模式
{
sprintf((char*)string," NO:%d ",Channel_num + 1); //显示通道
LCD_DisplayStringLine(Line1,string); //
if(Set_Select == 2) //显示颜色 如果被选中则显示红色 未选中显示蓝色
{
LCD_SetTextColor(Red);
}else
{
LCD_SetTextColor(White);
}
LCD_DisplayChar(Line4,210, hour / 10 + 0x30); //由于只能修改一行中某个数字的颜色,不能使用行显示 ;数字显示要转换成ascii码
LCD_DisplayChar(Line4,195, hour % 10 + 0x30); //同上 下面的显示操作也一样
LCD_SetTextColor(White);
LCD_DisplayChar(Line4,180, ':');
if(Set_Select == 1) //同上
{
LCD_SetTextColor(Red);
}else
{
LCD_SetTextColor(White);
}
LCD_DisplayChar(Line4,165, min / 10 + 0x30);
LCD_DisplayChar(Line4,150, min % 10 + 0x30);
LCD_SetTextColor(White);
LCD_DisplayChar(Line4,135, ':');
if(Set_Select == 0)//同上
{
LCD_SetTextColor(Red);
}else
{
LCD_SetTextColor(White);
}
LCD_DisplayChar(Line4,120, sec / 10 + 0x30);
LCD_DisplayChar(Line4,105, sec % 10 + 0x30);
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line7,(u8*)" Setting ");
}
if(system_mode == 2) // running和pause模式
{
sprintf((char*)string," NO:%d ",Channel_num + 1);
LCD_DisplayStringLine(Line1,string);
hour = Timer[Channel_num] / 3600; //获取当前计数值 时 、分 、秒
min = Timer[Channel_num] % 3600 / 60;
sec = Timer[Channel_num] % 3600 % 60;
sprintf((char*)string," %.2d:%.2d:%.2d ",hour,min,sec); //显示时间值
LCD_DisplayStringLine(Line4,string);
if(Run_Stop)
{
LCD_DisplayStringLine(Line7,(u8*)" Running "); //当前处于running模式,继续计时
}else
{
LCD_DisplayStringLine(Line7,(u8*)" Pause "); //当前处于pause模式,暂停计时
}
}
}
4、定时部分
在对于这套题的话,我专门设置了一个定时的部分,同时定时的话1s扫描一次,判断当前是否有定时时间,如果有则输出PWM波,如果没有则关闭输出。1s的定时效果右滴答定时器进行计算,不需要专门开一个RTC这么繁琐。 检测到DingShi变量有值,那么就代表获取到了定时的时间,PWM就会开始输出。
if(_1s_Flag) //每秒进入一次
{
_1s_Flag = 0; // 清除标志位
if(system_mode == 2) //当前在running和pause模式下
{
if(Run_Stop) //为真当前的状态是running
{
if(DingShi) //有定时值
{
DingShi--; //定时值每秒-1
}
if(DingShi != 0) // 有定时值
{
TIM3_PWM_OUTPUT(1000,80,0,1); //输出pwm
}else
{
system_mode = 0; // 无定时值或者已经计数完毕 则回到stanby模式
Run_Stop = 0;
TIM3_PWM_OUTPUT(1000,80,0,0); //停止输出pwm
}
}else
{
TIM3_PWM_OUTPUT(1000,80,0,0); // 如果当前状态不是在running态,也停止输出pwm
}
}
}
5、LED部分
LED部分就比较简单了,只需要判断当前有没有输出PWM就ok了
if(LED_Flag)
{
LED_Flag = 0;
if(system_mode == 2 && Run_Stop == 1)
{
LED_MODE ^= (1<<8);
}else
{
LED_MODE |= (1<<8);
}
GPIOC->ODR = LED_MODE;
GPIOD->ODR |= (1<<2);
GPIOD->ODR &=~(1<<2);
}
6、滴答定时器计时以及中断部分
基本上相差不大
extern u8 KEY_Flag;
extern u8 Display_Flag ;
extern u16 TIM3_CH1_Val;
extern u16 TIM3_CH1_Duty;
u8 TIM3_CH1_Flag;
extern u8 _1s_Flag;
extern u8 LED_Flag;
void SysTick_Handler(void)
{
static u8 key_sum = 0;
static u8 display_sum = 0;
static u16 _1s_sum = 0;
static u16 led_sum = 0;
TimingDelay--;
if(++led_sum == 500)
{
led_sum = 0;
LED_Flag = 1;
}
if(++_1s_sum == 1000)
{
_1s_sum = 0;
_1s_Flag = 1;
}
if(++key_sum == 50) // 50ms
{
key_sum = 0;
KEY_Flag = 1;
}
if(++display_sum == 100)
{
display_sum = 0;
Display_Flag = 1;
}
}
void TIM3_IRQHandler(void)
{
u16 capture;
if(TIM_GetITStatus(TIM3,TIM_IT_CC1) == 1)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_CC1);
capture = TIM_GetCapture1(TIM3);
if(TIM3_CH1_Flag)
{
TIM_SetCompare1(TIM3,capture + TIM3_CH1_Duty);
}else
{
TIM_SetCompare1(TIM3,capture + TIM3_CH1_Val - TIM3_CH1_Duty);
}
TIM3_CH1_Flag ^= 1;
}
}
#include "io.h"
#include "i2c.h"
#include "lcd.h"
extern void Delay_Ms(u32 nTime);
u16 LED_MODE = 0xffff;
u16 TIM3_CH1_Val;
u16 TIM3_CH1_Duty;
extern u8 system_mode; // 系统当前状态 0:Stanby 1:Setting 2:running¡¢pause
extern u8 Channel_num; // 储存位置选择 0 ~ 4
u8 Set_Select = 0; // 选择设置的时间 时 分 秒
extern u32 Timer[5]; // 定时存储区域
u32 Timer_Buf; // 修改缓冲区
extern u8 hour; //显示分解:时
extern u8 min ; //显示分解:分
extern u8 sec ; //显示分解:秒
u8 Run_Stop = 0; //1:运行 0:停止
extern u32 DingShi; //定时
//////////////////// 24C02 //////////////////
void _24c02_Write(u8 address,u8 data)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(address);
I2CWaitAck();
I2CSendByte(data);
I2CWaitAck();
I2CStop();
}
u8 _24c02_Read(u8 address)
{
u8 temp;
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(address);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
temp = I2CReceiveByte();
I2CWaitAck();
I2CStop();
return temp;
}
//////////////////// LED ///////////////////
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_Init(GPIOC, &GPIO_InitStructure);
GPIOC->ODR = LED_MODE;
GPIOD->ODR |= (1<<2);
GPIOD->ODR &=~(1<<2);
}
//////////////// PWM_OUTPUT ///////////////
void TIM3_PWM_OUTPUT(u16 Frequency,u8 duty,u8 status,u8 enable)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
if(status)
{
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_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_TimeBaseInitStructure.TIM_Period = 0xffff;
TIM_TimeBaseInitStructure.TIM_Prescaler = 71;
TIM_TimeBaseInitStructure.TIM_CounterMode = 0x0;
TIM_TimeBaseInitStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
}
TIM3_CH1_Val = 1000000 / Frequency;
TIM3_CH1_Duty = TIM3_CH1_Val * duty / 100;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
if(enable)
{
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
}else
{
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
}
TIM_OCInitStructure.TIM_Pulse = TIM3_CH1_Val;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
//TIM_SetCounter(TIM3, 0x0);
//TIM_SetCompare1(TIM3,0x0);
if(status)
{
TIM_Cmd(TIM3, ENABLE);
TIM_ITConfig( TIM3, TIM_IT_CC1,ENABLE);
}
}
/////////////////// KEY ////////////////////
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);
}