蓝桥杯嵌入式组第九届省赛练习(算是解决了长短按键,解决了高亮显示)

第九届代码实现代码

https://gitee.com/litte_enigner/lqb_emb_9th.git

蓝桥杯开发板的基本模块已经玩的差不多了,接下来就拿第九届题练一下手。

算是解决了长短按键,解决了高亮显示

先看一下第九届试题的题目和硬件框图。题目就是“电子定时器”。

硬件框图如下

蓝桥杯嵌入式组第九届省赛练习(算是解决了长短按键,解决了高亮显示)_第1张图片

感觉控制的外设不多,那么就按照描述一步一步的实现就可以了。

第一步是LCD的显示部分。

LCD 显示存储位置、定时时间和当前状态。系统预留 5 个存储位置用于存储常用的定时时间。当定时器停止时,当前状态为 Standby;当系统正在设置时间时,当前状态为 Setting;当定时器运行时,当前状态为 Running,定时器暂停时,当前状态为 Pause。

其显示部分是这样的

蓝桥杯嵌入式组第九届省赛练习(算是解决了长短按键,解决了高亮显示)_第2张图片

先定义一下模式选择

#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;}

相关实现效果如下图片所示。

蓝桥杯嵌入式组第九届省赛练习(算是解决了长短按键,解决了高亮显示)_第3张图片

蓝桥杯嵌入式组第九届省赛练习(算是解决了长短按键,解决了高亮显示)_第4张图片

蓝桥杯给的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

 

蓝桥杯嵌入式组第九届省赛练习(算是解决了长短按键,解决了高亮显示)_第5张图片

 

你可能感兴趣的:(蓝桥杯学习笔记,stm32编程)