第九届代码实现代码
https://gitee.com/litte_enigner/lqb_emb_9th.git
蓝桥杯开发板的基本模块已经玩的差不多了,接下来就拿第九届题练一下手。
算是解决了长短按键,解决了高亮显示
先看一下第九届试题的题目和硬件框图。题目就是“电子定时器”。
硬件框图如下
感觉控制的外设不多,那么就按照描述一步一步的实现就可以了。
第一步是LCD的显示部分。
LCD 显示存储位置、定时时间和当前状态。系统预留 5 个存储位置用于存储常用的定时时间。当定时器停止时,当前状态为 Standby;当系统正在设置时间时,当前状态为 Setting;当定时器运行时,当前状态为 Running,定时器暂停时,当前状态为 Pause。
其显示部分是这样的
先定义一下模式选择
#define STANNDBY 0
#define SETTING 1
#define RUNNING 2
#define PAUSE 3
再造一个函数,用来显示上面的东西,那这个函数应该需要什么参数了?存储位置的标记,时间(时、分、秒),还有下面的一行状态显示。
那么就先定义一个结构体吧
struct time_struct{
uint8_t No;
uint8_t Hour;
uint8_t Min;
uint8_t Sec;
}play_time[5];
然后构造一个显示函数,有些东西是宏定义的,具体部分看源文件。
set_display(Line4, (uint8_t *)buff ,pos);这一个函数是根据给的例程改写的具体在另一篇文章说
/*
显示函数,
用来显示题目要求的各种功能
参数 第一个struct time_struct display_time用来显示传入的时间
第二个uint8_t status 用来显示传入的状态信息
第三个 uint8_t pos这个只在设置状态下有效,显示高亮的部分
*/
void display(struct time_struct display_time, uint8_t status, uint8_t pos)
{
char buff[20]; //建立一个文字缓存区,sprintf()使用
LCD_DisplayStringLine(Line2, (uint8_t *)" No ");
LCD_DisplayChar(Line2, 16*13, display_time.No);
switch(status)
{
case STANNDBY:
sprintf(buff, " %02d: %02d: %02d ", display_time.Hour, display_time.Min, display_time.Sec);
LCD_DisplayStringLine(Line4, (uint8_t *)buff);
LCD_DisplayStringLine(Line6, (uint8_t *)" Standby");
break;
case SETTING:
sprintf(buff, " %02d: %02d: %02d ", display_time.Hour, display_time.Min, display_time.Sec);
set_display(Line4, (uint8_t *)buff ,pos);
LCD_DisplayStringLine(Line6, (uint8_t *)" Setting");
break;
case RUNNING:
sprintf(buff, " %02d: %02d: %02d ", display_time.Hour, display_time.Min, display_time.Sec);
LCD_DisplayStringLine(Line4, (uint8_t *)buff);
LCD_DisplayStringLine(Line6, (uint8_t *)" Running");
break;
case PAUSE:
sprintf(buff, " %02d: %02d: %02d ", display_time.Hour, display_time.Min, display_time.Sec);
LCD_DisplayStringLine(Line4, (uint8_t *)buff);
LCD_DisplayStringLine(Line6, (uint8_t *)" Pause");
break;
default : break;
}
}
接下来是按键,按键描述就比较多
但是在此之前,需要写一个按键驱动程序。驱动程序的解释戳这里。
这个按键需要实现长短按键的功能,在实现这个功能的时候,我过于局限于理论思考,反而把问题给想复杂了,实现起来很简单,就是加一个计数变量就行了。
因为key_scan()这个函数本身就是要放到定时器中断的,所以就可以在这里计数,实现长按。值得注意的是,这种方式有一个问题,那就是在识别长按键的时候会有短按键的键值出现,但是对于本道题而言这个不是问题。有兴趣的同学可以解决一下
void key_scan(void)
{
static uint8_t buff[] = {0xff, 0xff,0xff,0xff};
uint8_t i;
buff[0] = (buff[0] << 1) | KEY1;
buff[1] = (buff[1] << 1) | KEY2;
buff[2] = (buff[2] << 1) | KEY3;
buff[3] = (buff[3] << 1) | KEY4;
for(i = 0; i < 4; i ++)
{
if((buff[i] & 0xFf) == 0xFf)
{
keySta[i] = 1;
keyCount[i] = 0;//如果弹起就清零
}
else if((buff[i] & 0xFf) == 0x00)
{
keySta[i] = 0;
keyCount[i] += 1;//如果一直按着就一直加
if(keyCount[i] > 1000)
{
keyCount[i] = 1000;
}
}
}
}
这个是读键值函数
uint8_t key_drive(void)
{
uint8_t i, res = 0;
static uint8_t backup[] = {1,1,1,1};
uint16_t timeTr[] = {800,800,800,800};
for(i = 0; i < 4; i ++)
{
if(backup[i] != keySta[i])
{
if(backup[i] != 0)
{
res = SkeyMap[i];
if(keyCount[i] > 0)
{
if(keyCount[i] > timeTr[i])
{
res = LkeyMap[i];
}
}
}
backup[i] = keySta[i];
}//以上的是短按键实现
if(keyCount[i] > 1)
{
if(keyCount[i] > timeTr[i])
{
res = LkeyMap[i];
}
}//这个是长时间按键的实现
}
return res;
}
相关实现效果如下图片所示。
蓝桥杯给的LCD显示驱动里没有单个的高亮函数,所以需要自己利用现有的函数在写一个。
利用的函数有
LCD_SetBackColor();
LCD_SetTextColor();
LCD_DisplayChar(Line, refcolumn, *ptr);
仿照的就是
LCD_DisplayStringLine();这个函数
void set_display(uint8_t Line, uint8_t *ptr,uint8_t pos)
{
uint32_t i = 0;
uint16_t refcolumn = 319;//319;
if(pos == 1) pos = 6; //这样就方便处理,只需要输入在哪里显示就行
else if(pos == 2) pos = 10; //z在上层程序设计时就不需要考虑具体在那一个位置
else if(pos == 3) pos = 14; //显示
while ((*ptr != 0) && (i < 20)) // 20
{
if((i == pos) || (i == (pos + 1))) //在符合条件的位置及其下一个位置显示
{
LCD_SetBackColor(White);
LCD_SetTextColor(Blue);
}
else
{
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
}
LCD_DisplayChar(Line, refcolumn, *ptr);
refcolumn -= 16;
ptr++;
i++;
}
}
这样就可以实现主体功能了,为了方便,先把PWM,和存储的代码贴出来,注意在比赛时官方会提供iic的驱动文件,所以只需要写一下EEPROM的读写函数就行。
首先是PWM的输出,题目要求的是在定时器开始计时的时候PA6同时会输出频率为1KHz,占空比为80%的PWM波。这个要求还是比较简单的。具体看代码。
//初始化一下PA6除了模式改为复用推完输出其他的没有什么说的
static void PWM_gpio_config()
{
GPIO_InitTypeDef GPIO_structure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_structure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_structure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_structure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_structure);
}
void TIM3_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_structure;
TIM_OCInitTypeDef pwm_structure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //PA6对应的是TIM3通道1
TIM_structure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频1
TIM_structure.TIM_RepetitionCounter = 0; //不预分频
TIM_structure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_structure.TIM_Prescaler = 72 -1; //72预分频,就是1M
TIM_structure.TIM_Period = 1000 - 1; //1000就是1千赫兹的频率
TIM_TimeBaseInit(TIM3, &TIM_structure);
pwm_structure.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式
pwm_structure.TIM_OutputState = TIM_OutputState_Enable; //
pwm_structure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM3, &pwm_structure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_SetCompare1(TIM3, 200); //因为输出极性的问题这个就是80%的pwm
PWM_gpio_config();
TIM_Cmd(TIM3, DISABLE);
}
EEPROM的编写
基本上iic设备都是一个套路。读过程:i2c开始信号->写设备地址->等待响应->发送数据的读地址->等待响应->i2c开始信号->等待应答->读数据->发送i2c停止信号。
写过程:开始i2c信号->发送设备地址->等待响应->发送数据的写地址->等待应答->写数据->等待应答->停止i2c信号。
在EEPROM中需要注意的是写的时候需要延时一会儿以便EEPROM完成写数据的操作。
/*--------------储存函数-----------------*/
void save_data(struct time_struct save_time)
{
uint8_t addr = 0, i;
switch(save_time.No)
{
case '1': addr = 0x01; break;
case '2': addr = 0x05; break;
case '3': addr = 0x09; break;
case '4': addr = 0x0d; break;
case '5': addr = 0x11; break;
default : break;
}
for(i = 0; i <4; i ++)
{
I2CStart();
I2CSendByte(0xa0);
while(!I2CWaitAck());
I2CSendByte(addr + i);
while(!I2CWaitAck());
switch(i)
{
case 0:I2CSendByte(save_time.No);break;
case 1:I2CSendByte(save_time.Hour);break;
case 2:I2CSendByte(save_time.Min);break;
case 3:I2CSendByte(save_time.Sec);break;
default:break;
}
while(!I2CWaitAck());
I2CStop();
Delay_Ms(10);
}
}
void read_data(struct time_struct *save_time, uint8_t No)
{
uint8_t addr = 0, i;
switch(No)
{
case 1: addr = 0x01; break;
case 2: addr = 0x05; break;
case 3: addr = 0x09; break;
case 4: addr = 0x0d; break;
case 5: addr = 0x11; break;
default : break;
}
for(i = 0; i < 4; i ++)
{
I2CStart();
I2CSendByte(0xa0);
while(!I2CWaitAck());
I2CSendByte(addr+i);
while(!I2CWaitAck());
I2CStart();
I2CSendByte(0xa1);
while(!I2CWaitAck());
switch(i)
{
case 0: save_time->No = I2CReceiveByte(); break;
case 1: save_time->Hour = I2CReceiveByte(); break;
case 2: save_time->Min = I2CReceiveByte(); break;
case 3: save_time->Sec = I2CReceiveByte(); break;
default : break;
}
I2CSendAck();
I2CStop();
}
}
/*----------------结束-----------------*/
那个LED灯闪烁的就非常简单了就不赘述了。
接下来就是主代码。
逻辑非常的简单,因为每一个界面都是单任务运行,所以用一个while控制每一个功能在自己的while运行就行。剩下的都在这里了
视频
蓝桥杯第九届视频演示
第九届代码实现代码
https://gitee.com/litte_enigner/lqb_emb_9th.git