蓝桥杯备赛,第九届蓝桥杯省赛试题解析。

题目分析

原题

原题参见蓝桥杯大赛=>第九届真题.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来格式化需要显示的字符串
  • StatusSetting时通过设置背景颜色及文本颜色实现选中时、分、秒
#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链接蓝桥杯第九届省赛

Last modification:April 10th, 2021 at 07:39 pm