第十二届蓝桥杯第一场省赛试题解析。

题目分析

硬件分析

本题中使用到的外设主要有:LCD(液晶显示器)、按键(B1、B2、B3、B4)、LED、串行端口UART1、PWM输出

其中串行端口与PWM输出对应引脚如下:

UART1对应引脚与下载口相连

PWM输出为PA7引脚,选用TIM17-CH1

软件分析

在本题中我们需要实现如下功能:

  • 控制指定的LED亮灭
  • LCD显示用户界面(Data与Para)
  • PWM输出与占空比控制
  • 按键短按检查及处理
  • 串口通讯、不定长数据接收
  • 字符串解析、错误处理

该题主要难点在于串口不定长数据接收及错误处理,其它功能基本上没有难度

功能实现

STM32CubeMX引脚配置

本文主要强调串口配置,其它部分的配置参见之前的博文:

串口配置

USART1配置信息如下:

USART模式:异步(Asynchronous)模式

波特率(Baud Rate):9600

数据长度(Word Length):8Bits

奇偶校验(Parity):None

停止位(Stop Bit):1

本文将使用DMA的方式来接收来自USART1的数据,配置如下:

DMA Request: USART_RX

DMA Channel: DMA1 Channel1

Direction: Peripheral To Memory

Priority: Low

Mode: Circular

Increment Address: Memory Enable

代码逻辑

