草庐IT

【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛程序设计试题以及详细题解

☞黑心萝卜三条杠☜ 2023-06-12 原文

文章目录

原题展示





    通读本试题后,可以知本试题所涉及到的模块有LCD显示LED指示按键切换串口收发定时器的PWM输出五个部分,试题的总体变化不大。在试题要求的所有功能中,串口这部分是侧重点,它既要负责收发数据,又要对数据进行验证与处理,可谓是试题所有功能中最难的部分。话不多说,下面小编就带大家一起来看看第十二届省赛试题吧!😍😍😍

题解

LED模块

CubeMx配置

代码样例

    在该试题要求比较单一,只要求有空闲车位时点亮LED1PA7输出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;
}

小结

    总的来说,本届试题难度不大,知识点也比较常规,但是存储车辆信息的方式以及处理串口数据是试题中比较难的部分, 需要各位多花心思。


    最后,小编在此处附上获取源码的链接蓝桥杯嵌入式源码

文章福利

下边是小编个人整理出来免费的蓝桥杯嵌入式福利,有需要的童鞋可以自取哟!🤤🤤🤤
省赛:

国赛:

其他:

这是小编自创的嵌入式交流群Q:726128226,欢迎各位大佬加入交流哟!😁😁😁

也欢迎大家留言或私信交流,共同进步哟!😉😉😉

有关【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛程序设计试题以及详细题解的更多相关文章

  1. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  2. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  3. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  4. 阿里云国际版免费试用:如何注册以及注意事项 - 2

    作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。​关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐

  5. Hive SQL 五大经典面试题 - 2

    目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类

  6. ruby - ruby 中的同一个程序如何接受来自用户的输入以及命令行参数 - 2

    我的ruby​​脚本从命令行参数获取某些输入。它检查是否缺少任何命令行参数,然后提示用户输入。但是我无法使用gets从用户那里获得输入。示例代码:test.rbname=""ARGV.eachdo|a|ifa.include?('-n')name=aputs"Argument:#{a}"endendifname==""puts"entername:"name=getsputsnameend运行脚本:rubytest.rbraghav-k错误结果:test.rb:6:in`gets':Nosuchfileordirectory-raghav-k(Errno::ENOENT)fromtes

  7. 蓝桥杯备赛(二) - 2

    目录前言: 一、ASC分析代码实现二、 卡片分析代码实现三、 直线分析代码实现四、货物摆放分析代码实现小结:前言:  在刷题的过程中,发现蓝桥杯的题目和力扣的差别很大。让人有一种不一样的感觉,蓝桥杯题目偏向对于实际问题用编程去的解决,而力扣给人感觉很锻炼自己的编程思维,逻辑能力。两者结合去刷,相信会有不一样的收获。 一、ASC  已知大写字母A的ASCII码为65,请问大写字母L的ASCII码是多少?分析  这道题目看上去很简单,我们需确定自己计算的准确,所以我建议用编程去解决。代码实现publicclassTest8{publicstaticvoidmain(String[]args){Sy

  8. 【Elasticsearch基础】Elasticsearch索引、文档以及映射操作详解 - 2

    文章目录概念索引相关操作创建索引更新副本查看索引删除索引索引的打开与关闭收缩索引索引别名查询索引别名文档相关操作新建文档查询文档更新文档删除文档映射相关操作查询文档映射创建静态映射创建索引并添加映射概念es中有三个概念要清楚,分别为索引、映射和文档(不用死记硬背,大概有个印象就可以)索引可理解为MySQL数据库;映射可理解为MySQL的表结构;文档可理解为MySQL表中的每行数据静态映射和动态映射上面已经介绍了,映射可理解为MySQL的表结构,在MySQL中,向表中插入数据是需要先创建表结构的;但在es中不必这样,可以直接插入文档,es可以根据插入的文档(数据),动态的创建映射(表结构),这就

  9. 蓝桥杯C/C++VIP试题每日一练之报时助手 - 2

    ?作者主页:静Yu?简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者?社区地址:前端知识交流社区?博主的个人博客:静Yu的个人博客?博主的个人笔记本:前端面试题个人笔记本只记录前端领域的面试题目,项目总结,面试技巧等等。接下来会更新蓝桥杯官方系统基础练习的VIP试题,依然包括解题思路,源代码等等。问题描述:给定当前的时间,请用英文的读法将它读出来。时间用时h和分m表示,在英文的读法中,读一个时间的方法是:  如果m为0,则将时读出来,然后加上“o’clock”,如3:00读作“threeo’clock”。  如果m不为0,则将时读出来,然后将分读出来,如5

  10. FPGA 之 时钟,时钟域, 以及复位系统的设计 - 2

    FPGA时钟和时钟域时钟树所谓时钟树为FPGA内部资源,分:全局时钟树,区域时钟树,IO时钟树原则上优先使用全局时钟树,在GT接口上使用IO时钟树,一般工具也会对GT时钟加以限制;时钟树使用方式正确的物理连接FPGA会由物理管脚专门用于全局时钟设置,通过查询数据手册可以在PCB设计阶段进行确认,当外部时钟接入此管脚时,工具会自动占有全局时钟树资源,当接入普通信号时不会分配时钟树资源;恰当的代码描述原语的使用,即BUFG的使用,可以将PLL的输出等内部时钟进行全局时钟资源的分配;IO时钟资源需要参考相应接口手册,以ultrascale的GTH为例,其JESD204的时钟方案针对不同的子类会由不同

随机推荐