第十二届蓝桥杯第一场省赛试题解析。
题目分析
硬件分析
本题中使用到的外设主要有: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链接蓝桥杯第十二届省赛