蓝桥杯备赛,第九届蓝桥杯省赛试题解析。
题目分析
原题
原题参见蓝桥杯大赛=>第九届真题.zip
另:官方试题汇总链接=>蓝桥杯大赛历届真题
硬件分析
本题中使用到的硬件设备除了MCU外主要有:LCD(液晶显示器)、按键(B1、B2、B3、B4)、LED、I2C、24C02
硬件上与第十届与十一届多了个I2C,因此,除I2C外的硬件分析请参照上篇博文:
注意:官方将提供的I2C的软件驱动代码,我们需要根据该驱动来编写对24C02存储芯片的读写代码。对于该驱动程序,需要我们去将对于的PB6与PB7引脚配置为Output引脚。
软件分析
在本题中我们需要实现如下功能:
- LED闪烁(500ms)
- 使用LCD显示用户界面
- 按键长按与短按的检测及处理
- PWM输出
- 对存储芯片24C02的读写
功能实现
STM32CubeMX引脚配置
与近几届差不多的配置,在LCD与按键引脚的基础上,多了PA6引脚用于PWM输出,根据要求配置如下:
TIM3-CH1
Prescaler = 80 - 1
Period = 999
Pulse = 799;
LED部分
代码逻辑为:
- 通过全局变量
LEDTim
以及Systick中断来判断是否到达500ms - LED每间隔500ms翻转一次电平,以实现闪烁效果
#define LED_FLASH_INTERVAL 500
#define LED_ALL_PIN (GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15)
volatile uint32_t LEDTim = 0;
void LED_Set(uint8_t led)
{
HAL_GPIO_WritePin(GPIOC, LED_ALL_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, led << 8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
void LED_Flash(void)
{
if (Status != STATUS_RUNNING)
{
LED_Set(0x00);
return;
}
if (LEDTim > LED_FLASH_INTERVAL * 2)
LEDTim = 0;
LED_Set((LEDTim < LED_FLASH_INTERVAL) ? 0x01 : 0x00);
}
LCD部分
代码逻辑为:
- 根据
Status
的值判断当前要显示当前走时还是常用的定时时间 - 通过标准库字符串格式化函数
sprintf
来格式化需要显示的字符串 - 在
Status
为Setting
时通过设置背景颜色及文本颜色实现选中时、分、秒
#define STATUS_RUNNING 0x00
#define STATUS_STANDBY 0x01
#define STATUS_PAUSE 0x02
#define STATUS_SETTING 0x03
volatile uint8_t Status = STATUS_STANDBY;
const char status_str[STATUS_SUM_MAX][20] = {
" Running ",
" Standby ",
" Pause ",
" Setting "};
void LCD_Show(void)
{
char str[30] = " No 1 ";
str[6] += NowListItem;
LCD_DisplayStringLine(Line1, (u8 *)str);
uint8_t *p = (Status == STATUS_RUNNING || Status == STATUS_PAUSE) ? (uint8_t *)NowTime : (uint8_t *)TimeList[NowListItem];
sprintf(str, " %02d:%02d:%02d ", p[TIME_H], p[TIME_M], p[TIME_S]);
u32 i = 0;
u16 refcolumn = 319; //319;
char *ptr = str;
while ((*ptr != 0) && (i < 20)) // 20
{
if (i == 5 + 3 * NowChoose && Status == STATUS_SETTING)
{
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
}
LCD_DisplayChar(Line4, refcolumn, *ptr);
refcolumn -= 16;
ptr++;
i++;
if (i == 7 + 3 * NowChoose && Status == STATUS_SETTING)
{
LCD_SetBackColor(White);
LCD_SetTextColor(Black);
}
}
LCD_DisplayStringLine(Line6, (u8 *)status_str[Status]);
}
按键部分
代码逻辑:
- 在十届和十一届的短按代码基础上,新增加一个变量用于记录按键按下的时长来判断长按
- 当按键抬起时,才认为按键按下一次
- 当B3满足长按条件时,只要B3依旧未抬起,则将B3长按标志置位
#define KEY_DOWN 0x00
#define KEY_UP 0xff
struct KeyInfo
{
GPIO_TypeDef *port;
uint16_t pin;
uint8_t status;
uint8_t count;
};
typedef void (*Callback)(void);
struct KeyInfo KeyList[KEY_SUM_MAX] = {
{GPIOB, GPIO_PIN_0, KEY_UP, 0x00},
{GPIOB, GPIO_PIN_1, KEY_UP, 0x00},
{GPIOB, GPIO_PIN_2, KEY_UP, 0x00},
{GPIOA, GPIO_PIN_0, KEY_UP, 0x00}};
Callback KeyCallback[KEY_SUM_MAX * 2] = {
B1_short,
B2_short,
B3_short,
B4_short,
B1_long,
B2_long,
B3_long,
B4_long};
volatile uint8_t KeyClick = 0x00;
void Key_Scan(void)
{
if (KeyTim < 20)
return;
KeyTim = 0;
for (size_t i = 0; i < KEY_SUM_MAX; i++)
{
if (HAL_GPIO_ReadPin(KeyList[i].port, KeyList[i].pin) == GPIO_PIN_SET)
{
if (KeyList[i].status == KEY_DOWN)
{
if (KeyList[i].count < KEY_LONG_DOWN_MAX)
{
// Short
KeyClick = 0x01 << i;
}
else
{
// Long
// B2
if (i != 2)
KeyClick = 0x10 << i;
}
KeyList[i].status = KEY_UP;
KeyList[i].count = 0;
}
}
else
{
if (KeyList[i].count <= KEY_LONG_DOWN_MAX)
{
++KeyList[i].count;
}
else
{
// B3
if (i == 2)
KeyClick = 0x10 << i;
}
KeyList[i].status = KEY_DOWN;
}
}
}
void Key_Handle(void)
{
for (size_t i = 0; i < KEY_SUM_MAX * 2; i++)
{
if (KeyClick == 0x01 << i)
KeyCallback[i]();
}
KeyClick = 0x00;
}
void B1_short(void)
{
NowListItem = (NowListItem + 1) % 5;
}
void B2_short(void)
{
if (Status == STATUS_SETTING)
{
NowChoose = (NowChoose + 1) % 3;
}
else
{
Status = STATUS_SETTING;
NowChoose = TIME_H;
}
}
void B3_short(void)
{
if (Status != STATUS_SETTING)
return;
TimeList[NowListItem][NowChoose] = (TimeList[NowListItem][NowChoose] < ((NowChoose > TIME_H) ? 59 : 23)) ? TimeList[NowListItem][NowChoose] + 1 : 0;
}
void B4_short(void)
{
if (Status == STATUS_SETTING || Status == STATUS_STANDBY)
{
NowTime[TIME_H] = TimeList[NowListItem][TIME_H];
NowTime[TIME_M] = TimeList[NowListItem][TIME_M];
NowTime[TIME_S] = TimeList[NowListItem][TIME_S];
}
Status = (Status == STATUS_RUNNING) ? STATUS_PAUSE : STATUS_RUNNING;
}
void B1_long(void)
{
;
}
void B2_long(void)
{
if (Status == STATUS_SETTING)
{
Set_TImeList(NowListItem);
Status = STATUS_STANDBY;
}
}
void B3_long(void)
{
B3_short();
}
void B4_long(void)
{
Status = STATUS_STANDBY;
}
24C02部分
代码逻辑:
- 根据官方提供的I2C驱动函数,依照24C02时序图发送控制字与器件地址完成读写单个字节
- 赛题需要存储5组时间数据,每组数据有3个字节表示,共15个字节
- 在写24C02时需要根据数据手册的说明延时5ms,否则会出现写入异常
uint8_t M24c02_Read(uint8_t addr)
{
uint8_t val = 0;
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xA1);
I2CWaitAck();
val = I2CReceiveByte();
I2CWaitAck();
I2CStop();
return val;
}
void M24c02_Write(uint8_t addr, uint8_t val)
{
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CSendByte(val);
I2CWaitAck();
I2CStop();
HAL_Delay(5);
}
void Get_TimeList(uint8_t item)
{
for (size_t i = 0; i < 3; i++)
TimeList[item][i] = M24c02_Read(item * 3 + i);
}
void Set_TImeList(uint8_t item)
{
for (size_t i = 0; i < 3; i++)
M24c02_Write(item * 3 + i, TimeList[item][i]);
}
完整代码
参见Github链接蓝桥杯第九届省赛