单片机系列|第十二届省赛嵌入式设计与开发《停车计费系统》

前言 刷了几套蓝桥杯省赛程序题目,心有所感,故写下文章记录一下,本篇文章记录的是第十二届省赛嵌入式设计与开发程序设计题目。
这套题的考察重点和难点在STM32的串口和字符串的处理,难点在于细节方面,对字符串考察很细。其他知识点虽有考察,但比较常规(参考往年题目思路即可)。
文章重点讲解字符串的处理和小算法。
(一) 题目分析: 使用串口获取车辆进、出停车场信息和时间,并能够输出计费信息。
分析:
1.车辆 出车库要计算停车时间,然后根据价格计算并输出计费信息。
2.接下来,我们要考虑如何去判断小车是进车库还是出车库?
有一个办法就是构建一个数据库,进车库的车辆信息必须登记到数据库里。每当串口收到一串字符串信息,就与数据库中的车辆进行对比,如果查询到数据库有这个车,则是出库,随后打印计费信息; 如果没有这辆车, 则是入库,登记车辆信息到数据库。
3.根据题目
单片机系列|第十二届省赛嵌入式设计与开发《停车计费系统》
文章图片

串口接受的字符串是带有固定的格式,所以要先判断串口接受数据的格式和逻辑是否符合要求。
单片机系列|第十二届省赛嵌入式设计与开发《停车计费系统》
文章图片
单片机系列|第十二届省赛嵌入式设计与开发《停车计费系统》
文章图片

(1)字符串格式:CNBR : : 和 VNBR: : 这几个字符都是固定。
定义Check_String( )函数对其检查