将各个主要功能写成任务的形式放在main()函数中的while(1)`内不断执行,通过全局变量来保存状态,而在任务内根据当前状态来控制对应外设完成该外设负责的功能,这里将主要介绍串口部分的逻辑。

串口部分的功能实现

流程如下:

  • 在串口中断内,若接收到一帧数据,将串口接收标志(全局变量)置位
  • 在串口任务中若串口接收标志被置位,处理串口接收缓冲区的数据
  • 对数据的长度、格式进行合法性检查
  • 若数据格式合法,则继续对数据进行处理,并判断是否存在逻辑错误

对车辆信息进行操作

分别包括:

  • 相关结构体定义
  • 相关全局变量定义
  • 相关操作:查找、添加、删除
#define CAR_LEN_MAX 8

#define HANDLE_ERR 0xff
#define HANDLE_OK 0x00

#define T_1m (60)
#define T_1H (60 * 60)
#define T_1D (24 * 60 * 60)

struct Time
{
    int YY;
    int MM;
    int DD;
    int HH;
    int mm;
    int SS;
};

struct CarInfo
{
    uint8_t mode;
    uint32_t id;
    struct Time time;
};

struct CarInfo CarList[CAR_LEN_MAX] = {0};

uint8_t CarData[3] = {0, 0, 8};
double FeeData[2] = {3.5, 2.0};

uint8_t CarInfoFind(uint32_t id)
{
    for (size_t i = 0; i < CAR_LEN_MAX; i++)
    {
        if (id == CarList[i].id)
            return i; // 匹配成功
    }
    // Not Find
    return HANDLE_ERR;
}

uint8_t CarInfoAdd(const struct CarInfo *info)
{
    for (size_t i = 0; i < CAR_LEN_MAX; i++)
    {
        if (CarList[i].mode == MODE_NONE)
        {
            CarList[i] = *info;
            --CarData[DATA_IDLE];
            ++CarData[(info->mode == MODE_CNBR) ? DATA_CNBR : DATA_VNBR];

            return HANDLE_OK;
        }
    }

    return HANDLE_ERR;
}

uint8_t CarInfoDel(uint8_t id)
{
    ++CarData[DATA_IDLE];
    --CarData[(CarList[id & 0x07].mode == MODE_CNBR) ? DATA_CNBR : DATA_VNBR];
    memset((void *)(&CarList[id & 0x07]), 0, sizeof(struct CarInfo));
    return HANDLE_OK;
}

串口数据处理

相关函数功能有:

  • 数据格式化及合法性检查
  • 时间信息格式及合法性检查
  • 时间差计算
char RecvBuf[RECV_BUF_MAX] = {0};

volatile uint8_t RecvFlag = RECV_WAIT;

void Uart_Task(void)
{
    if (RecvFlag == RECV_WAIT)
        return;

    RecvFlag = RECV_WAIT;

    char str[30] = {"Error"};
    do
    {
        if (strlen(RecvBuf) != CMD_LEN_MAX)
            // 数据长度不符合预期
            break;
        struct CarInfo Info = {0};
        if (RecvFormat(RecvBuf, &Info) == HANDLE_ERR)
            break;
        uint8_t id = CarInfoFind(Info.id);
        if (id == HANDLE_ERR)
        {
            if (CarInfoAdd(&Info) == HANDLE_ERR)
                break;
            str[0] = '\0';
            break;
        }
        // 车辆停车类型不符
        if (Info.mode != CarList[id].mode)
            break;
        // 车辆离开
        uint32_t dtime = 0;
        if (CalTime(&CarList[id].time, &Info.time, &dtime) == HANDLE_ERR)
            break;
        sprintf(str, "%.4s:%.4s:%lu:%.2f",
                ((Info.mode == MODE_CNBR) ? "CNBR" : "VNBR"),
                (char *)(&Info.id),
                dtime,
                dtime * FeeData[Info.mode - 1]);
        CarInfoDel(id);

    } while (0);

    HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), 20);
    memset((void *)RecvBuf, 0, RECV_BUF_MAX);
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)RecvBuf, RECV_BUF_MAX);
}

uint8_t CalTime(const struct Time *t0, const struct Time *t1, uint32_t *ret)
{
    // 计算秒
    uint32_t tt0 = t0->SS + t0->mm * T_1m + t0->HH * T_1H + t0->DD * T_1D;
    uint32_t tt1 = t1->SS + t1->mm * T_1m + t1->HH * T_1H + t1->DD * T_1D;
    long dt = 0;

    // 计算月
    for (size_t i = 0; i < t0->MM; i++)
        tt0 += ((i % 2 == ((i < 8) ? 1 : 0)) ? 31 : 30) * T_1D;
    for (size_t i = 0; i < t1->MM; i++)
        tt1 += ((i % 2 == ((i < 8) ? 1 : 0)) ? 31 : 30) * T_1D;

    // 计算年
    for (size_t i = 0; i < t0->YY; i++)
        tt0 += (((t0->YY % 4) ? 355 : 356) * T_1D);
    for (size_t i = 0; i < t1->YY; i++)
        tt1 += (((t1->YY % 4) ? 355 : 356) * T_1D);

    dt = tt1 - tt0;
    if (dt < 0)
        return HANDLE_ERR;

    // 把秒换算成时
    *ret = (dt / T_1H) + ((dt % T_1H == 0) ? 0 : 1);

    return HANDLE_OK;
}

uint8_t TimeCheck(const struct Time *time)
{
    // 1 3 5 7 8 10 12 => 31
    // 2 4 6 9 11 => 30
    // 时间信息验证
    // 闰年
    if (time->MM == 2)
        if (time->DD > ((time->YY % 4) ? 28 : 29))
            return 1;

    if (time->MM > 12 || time->MM < 1)
        return 1;
    // 大小月
    if (time->DD > ((time->MM % 2 == ((time->MM < 8) ? 1 : 0)) ? 31 : 30) || time->DD < 1)
        return 1;

    if (time->HH > 23 || time->HH < 0)
        return 1;

    if (time->mm > 59 || time->mm < 0)
        return 1;

    if (time->SS > 59 || time->SS < 0)
        return 1;

    return 0;
}

uint8_t RecvFormat(const char *s, struct CarInfo *d)
{
    struct CarInfo info = {};
    char mode[5] = {}, id[5] = {};
    uint8_t ret = 0;

    ret = sscanf(s, "%4s:%4s:%02d%02d%02d%02d%02d%02d",
                 mode, id, &info.time.YY, &info.time.MM, &info.time.DD, &info.time.HH, &info.time.mm, &info.time.SS);
    // 数据格式验证
    if (ret != 8)
        return HANDLE_ERR;

    // 停车模式验证
    if (strcmp(mode, "CNBR") == 0)
        info.mode = MODE_CNBR;
    else if (strcmp(mode, "VNBR") == 0)
        info.mode = MODE_VNBR;
    else
        return HANDLE_ERR;

    // 时间格式验证
    if (TimeCheck(&info.time))
        return HANDLE_ERR;

    info.id = *((uint32_t *)id);
    *d = info;

    return HANDLE_OK;
}

完整代码

参见Github链接蓝桥杯第十二届省赛

Last modification:April 22nd, 2021 at 03:04 pm