原题展示
通读本试题后,可以知本试题所涉及到的模块有LCD显示、LED指示、按键切换、串口收发、定时器的PWM输出五个部分,试题的总体变化不大。在试题要求的所有功能中,串口这部分是侧重点,它既要负责收发数据,又要对数据进行验证与处理,可谓是试题所有功能中最难的部分。话不多说,下面小编就带大家一起来看看第十二届省赛试题吧!😍😍😍
题解
LED模块
CubeMx配置
代码样例
在该试题要求比较单一,只要求有空闲车位时点亮LED1、PA7输出2KHz,占空比为20%的脉冲时点亮LED2,其余情况所有的LED灯都熄灭,因此,我们的LED工作函数只需要两个:关闭所有的LED灯以及点亮某个LED灯。但是要注意的是:在初始化完成LCD后以及每次操作LED时都需要先熄灭所有的LED灯,否则开发板上的8个LED会出现工作紊乱的情况。
/*****************************************************
* 函数功能:改变所有LED的状态
* 函数参数:
* char LEDSTATE: 0-表示关闭 1-表示打开
* 函数返回值:无
******************************************************/
void changeAllLedByStateNumber(char LEDSTATE)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12,(LEDSTATE==1?GPIO_PIN_RESET:GPIO_PIN_SET));
//打开锁存器 准备写入数据
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
//关闭锁存器 锁存器的作用为 使得锁存器输出端的电平一直维持在一个固定的状态
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/*****************************************************
* 函数功能:根据LED的位置打开或者是关闭LED
* 函数参数:
* uint16_t LEDLOCATION:需要操作LED的位置
* char LEDSTATE: 0-表示关闭 1-表示打开
* 函数返回值:无
******************************************************/
void changeLedStateByLocation(uint16_t LEDLOCATION,char LEDSTATE)
{
HAL_GPIO_WritePin(GPIOC,LEDLOCATION,(LEDSTATE==1?GPIO_PIN_RESET:GPIO_PIN_SET));
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
按键模块
CubeMx配置
代码样例
试题对于按键的要求比较简单,只要能够识别按键按下的位置即可,但是需要注意的是:按键需要消抖,通常使用的消抖方式有:延时消抖与状态机,由于G431开发板的按键数量较少,小编采用的是延时消抖。值得一提的是,小编在按键扫描函数中加入锁机制也是为了消抖,防止二次触发。😉😉😉
代码逻辑也比较简单,核心思想就是:在按键锁打开的前提下,间隔地读取两次按键的值,如果两次值都显示按键已经按下,那么按键就确实是按下;否则,就存在抖动。
/*********************************************
* 函数功能:按键扫描 含按键消抖 无长按短按设计
* 函数参数:无
* 函数返回值:按键的位置
* 返回值说明:B1-1 B2-2 B3-3 B4-4
*********************************************/
unsigned char scanKey(void)
{
//按键锁
static unsigned char keyLock = 1;
//记录按键消抖时间
// static uint16_t keyCount = 0;
//按键按下
if((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == RESET || HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == RESET
|| HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) == RESET || HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET)
&& keyLock == 1){
//给按键上锁 避免多次触发按键
keyLock = 0;
//按键消抖 这里最好不要使用延时函数进行消抖 会影响系统的实时性
// if(++keyCount % 10 < 5) return 0;
// if(HAL_GetTick()%15 < 10) return 0;
HAL_Delay(10);
//按键B1
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == RESET){
return 1;
}
//按键B2
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == RESET){
return 2;
}
//按键B3
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) == RESET){
return 3;
}
//按键B4
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET){
return 4;
}
}
//按键松开
if((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == SET && HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == SET
&& HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) == SET && HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == SET)
&& keyLock == 0){
//开锁
keyLock = 1;
}
return 0;
}
LCD模块
LCD显示模块是所有模块中初始化最简单的啦!由于官方提供了源代码,咱只需要会使用即可,初始化都可以不使用CubeMX,直接使用源码初始化就好啦。
LCD初始化样例
下面会将LCD显示屏初始化成背景色为黑色、字体颜色为白色的屏幕。LCD显示屏支持的颜色有: 白色(White)、黑色(Black) 、灰色(Grey)、蓝色(Blue)、蓝色2( Blue2)、红色(Red) 、品红色(Magenta)、绿色(Green)、青色(Cyan) 、黄色(Yellow)。
/******************************************************************************
* 函数功能:LCD初始化
* 函数参数:无
* 函数返回值:无
*******************************************************************************/
void lcdInit(void)
{
//HAL库的初始化
LCD_Init();
//设置LCD的背景色
LCD_Clear(Black);
//设置LCD字体颜色
LCD_SetTextColor(White);
//设置LCD字体的背景色
LCD_SetBackColor(Black);
}
定时器的PWM输出
CubeMx配置
代码样例
在本次试题中,PWM的输出要有两种即可,其一是频率为2KHz、占空比为20%的脉冲信号,另一种是持续输出低电平,那么持续输出低电平是不是可以理解为输出频率2KHz、占空比为0%的脉冲信号呢?😂😂😂答案是当然可以啦,题目要求是输出低电平,那我占空比为0的脉冲信号肯定也是符合要求的呀!!!😜😜😜
由此,只要按键B4按下,使用函数_pwmWorkByFre()切换PWM工作的占空比即可。
/****************************************
* 函数功能:修改PWM的占空比
* 函数参数:
* unsigned int compareDate:PWM的比较值
* 函数返回值:无
****************************************/
void _pwmWorkByFre(unsigned int compareDate)
{
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,compareDate);
}
串口收发模块
CubeMX配置
代码样例
在试题的要求中,单纯的数据传输还是蛮简单的,只要满足能够接收串口发送的数据以及当串口数据出现不符合要求时发送错误信息即可。
使用中断接收PC发送的数据
为了防止系统未处理完成上一次PC发送的数据,再次接收本次PC发送的数据带来的影响,在串口接收与数据处理中特意加了锁机制。每次系统接收PC数据后会关闭锁,避免多次接收数据;每次数据处理完成后,就会打开锁,系统才会开始接收PC数据。
从PC接收固定长度的数据:
/***使用HAL_UART_Receive_IT中断接收数据 每次接收完成数据后就会执行该函数***/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
// 重新使能中断
if(!iRxFlag)
{
HAL_UART_Receive_IT(huart,(uint8_t *)&ucRxbuff,sizeof(ucRxbuff));
iRxFlag = 1;
}
}
}
从PC接收变长数据,当然这里的变长也是有最大值的:
//定义一个串口信息的结构
uint8_t ucRxbuff[30];
uint8_t _ucRxbuff[1],lenBuff = 0;
/***使用HAL_UART_Receive_IT中断接收数据 每次接收完成数据后就会执行该函数***/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){
ucRxbuff[lenBuff++%30] = _ucRxbuff[0];
// 重新使能中断
HAL_UART_Receive_IT(huart,(uint8_t *)&_ucRxbuff,sizeof(_ucRxbuff));
}
}
如果大家使用固定长度接收PC数据,那么一旦PC发送的数据长度小于固定长度,就会变成累计接收数据模式哈。
串口发送错误信息
由于题目要求发送信息比较简单,因此,只需要使用HAL库提供的串口发送函数即可
HAL_UART_Transmit(&huart1,(uint8_t*)“Error\r\n”,sizeof(“Error\r\n”),50);
。
函数解析:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout);
/*
/*函数解析*/
UART_HandleTypeDef *huart:串口编号
const uint8_t *pData:发送的数据
uint16_t Size:发送数据的大小
uint32_t Timeout:超时时间,需要注意的时其为发送数据的最大时间,如果时间到了,但是数据未发送完,也不会再发送数据了
*/
串口数据处理
串口数据处理时需要注意以下五点:
- 数据长度是否符合要求;
- 数据中的停车类型是否符合要求;
- 数据中的时间是否符合要求;
- 停车场中是否还有空余车位给新来的车停放;
- 车辆出库时的停车类型是否跟车辆入库时停车类型一致;
只要上述五点中有一点不满足,本次接收数据处理完成后都应该返回Error。
样例代码
函数逻辑:
- 步骤一:判断数据长度是否符合要求,如果不符合要求,发送错误信息后返回,否则,进入步骤二;
- 步骤二:判断该车辆是否是新车入库,如果是,则新增一个存储车辆信息的节点到链表中,函数返回;否则,进入步骤三;
- 步骤三:车辆出库,先查找车辆入库时间,再判断出库时间以及车辆停车类型是否合理,如果合理,将入库出库时间转换成秒求差后,计算相差的时间间隔,最后进入步骤四;否则发送错误信息后,函数返回;
- 步骤四:根据车辆停车类型计算停车费,并且通过串口发送到PC,最后删除车辆信息后返回。
/****************************************
* 函数功能:处理接收串口信息
* 函数参数:无
* 函数返回值:无
****************************************/
void _usartMsgProcess(void)
{
char temp[19];
//串口未收到数据该函数应该直接返回
if(strlen((char*)ucRxbuff) == 0) return ;
struct node*msg = NULL;
//串口发送的数据长度不对
if(strlen((char*)ucRxbuff)!= 22)
{
goto MYERROR;
}
//获取本次车辆的车牌 用于查找该车是否已经入库
getStringRxBuffDataByLocation((char*)ucRxbuff,temp,5,9);
msg = searchListNode(pcarMessage,temp);
//车辆出库
if(msg != NULL)
{
uint32_t longTime[3] = {0,0,0};
//记录停车时间 单位为h
double dStopTime = 0;
//记录停车费用 单位为元
double dStopMoney = 0;
//记录开始停车时间 结束停车时间
struct node*eTime = (struct node*)malloc(sizeof(struct node));
getStringRxBuffDataByLocation((char*)ucRxbuff,eTime->ucType,0,4);
eTime->year = getIntRxBuffDataByLocation((char*)ucRxbuff,10,12);
eTime->month = getIntRxBuffDataByLocation((char*)ucRxbuff,12,14);
eTime->day = getIntRxBuffDataByLocation((char*)ucRxbuff,14,16);
eTime->hour = getIntRxBuffDataByLocation((char*)ucRxbuff,16,18);
eTime->minute = getIntRxBuffDataByLocation((char*)ucRxbuff,18,20);
eTime->second = getIntRxBuffDataByLocation((char*)ucRxbuff,20,22);
//判断数据是否合理 不合理直接返回打印错误信息
if(checkData(msg->ucType,eTime->year,eTime->month,eTime->day,eTime->hour,eTime->minute,eTime->second)==0 || strcmp(eTime->ucType,msg->ucType)!=0)
{
goto MYERROR;
}
else
{
//时间转换
longTime[0] = myMktime(2000+msg->year,msg->month,msg->day,msg->hour,msg->minute,msg->second);
longTime[1] = myMktime(2000+eTime->year,eTime->month,eTime->day,eTime->hour,eTime->minute,eTime->second);
longTime[2] = longTime[1]-longTime[0];
//计算停留时间
dStopTime = ceil((double)(longTime[2]*1.0/3600));
//计算停车费
if(msg->ucType[0] == 'C')
dStopMoney = dStopTime*dCnbrPrice;
else
dStopMoney = dStopTime*dVnbrPrice;
//发送信息到PC
sprintf(temp,"%s:%s:%.0f,%.2f",msg->ucType,msg->ucCode,dStopTime,dStopMoney);
HAL_UART_Transmit(&huart1,(uint8_t*)temp,sizeof(temp),150);
//车辆出库
deleteListNode(pcarMessage,msg->ucCode);
}
}
// 新车入库
else
{
//新车入库 需要新增一个节点存储车辆信息
struct node* node = (struct node*)malloc(sizeof(struct node));
node->pNext = NULL;
getStringRxBuffDataByLocation((char*)ucRxbuff,node->ucType,0,4);
getStringRxBuffDataByLocation((char*)ucRxbuff,node->ucCode,5,9);
node->year = getIntRxBuffDataByLocation((char*)ucRxbuff,10,12);
node->month = getIntRxBuffDataByLocation((char*)ucRxbuff,12,14);
node->day = getIntRxBuffDataByLocation((char*)ucRxbuff,14,16);
node->hour = getIntRxBuffDataByLocation((char*)ucRxbuff,16,18);
node->minute = getIntRxBuffDataByLocation((char*)ucRxbuff,18,20);
node->second = getIntRxBuffDataByLocation((char*)ucRxbuff,20,22);
//判断数据是否合理 不合理直接返回打印错误信息
if(checkData(node->ucType,node->year,node->month,node->day,node->hour,node->minute,node->second)==0 || pcarMessage->uiIdleCount-1<0)
goto MYERROR;
//数据无误 添加车辆信息到存储链表中
else
addListNode(pcarMessage,node);
}
//清除本次串口接收到的数据 避免影响后续数据
memset(ucRxbuff,0,sizeof(ucRxbuff));
//处理完本次串口接收到的数据后清除标志位
iRxFlag = 0;
return ;
//接收数据出现问题时 发送错误信息到PC
MYERROR:
HAL_UART_Transmit(&huart1,(uint8_t*)"Error\r\n",sizeof("Error\r\n"),50);
//清除本次串口接收到的数据 避免影响后续数据
memset(ucRxbuff,0,sizeof(ucRxbuff));
//处理完本次串口接收到的数据后清除标志位
iRxFlag = 0;
}
如果大家认为上面函数处理过程太过复杂,大家还可以尝试利用sscanf()
函数从字符串中取出目标数据,详细的处理过程大家可见下函数:
//解析串口信息
sscanf((char*)ucRxbuff,"%4s:%4s:%2d%2d%2d%2d%2d%2d",node->ucType,node->ucCode,&node->year,&node->month,&node->day,&node->hour,&node->minute,&node->second);
msg = searchListNode(pcarMessage,node->ucCode);
//车辆出库
if(msg != NULL)
{
uint32_t longTime[3] = {0,0,0};
//记录停车时间 单位为h
double dStopTime = 0;
//记录停车费用 单位为元
double dStopMoney = 0;
//判断数据是否合理 不合理直接返回打印错误信息
if(checkData(msg->ucType,node->year,node->month,node->day,node->hour,node->minute,node->second)==0 || strcmp(node->ucType,msg->ucType)!=0)
goto MYERROR;
else
{
//时间转换
longTime[0] = myMktime(2000+msg->year,msg->month,msg->day,msg->hour,msg->minute,msg->second);
longTime[1] = myMktime(2000+node->year,node->month,node->day,node->hour,node->minute,node->second);
longTime[2] = longTime[1]-longTime[0];
//计算停留时间
dStopTime = ceil((double)(longTime[2]*1.0/3600));
//计算停车费
if(msg->ucType[0] == 'C')
dStopMoney = dStopTime*dCnbrPrice;
else
dStopMoney = dStopTime*dVnbrPrice;
//发送信息到PC
sprintf(temp,"%s:%s:%.0f,%.2f\r\n",msg->ucType,msg->ucCode,dStopTime,dStopMoney);
HAL_UART_Transmit(&huart1,(uint8_t*)temp,sizeof(temp),150);
//车辆出库
deleteListNode(pcarMessage,msg->ucCode);
}
}
// 新车入库
else
{
node->pNext = NULL;
//判断数据是否合理 不合理直接返回打印错误信息
if(checkData(node->ucType,node->year,node->month,node->day,node->hour,node->minute,node->second)==0 || pcarMessage->uiIdleCount-1<0)
goto MYERROR;
//数据无误 添加车辆信息到存储链表中
else
addListNode(pcarMessage,node);
}
(大家看是不是很简洁🤣🤣🤣)
使用sscanf()
函数,就可以不用再写函数获取字符串指定位置的内容了。下面就是一段关于sscanf()
函数的解释:
int sscanf(const char *str, const char *format, …)
函数,是一个C 库函数,其功能是从字符串读取格式化输入。
- 参数 str: 是 C 字符串,是函数检索数据的源;
- 参数format: 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符;
- …:表示该函数支持是一个可变参数。
格式化使用时与scanf()
类似,如这就是一个格式化输入的示例: sscanf(“today is 6”,“%5s %2s %d”,str1,srt2,&data)
,其目的是将字符串"today is 6"挨个取出。
辅助数据处理的函数
获取字符串指定位置的内容
在C语言中,对字符串赋值操作并不是特别舒服,因此,写了两个字符串赋值函数,一个函数是将字符串指定位置的值转换成整型数据返回,另一个函数是获取字符串指定位置为值,以字符串返回。
/****************************************
* 函数功能:将指定位置字符串转换成整型数字
* 函数参数:
* char*s:传入的字符串数字
* int iStart:起始位置
* int iEnd:终止位置
* 函数返回值:
* res:返回的整型数字
****************************************/
int getIntRxBuffDataByLocation(char*s,int iStart,int iEnd)
{
int res = 0;
while(iStart < iEnd)
{
res = res*10 + s[iStart] - '0';
iStart++;
}
return res;
}
/****************************************
* 函数功能:获取指定位置字符串
* 函数参数:
* char*s:传入的字符串
* int iStart:起始位置
* int iEnd:终止位置
* 函数返回值:
* res:返回的字符串
****************************************/
void getStringRxBuffDataByLocation(char*s,char*res,int iStart,int iEnd)
{
int j=0;
while(iStart < iEnd)
{
res[j++] = s[iStart++];
}
res[j++] = '\0';
}
时间处理
考虑到串口接收数据的多样性——只同年的时间、同年同月的时间、同年同月同天的时间等等,小编特意写了时间转换函数,其能够将年月日时分秒的时间转换成秒,这样就不用再考虑停车起始时间是否同年、同月、同天等情况了。)在获取停车时间时,只需要先将两个时间转换成秒,再计算时间间隔,最后除一小时时间间隔(60*60s)向上取整就可以得到停车时间。
//存储每月的天数
int monthTable[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
/****************************************
* 函数功能:将一个年月日时分秒的时间转换成秒
* 函数参数:
* const unsigned int year0:转换时间的年
* const unsigned int mon0:转换时间的月
* const unsigned int day:转换时间的天
* const unsigned int hour:转换时间的小时
* const unsigned int min:转换时间的分钟
* const unsigned int sec:转换时间的秒
* 函数返回值:
* res:返回转换成秒后的时间
****************************************/
unsigned long myMktime(const unsigned int year0, const unsigned int mon0,const unsigned int day,
const unsigned int hour,const unsigned int min,const unsigned int sec)
{
unsigned int mon = mon0, year = year0;
/* 1..12 -> 11,12,1..10 */
if (0 >= (int) (mon -= 2)) {
mon += 12; /* Puts Feb last since it has leap day */
year -= 1;
}
return ((((unsigned long)
(year/4 - year/100 + year/400 + 367*mon/12 + day) +
year*365 - 719499
)*24 + hour /* now have hours */
)*60 + min /* now have minutes */
)*60+sec; /* finally seconds */
}
链表处理
- 链表头以及节点的结构体包含的内容。
//链表头结点
struct head{
//指向存储辆车的信息
struct node*pNext;
//记录空余车位数量
int uiIdleCount;
//CNBR类型车辆数量
int uiCnbrCount;
//VNBR类型车辆的数量
int uiCnbrCount;
};
//车辆信息结构体
struct node{
//记录停车类型
char ucType[5];
//记录车牌号
char ucCode[5];
//记录进入时间
int year;
int month;
int day;
int hour;
int minute;
int second;
//指向下一辆车信息
struct node*pNext;
};
- 增加链表节点
判断链表中是否含有节点,如果没有,就直接添加到头结点中;否则就先移动到链表末端,再添加节点;
- 删除节点
判断删除的是否是头结点,如果是头结点,那么就返回除头结点外的所有节点;否则就先移动到待删除节点的前,将其指向待删除节点的后一个节点,最后返回即可。
- 查找节点
查找节点比较简单,直接遍历链表,挨个节点判断是否是目标节点,如果是目标节点就返回目标节点,否则就返回NULL;
- 获取节点数量
由于每次添加节点时都会增加uiCnbrCount的值或uiVnbrCount的值,因此只要返回uiCnbrCount+uiVnbrCount即可。
/*******************************************
* 函数功能:给链表添加节点
* 参数:
* struct head*head:链表的头结点
* LISTNODETYPE*newNode:链表新增的节点
* 返回值:无
********************************************/
void addListNode(struct head*head,struct node*newNode)
{
struct node*list = head->pNext;
//判断是否没有任何节点
if(list)
{
//移动到待添加位置的前一个位置
while(list->pNext){
list = list->pNext;
}
//添加节点并且给链表长度加1
list->pNext = newNode;
}
else
{
head->pNext = newNode;
}
//判断节点的类型
if(newNode->ucType[0] == 'C')
{
head->uiCnbrCount++;
head->uiIdleCount--;
}
else if(newNode->ucType[0] == 'V')
{
head->uiVnbrCount++;
head->uiIdleCount--;
}
}
/*******************************************
* 函数功能:给链表删除节点
* 参数:
* struct head*head:链表的头结点
* unsigned char*target:目标值
* 返回值:无
********************************************/
void deleteListNode(struct head*head,char*target)
{
struct node*p,*q;
if(!head->pNext) return ;
p = head->pNext;
//判断头结点是否是目标值
if(strcmp((char*)p->ucCode,(char*)target))
{
//遍历出头节点外的所有节点
while(p->pNext && strcmp((char*)p->pNext->ucCode,(char*)target))
{
p = p->pNext;
}
q = p->pNext;
p->pNext = p->pNext->pNext;
}
else
{
//删除头结点
q = head->pNext;
head->pNext = head->pNext->pNext;
}
//判断目标值的类型
if(q->ucType[0] == 'C')
{
head->uiCnbrCount--;
head->uiIdleCount++;
}
else if(q->ucType[0] == 'V')
{
head->uiVnbrCount--;
head->uiIdleCount++;
}
}
/*******************************************
* 函数功能:判断链表是否为空
* 参数:
* struct head*head:链表的头结点
* 返回值:
* 链表为空返回0 否则返回链表长度
********************************************/
unsigned int isEmptyListNode(struct head*head)
{
return head->uiCnbrCount+head->uiVnbrCount;
}
/*******************************************
* 函数功能:查找链表
* 参数:
* struct head*head:链表的头结点
*
* 返回值:
* 链表无值返回0 否则返回1
********************************************/
struct node* searchListNode(struct head*head,char*target)
{
struct node*p = head->pNext;
//遍历链表
while(p)
{
//判断是否应该删除
if(!strcmp(p->ucCode,target))
{
return p;
}
p = p->pNext;
}
return NULL;
}
完整的系统配置文件
- 使用说明
sysInit()函数是在CubeMx配置完成后新增的初始化函数,sysWork()是系统的工作逻辑函数,其余函数都是一些小型配置函数,均被sysInit()或sysWork函数调用过了。
#include "config.h"
/* 存储串口1接收的数据
** 数据样例:CNBR: A392: 2002021 20000 停车类型:车辆编号:进入/退出时间(YYMMDDHmmSS)
** 数据样例解释:表示停车类型CNBR,编号为A392的车辆,进入停车场时间为2020年2月2日12时整。
**/
//频率测量
u32 crrl_t,frd;
u32 oldFrd = 1;
//记录定时器7触发次数
uint16_t uiTime7Count = 0;
//记录按键的值
uint8_t ucKeyNumber = 0;
//记录车辆信息的头指针
struct head*pcarMessage;
//VNBR类型停车单价
double dVnbrPrice = 2.0;
//CNBR类型停车单价
double dCnbrPrice = 3.5;
//记录显示界面
int iDisplayMod = 1;
//记录PWM输出的模式
int iPwmMode = 0;
/***********************************************
* 函数功能:自定义的系统初始化
* 函数参数:无
* 函数返回值:无
***********************************************/
void sysInit(void)
{
//LCD初始化
lcdInit();
//关闭所有的LED
changeAllLedByStateNumber(0);
//打开串口的中断接收功能
HAL_UART_Receive_IT(&huart1,(uint8_t *)&_ucRxbuff,sizeof(_ucRxbuff));
//打开定时器17通道1的PWM输出功能
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);
_pwmWorkByFre(500);
//为汽车存储申请空间
pcarMessage = (struct head*)malloc(sizeof(struct head));
pcarMessage->uiIdleCount = 8;
pcarMessage->uiCnbrCount = 0;
pcarMessage->uiVnbrCount = 0;
}
/***********************************************
* 函数功能:系统工作逻辑函数
* 函数参数:无
* 函数返回值:无
***********************************************/
void sysWork(void)
{
//按键处理
_keyPro();
//串口数据处理
_usartMsgProcess();
//显示停车场车辆信息
if(iDisplayMod)
_dataMessageDisplay();
//显示单价信息
else
_paraMessageDisplay();
//LED工作函数
_LEDDisplay();
}
/***********************************************
* 函数功能:按键工作逻辑函数
* 函数参数:无
* 函数返回值:无
***********************************************/
void _keyPro(void)
{
ucKeyNumber = scanKey();
switch(ucKeyNumber)
{
//按键B1 切换显示界面
case 1:
iDisplayMod ^= 1;
break;
//按键B2 增加费率
case 2:
if(!iDisplayMod)
{
dVnbrPrice += 0.5;
dCnbrPrice += 0.5;
}
break;
//按键B3 减少费率
case 3:
if(!iDisplayMod)
{
dVnbrPrice -= 0.5;
dCnbrPrice -= 0.5;
}
break;
//按键B4 切换PA7的PWM输出
case 4:
iPwmMode ^= 1;
//输出占空比为20的PWM
if(iPwmMode)
_pwmWorkByFre(200);
//持续输出低电平
else
_pwmWorkByFre(500);
break;
default:return ;
}
ucKeyNumber = 0;
}
/****************************************
* 函数功能:汽车信息显示界面
* 函数参数:无
* 函数返回值:无
****************************************/
void _dataMessageDisplay(void)
{
char temp[20];
LCD_DisplayStringLine(Line1,(uint8_t*)" Data");
sprintf(temp," CNBR:%3d ",pcarMessage->uiCnbrCount);
LCD_DisplayStringLine(Line3,(uint8_t*)temp);
sprintf(temp," VNBR:%3d ",pcarMessage->uiVnbrCount);
LCD_DisplayStringLine(Line5,(uint8_t*)temp);
sprintf(temp," IDLE:%3d",pcarMessage->uiIdleCount);
LCD_DisplayStringLine(Line7,(uint8_t*)temp);
}
/****************************************
* 函数功能:汽车信息显示界面
* 函数参数:无
* 函数返回值:无
****************************************/
void _paraMessageDisplay(void)
{
char temp[20];
LCD_DisplayStringLine(Line1,(uint8_t*)" Para");
sprintf(temp," CNBR:%.2f",dCnbrPrice);
LCD_DisplayStringLine(Line3,(uint8_t*)temp);
sprintf(temp," VNBR:%.2f",dVnbrPrice);
LCD_DisplayStringLine(Line5,(uint8_t*)temp);
LCD_ClearLine(Line7);
}
/****************************************
* 函数功能:修改PWM的占空比
* 函数参数:
* unsigned int compareDate:PWM的比较值
* 函数返回值:无
****************************************/
void _pwmWorkByFre(unsigned int compareDate)
{
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,compareDate);
}
/****************************************
* 函数功能:LED工作函数
* 函数参数:无
* 函数返回值:无
****************************************/
void _LEDDisplay(void)
{
//关闭所有LED灯
changeAllLedByStateNumber(0);
//有空余 LED1点亮
if(pcarMessage->uiIdleCount > 0)
changeLedStateByLocation(LED1,1);
//PWM占空比为20 LED2点亮
if(iPwmMode)
changeLedStateByLocation(LED2,1);
}
/****************************************
* 函数功能:处理接收串口信息
* 函数参数:无
* 函数返回值:无
****************************************/
void _usartMsgProcess(void)
{
char temp[19];
//串口未收到数据该函数应该直接返回
if(strlen((char*)ucRxbuff) == 0) return ;
struct node*msg = NULL;
//串口发送的数据长度不对
if(strlen((char*)ucRxbuff)!= 22)
{
goto MYERROR;
}
//获取本次车辆的车牌 用于查找该车是否已经入库
getStringRxBuffDataByLocation((char*)ucRxbuff,temp,5,9);
msg = searchListNode(pcarMessage,temp);
//车辆出库
if(msg != NULL)
{
uint32_t longTime[3] = {0,0,0};
//记录停车时间 单位为h
double dStopTime = 0;
//记录停车费用 单位为元
double dStopMoney = 0;
//记录开始停车时间 结束停车时间
struct node*eTime = (struct node*)malloc(sizeof(struct node));
getStringRxBuffDataByLocation((char*)ucRxbuff,eTime->ucType,0,4);
eTime->year = getIntRxBuffDataByLocation((char*)ucRxbuff,10,12);
eTime->month = getIntRxBuffDataByLocation((char*)ucRxbuff,12,14);
eTime->day = getIntRxBuffDataByLocation((char*)ucRxbuff,14,16);
eTime->hour = getIntRxBuffDataByLocation((char*)ucRxbuff,16,18);
eTime->minute = getIntRxBuffDataByLocation((char*)ucRxbuff,18,20);
eTime->second = getIntRxBuffDataByLocation((char*)ucRxbuff,20,22);
//判断数据是否合理 不合理直接返回打印错误信息
if(checkData(msg->ucType,eTime->year,eTime->month,eTime->day,eTime->hour,eTime->minute,eTime->second)==0 || strcmp(eTime->ucType,msg->ucType)!=0)
{
goto MYERROR;
}
else
{
//时间转换
longTime[0] = myMktime(2000+msg->year,msg->month,msg->day,msg->hour,msg->minute,msg->second);
longTime[1] = myMktime(2000+eTime->year,eTime->month,eTime->day,eTime->hour,eTime->minute,eTime->second);
longTime[2] = longTime[1]-longTime[0];
//计算停留时间
dStopTime = ceil((double)(longTime[2]*1.0/3600));
//计算停车费
if(msg->ucType[0] == 'C')
dStopMoney = dStopTime*dCnbrPrice;
else
dStopMoney = dStopTime*dVnbrPrice;
//发送信息到PC
sprintf(temp,"%s:%s:%.0f,%.2f\r\n",msg->ucType,msg->ucCode,dStopTime,dStopMoney);
HAL_UART_Transmit(&huart1,(uint8_t*)temp,sizeof(temp),150);
//车辆出库
deleteListNode(pcarMessage,msg->ucCode);
}
}
// 新车入库
else
{
//新车入库 需要新增一个节点存储车辆信息
struct node* node = (struct node*)malloc(sizeof(struct node));
node->pNext = NULL;
getStringRxBuffDataByLocation((char*)ucRxbuff,node->ucType,0,4);
getStringRxBuffDataByLocation((char*)ucRxbuff,node->ucCode,5,9);
node->year = getIntRxBuffDataByLocation((char*)ucRxbuff,10,12);
node->month = getIntRxBuffDataByLocation((char*)ucRxbuff,12,14);
node->day = getIntRxBuffDataByLocation((char*)ucRxbuff,14,16);
node->hour = getIntRxBuffDataByLocation((char*)ucRxbuff,16,18);
node->minute = getIntRxBuffDataByLocation((char*)ucRxbuff,18,20);
node->second = getIntRxBuffDataByLocation((char*)ucRxbuff,20,22);
//判断数据是否合理 不合理直接返回打印错误信息
if(checkData(node->ucType,node->year,node->month,node->day,node->hour,node->minute,node->second)==0 || pcarMessage->uiIdleCount-1<0)
goto MYERROR;
//数据无误 添加车辆信息到存储链表中
else
addListNode(pcarMessage,node);
}
//清除本次串口接收到的数据 避免影响后续数据
memset(ucRxbuff,0,sizeof(ucRxbuff));
//处理完本次串口接收到的数据后清除标志位
iRxFlag = 0;
return ;
//接收数据出现问题时 发送错误信息到PC
MYERROR:
HAL_UART_Transmit(&huart1,(uint8_t*)"Error\r\n",sizeof("Error\r\n"),50);
//清除本次串口接收到的数据 避免影响后续数据
memset(ucRxbuff,0,sizeof(ucRxbuff));
//处理完本次串口接收到的数据后清除标志位
iRxFlag = 0;
}
小结
总的来说,本届试题难度不大,知识点也比较常规,但是存储车辆信息的方式以及处理串口数据是试题中比较难的部分, 需要各位多花心思。
最后,小编在此处附上获取源码的链接蓝桥杯嵌入式源码。
文章福利
下边是小编个人整理出来免费的蓝桥杯嵌入式福利,有需要的童鞋可以自取哟!🤤🤤🤤
省赛:
- 【蓝桥杯嵌入式】第十一届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解
- 【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛程序设计试题以及详细题解
- 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛程序设计试题及其详细题解
- 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解
- 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛客观题以及详细题解
- 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛[第二场]客观题以及详细题解
- 【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛客观题及详细题解
国赛:
其他:
- 【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式[模拟赛1]客观题及详细题解
- 【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式[模拟赛1]程序设计试题及详细题解
- 【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式[模拟赛2]客观题及详细题解
- 【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式[模拟赛2]程序设计试题及详细题解
- 【蓝桥杯】一文解决蓝桥杯嵌入式开发板(STM32G431RBT6)LCD与LED显示冲突问题,并讲述LCD翻转显示
这是小编自创的嵌入式交流群Q:726128226,欢迎各位大佬加入交流哟!😁😁😁
也欢迎大家留言或私信交流,共同进步哟!😉😉😉
转载自CSDN-专业IT技术社区
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_53960242/article/details/128508314