_Bool Check_String(uint8_t* str) { if(RxCounter1 != 22) //判断收到的字节是否为22个 return 0; // //判断字符是否正确 if((str[0] == 'C' || str[0] == 'V') && str[1] == 'N' && str[2] == 'B' && str[3] == 'R' && str[4] == ':' && str[9] == ':') { uint8_t i; for(i=10; i<22; i++)//for扫描第十位及之后数字是否正确 { if(str[i]>'9'|| str[i]<'0') return 0; } } return 1; }

很多人觉得字符不能比较大小,其实是可以。可以自行百度下。
if(str[i]>'9'|| str[i]<'0')

(2)接下去我们对字符串的逻辑方面进行判断。首先1年只有12月,1月最多31天,1天只有24小时,以及只有59分钟,59秒。
if((Mouth_temp>12) || (Day_temp>31) || (Hour_temp>23) || (Min_temp>59) || (Sec_temp>59) ) //判断日期和时间是否正确 { }

(二)构建车库(数据库)
//字符串信息的处理 typedef struct { uint8_t Car_Type[5]; //车辆类型 uint8_t Car_ID[5]; //车辆编号ID uint8_t Year; uint8_t Month; uint8_t Day; uint8_t Hour; uint8_t Min; uint8_t Sec; _Bool notEmpty; }Car_StructureTypeDef; //定义车辆结构体Car_StructureTypeDef Car_Structure[8]={0}; //构建数据库

我们通过自行定义一个存储车辆信息的结构体类型,用这个类型定义一个结构体数组Car_Structure[8] ,来存储8辆车的信息(题目规定车库最多有8个车位)
原来的C语言其实没有_Bool布尔类型 , 后来C语言又加上这条规则!
很多新手对结构体不太熟练,其实STM32_Lib3.5有很多规范的写法,可以直接拿来改一下。例如熟知的GPIO_InitTypeDef类型。
typedef struct { uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIOSpeed_TypeDef */GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIOMode_TypeDef */ }GPIO_InitTypeDef;

(3)车辆的出入库判断 首先我们是通过车辆的类型(分为CNBR和VNBR)和编号ID判断车辆的唯一性。程序细节注释都很详细。
/* 判断车辆类型ID是否与数据库中相同,相同则出库,不同则入库 Car_Structure[8]-->题目中车库最多停八辆车,用for语句依次扫描数据库 */ uint8_tCar_isExist(uint8_t* str1 , uint8_t* str2) { uint8_t i; for(i=0 ; i<8 ; i++) { if( Buffercmp(str1 , Car_Structure[i].Car_Type ,5)== PASSED) //判断Car_Type是否匹配 { if(Buffercmp(str2 ,Car_Structure[i].Car_ID , 5)==PASSED) //判断Car_ID是否匹配 return i; //到这一步说明匹配成功,返回数据库中车辆索引 }return 0xFF; //表示匹配失败,车库没有这辆车。 } }

【单片机系列|第十二届省赛嵌入式设计与开发《停车计费系统》】Buffercmp()函数比较两个字符串是否相等。若字符串相等,返回PASSED,否则返回FAILED。这个函数是蓝桥杯例程中写好,直接复制粘贴。源码如下:
/* * @briefCompares two buffers. * @parampBuffer1, pBuffer2: buffers to be compared. * @paramBufferLength: buffer's length * @retval PASSED: pBuffer1 identical to pBuffer2 *FAILED: pBuffer1 differs from pBuffer2 */ typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus; TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength) { while(BufferLength--) { if(*pBuffer1 != *pBuffer2) { return FAILED; }pBuffer1++; pBuffer2++; } return PASSED; }

其实还有一种方法就是调用C库函数的strcmp函数。
strcmp函数是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结果返回整数。基本形式为strcmp(str1,str2),若str1=str2,则返回零;若str1str2,则返回正数。
好了,加上上面的代码铺垫,现在只需要编写一个串口信息处理函数,放在Main主函数即可。其主要框架是
(1)先检查字符串格式逻辑错误
(2)接着判断车辆入库还是出库
  • 入库则提取串口信息登记到定义的车库存储好
  • 出库则清除车库登记信息,计算费用时间,打印串口输出计费信息
其中车辆入库时,要考虑到车库是否有空闲车位,所以这里要编写一个找车位函数进行判断find_carLocate()
//为入库车辆找一个空闲位置 uint8_tfind_carLocate() { uint8_t i; for(i=0 ; i<8 ; i++) { if(Car_Structure[i].notEmpty == 0)//说明此位置空闲 return i; } return 0XFF; //说明车库已满,寻找位置失败 }

void usart_printf() { if(Check_String(RxBuffer1)==1) //基本的数据格式正确 { uint8_t Car_Type_temp[5]; //数组长度为5个,是因为还要加上'\0' uint8_t Car_ID_temp[5]; uint8_t Year_temp,Mouth_temp,Day_temp,Hour_temp,Min_temp,Sec_temp; Year_temp= (RxBuffer1[10]-'0')*10 + (RxBuffer1[11]-'0'); //字符转数字 Mouth_temp = (RxBuffer1[12]-'0')*10 + (RxBuffer1[13]-'0'); Day_temp = (RxBuffer1[14]-'0')*10 + (RxBuffer1[15]-'0'); Hour_temp= (RxBuffer1[16]-'0')*10 + (RxBuffer1[17]-'0'); Min_temp = (RxBuffer1[18]-'0')*10 + (RxBuffer1[19]-'0'); Sec_temp = (RxBuffer1[20]-'0')*10 + (RxBuffer1[21]-'0'); if((Mouth_temp>12) || (Day_temp>31) || (Hour_temp>23) || (Min_temp>59) || (Sec_temp>59) ) //判断日期和时间是否正确 { goto SEND_ERROR; } //VNBR:D583:200202120000 substr(Car_Type_temp , RxBuffer1 ,0 ,4); //截取RxBuffer1中car类型信息 VNBR substr(Car_ID_temp , RxBuffer1 ,5 ,4); //截取RxBuffer1中car类型信息 D583 //此处开始判断车辆是入库还是出库 if( Car_isExist(Car_Type_temp , Car_ID_temp) == 0XFF) {//车库没有这辆车,准备入库uint8_t Locate = find_carLocate(); //找一个车位 if(Locate != 0xFF) { //登记信息入库 substr(Car_Structure[Locate].Car_Type , RxBuffer1 ,0 ,4); substr(Car_Structure[Locate].Car_ID , RxBuffer1 ,5 ,4); //Car_Structure[Locate].Car_Type = Car_Type_temp; 这种写法是错的,两个数组之间不可以这样赋值 //Car_Structure[Locate].Car_ID= Car_ID_temp; Car_Structure[Locate].Year= Year_temp; Car_Structure[Locate].Month = Mouth_temp; Car_Structure[Locate].Day= Day_temp; Car_Structure[Locate].Hour =Hour_temp; Car_Structure[Locate].Min = Min_temp ; Car_Structure[Locate].Sec = Sec_temp; Car_Structure[Locate].notEmpty = 1; //此时LCD记录车位信息应更新 if(Car_Type_temp[0] == 'C') { D_Cnbr++; Idle--; } else if(Car_Type_temp[0] == 'V') { D_Vnbr++; Idle--; } }else goto SEND_ERROR; //找车位,车位已满 } else{ //车库有这辆车,准备出库,计算费用时间 uint8_t Locate = Car_isExist(Car_Type_temp, Car_ID_temp); // 1.先计算年份 7个月为31日,4个月为30日,1个月为28日(估算平均30天:多出5天) uint16_t year_time , month_time,day_time,hour_time,min_time,sec_time; uint32_t all_time; //全部化为秒为单位 year_time = (Year_temp - Car_Structure[Locate].Year)*365*24*3600 ; month_time = (Mouth_temp - Car_Structure[Locate].Month)*30*24*3600 ; day_time = (Day_temp - Car_Structure[Locate].Day)*24*3600; hour_time = (Hour_temp - Car_Structure[Locate].Hour)*3600; min_time = (Min_temp - Car_Structure[Locate].Min)*60; sec_time = (Sec_temp - Car_Structure[Locate].Sec); all_time = year_time+month_time+day_time+hour_time+min_time+sec_time; all_time = (all_time + 3599) / 3600; // 换算成小时,并且不足一个小时按一个小时算 /*这种清空结构体方式不可取*/ //Car_Structure[Locate] = { {0,0,0,0,0},{0,0,0,0,0}, 0}; /*正解:调用C库函数memset清空结构体*/ memset(&Car_Structure[Locate] ,0 , sizeof(Car_Structure[Locate]) ); Car_Structure[Locate].notEmpty = 0; //该车已经出库//此时LCD记录车位信息应更新 if(Car_Type_temp[0] == 'C') { float Money = (float)(all_time * P_Cnbr); D_Cnbr--; Idle++; printf("CNBR:%s:%d:%.2f\r\n" , Car_ID_temp ,all_time ,Money); //打印串口输出计费信息 } else if(Car_Type_temp[0] == 'V') { float Money = (float)(all_time * P_Vnbr); D_Vnbr--; Idle++; printf("VNBR:%s:%d:%.2f\r\n" , Car_ID_temp ,all_time ,Money); //打印串口输出计费信息 } } //sprintf((unsigned char *)testStr , "Car_isExist:%d" , Car_isExist(Car_Type_temp , Car_ID_temp)); //LCD_DisplayStringLine(Line9 ,testStr); goto CMD_YES; SEND_ERROR: printf("Error\n\r"); CMD_YES: RxCounter1=0; } }

这里提一下(一)应该如何正确对结构体变量进行清0操作。
/*这种清空结构体方式不可取*/ //Car_Structure[Locate] = { {0,0,0,0,0},{0,0,0,0,0}, 0}; /*正解:调用C库函数memset清空结构体*/ memset(&Car_Structure[Locate] ,0 , sizeof(Car_Structure[Locate]) );

详情见–>void *memset(void *str, int c, size_t n)
(二)以下赋值不可取
int a[5]="abcde"; int b[5]; b = a; //这种方式是错误的//题目程序 //Car_Structure[Locate].Car_Type = Car_Type_temp; //Car_Structure[Locate].Car_ID= Car_ID_temp; //这种写法是错的,两个数组之间不可以这样赋值/*正解:调用C库函数substr函数*/ substr(Car_Structure[Locate].Car_Type , RxBuffer1 ,0 ,4); substr(Car_Structure[Locate].Car_ID , RxBuffer1 ,5 ,4);

好了,对第十二届省赛嵌入式设计与开发程序设计题目中处理字符串的思路到此结束!!!

    推荐阅读