文章目录

  • 一、任务要求
    • 1.1 概述
    • 1.2 串口收发
      • 1.2.1 串口输出内容
      • 1.2.2 串口接收内容
    • 1.3 说明
  • 二、实现思路
    • 2.1 指令判别
    • 2.1 车辆进入
    • 2.2 车辆驶出
    • 2.3 费率调整
  • 三、程序设计
    • 3.1 串口接收消息处理
    • 3.2 车辆驶入处理函数
    • 3.3 车辆驶出处理函数
    • 3.4 费率调整处理函数

题目原型是第十二届蓝桥杯嵌入式大学组省赛题目

本博客的工程会上传到个人资源,需要的小伙伴可以自行下载,仅供参考。

一、任务要求

1.1 概述

设计一个停车计费系统,使用串口获取车辆进/出停车场信息,并且能够输出计费信息。另外,也可以通过串口完成费率设置,调整功能。

1.2 串口收发

使用 4个任意ASCII 字符组成的字符串标识车辆,作为车辆编号。

1.2.1 串口输出内容

  • 当有车辆进入停车场时,串口输出停车类型,车辆编号和进入时间,举例如下
    停车类型:CNBR
    车辆编号:A392
    进入时间:2023.6.29-11:33:00
  • 当有车辆驶出停车场时,串口输出停车类型,车辆编号,推出时间和总计费用,举例如下
    停车类型:CNBR
    车辆编号:A392
    退出时间:2023.6.29-11:50:00
    停车时长:1小时(不满1小时,按1小时计算)
    总计费用:2元

1.2.2 串口接收内容

  • 调整费率
    上位机输入“CNBR(空格)Rate(空格)U”时,对应停车类型的费率升高0.5元/小时。
    上位机输入“CNBR(空格)Rate(空格)D”时,对应停车类型的费率下降0.5元/小时。
    每次调整成功后返回“OK!停车类型:当前费率”。比如CNBR降低0.5,串口会返回“OK!CNBR:0.5元/小时”。
  • 车辆进出
    有车辆进/出时,按照“停车类型:车辆编号:IN/OUT”的格式输入进/出车辆信息和停车类型。

1.3 说明

  • CNBR 类停车费率位 3.50元/小时,VNBR 类型停车费率位2.00元/小时。
  • 停车时长:整数,单位为小时,不足1小时,按 1小时统计。
  • 停车费用:以元为单位,按小时计费,保留小数点后2 位有效数字。
  • 系统收到入停车场信息后,不需要回复;接收到出停车场信息后,解析、计算并通过串口回复计费信息。
  • 当接收到的字符串格式不正确或存在逻辑错误,系统通过串口输出“Error!”。
  • 每一条串口指令都带有换行和回车。

二、实现思路

2.1 指令判别

这里总结一下上面提到的指令

  • 车辆进入
    “停车类型:车辆编号:IN”,停车类型两种,分别是CNBR和VNBR。车辆编号是任意的4个字符,IN表示进入。该指令加上换行和回车,总长度为14
  • 车辆驶出
    车辆驶出与进入的区别在于,车辆驶出指令最后为“OUT”。加上换行和回车,总长度为15
  • 费率调整
    按照规定,费率调整指令格式为“CNBR(空格)Rate(空格)U/D”,算上空格,换行和回车,总长度为13

经过分析发现,各个指令的长度是不同的,因此在做指令判别时,接收到的指令长度可以作为第一步筛选条件。对于小于和大于这三个长度的接收内容,按照要求,统一返回“Error”。当然,并不是说接收到的长度符合要求,接收到的指令就是有效的指令,后续还会有判别。

2.1 车辆进入

车辆进入需要获取的信息有三个,分别是停车类型,车辆编号和进入时间。首先在接收到消息后根据长度判断是否是车辆进入的消息长度,而且在接收数组中有“IN”。如果不是,不按照车辆进入处理。获取停车类型和车辆编号的方法是一位一位地从接收数组中提取,存储到一个二维数组中。二维数组的每一行代表一辆车。这里在有车辆进入时,会记录进入时刻的总秒数,方便后续总计费用的计算。

2.2 车辆驶出

有车辆驶出时,首先需要匹配车牌号,找到具体是哪辆车驶出。再根据车牌号索引匹配停车类型和驶入总秒数。然后获取驶出时的总秒数,计算总计费用。在车辆驶出时,不必再存储相关信息。车辆驶出后需要对之前存储的车辆信息重新整理,防止信息被覆盖。

2.3 费率调整

首先解析接收到的指令,判断费率增减。如果已经为0.5元/小时,无法继续降低。每次调整后,按照规定格式输出信息。

三、程序设计

首先需要配置好RTC和串口等外设,具体配置方法可以看博主的STM32速成系列,这里就不再做详细介绍了。

3.1 串口接收消息处理

上面说了,接收到指令后根据接收内容的长度和固定为的内容来确定是否为正确指令,不正确返回“Error!”。这里直接贴出程序,程序注释很详细,逻辑也比较简单,就不再赘述了。

extern u8 gCarInFlag; // 车辆进入标志位extern u8 gCarOutFlag; // 车辆驶出标志位extern u8 gRateAdjFlag; // 费率调整标志位extern u8 gInCarCunt; // 进入车辆数量void Uart_Rece_Pares(void) // 串口接收内容解析函数{// 分析接收内容长度// 有车辆进入if (gReceCount == 14 && gReceFifo[10] == 'I' && gReceFifo[11] == 'N'){gCarInFlag = 1; // 车辆进入标志位置1}// 有车辆驶出else if (gReceCount == 15 && gReceFifo[10] == 'O' && gReceFifo[11] == 'U' && gReceFifo[12] == 'T'){gCarOutFlag = 1; // 车辆驶出标志位置1}// 费率调整else if (gReceCount == 13 && gReceFifo[5] == 'R' && gReceFifo[6] == 'a' && gReceFifo[7] == 't' && gReceFifo[8] == 'e'){gRateAdjFlag = 1; // 费率调整标志位置1}// 非法指令else{printf ("Error!\r\n"); // 打印错误信息}}

3.2 车辆驶入处理函数

有车辆驶入时,需要获取停车类型,车辆编号和进入时间。根据格式要求,在接收到的数组中,前四位是停车类型,中间是车辆编号。车辆驶入时间直接从RTC获得即可。程序设计如下

u8 gCarNumber[8][4]; // 8行4列二维数组,存放车牌号u16 gCarInDate[8][6]; // 存储车辆驶入的年月日时分秒u32 gCarInSec[8]; // 存储车辆进入时的总秒数u8 gCarType[8][4]; // 存储停车类型extern u32 gReceCount; // 接收计数变量extern u8 gReceFifo[20]; // 接收数组u32 gClearCount = 0; // 清空接收数组计数变量extern _calendar calendar; // RTC结构体// 车辆进入处理函数void CarIn (void){u8 tempVar = 0; // 临时循环变量// 有车辆进入if (gCarInFlag == 1){gInCarCunt = gInCarCunt + 1; // 进入车辆数量加1// 解析停车类型for (tempVar = 0;tempVar < 4;tempVar ++){gCarType[gInCarCunt - 1][tempVar] = gReceFifo[tempVar];}// 提取车牌号for (tempVar = 5;tempVar < 9;tempVar ++){gCarNumber[gInCarCunt - 1][tempVar - 5] = gReceFifo[tempVar];}RTC_Get_CurDate(); // 获取当前年月日时分秒gCarInSec[gInCarCunt - 1] = RTC_GetCounter(); // 存储总秒数// 存储车辆进入年月日时分秒gCarInDate[gInCarCunt - 1][0] = calendar.w_year;gCarInDate[gInCarCunt - 1][1] = calendar.w_month;gCarInDate[gInCarCunt - 1][2] = calendar.w_date;gCarInDate[gInCarCunt - 1][3] = calendar.hour;gCarInDate[gInCarCunt - 1][4] = calendar.min;gCarInDate[gInCarCunt - 1][5] = calendar.sec;// 按照格式输出车辆进入信息printf ("停车类型:%c%c%c%c\r\n",gCarType[gInCarCunt - 1][0],gCarType[gInCarCunt - 1][1],gCarType[gInCarCunt - 1][2],gCarType[gInCarCunt - 1][3]);printf ("车辆编号:%c%c%c%c\r\n",gCarNumber[gInCarCunt - 1][0],gCarNumber[gInCarCunt - 1][1],gCarNumber[gInCarCunt - 1][2],gCarNumber[gInCarCunt - 1][3]);printf ("进入时间:%d.%d.%d-%d:%d:%d\r\n",gCarInDate[gInCarCunt - 1][0],gCarInDate[gInCarCunt - 1][1],gCarInDate[gInCarCunt - 1][2],gCarInDate[gInCarCunt - 1][3],gCarInDate[gInCarCunt - 1][4],gCarInDate[gInCarCunt - 1][5]);gCarInFlag = 0; // 清除车辆进入标志位Clear_Rece(); // 清空接收数组}}

这里顺便记录一下车辆进入时的总秒数,方便后续总计费用的计算。

串口测试一下效果

3.3 车辆驶出处理函数

// 车辆驶出处理函数void CarOut (void){u8 tempVar = 0; // 临时循环变量u8 tempVar1 = 0; // 临时循环变量u8 outCarNum = 0; // 存储是第几辆车驶出u32 time = 0; // 存储停车总秒数u32 hourCunt = 0; // 小时数// 有车辆驶出if (gCarOutFlag == 1){// 匹配车牌号for (tempVar = 0;tempVar < 8;tempVar ++){for (tempVar1 = 5;tempVar1 < 9;tempVar1 ++){if (gReceFifo[tempVar1] == gCarNumber[tempVar][tempVar1 - 5]){outCarNum = tempVar;}}}RTC_Get_CurDate(); // 获取当前年月日时分秒// 存储车辆驶出年月日时分秒gCarOutDate[0] = calendar.w_year;gCarOutDate[1] = calendar.w_month;gCarOutDate[2] = calendar.w_date;gCarOutDate[3] = calendar.hour;gCarOutDate[4] = calendar.min;gCarOutDate[5] = calendar.sec;// 计算停车时间// 计算方法是用驶出时间总秒数减去进入时间总秒数gCarOutSec = RTC_GetCounter(); // 获取车辆驶出时总秒数time = gCarOutSec - gCarInSec[outCarNum]; // 计算停车时间// 如果不足一小时,按照一小时计算while (time >= 3600){time = time - 3600;hourCunt = hourCunt + 1;}if (time != 0){hourCunt = hourCunt + 1;}// 根据停车类型计算总计费用if (gCarType[outCarNum][0] == 'C'){gTotalCost = (float)hourCunt * gCnbrRate;}else{gTotalCost = (float)hourCunt * gVnbrRate;}// 按照格式输出车辆驶出信息printf ("停车类型:%c%c%c%c\r\n",gCarType[outCarNum][0],gCarType[outCarNum][1],gCarType[outCarNum][2],gCarType[outCarNum][3]);printf ("车辆编号:%c%c%c%c\r\n",gCarNumber[outCarNum][0],gCarNumber[outCarNum][1],gCarNumber[outCarNum][2],gCarNumber[outCarNum][3]);printf ("驶出时间:%d.%d.%d-%d:%d:%d\r\n",gCarOutDate[0],gCarOutDate[1],gCarOutDate[2],gCarOutDate[3],gCarOutDate[4],gCarOutDate[5]);printf ("停车时长:%d小时\r\n",hourCunt);printf ("总计费用:%.2f\r\n",gTotalCost);// 重新整理车辆信息// 如果驶出的车辆是最后一辆,那么不需要再重新整理车辆信息// 如果不是最后一辆,将存储的最后一条车辆信息填充到驶出车辆位置if (outCarNum != gInCarCunt - 1){// 重新整理车牌号for (tempVar = 0;tempVar < 4;tempVar ++){gCarNumber[outCarNum][tempVar] = gCarNumber[gInCarCunt - 1][tempVar];}// 重新整理驶入时间for (tempVar = 0;tempVar < 6;tempVar ++){gCarInDate[outCarNum][tempVar] = gCarInDate[gInCarCunt - 1][tempVar];}// 重新整理驶入时的总秒数gCarInSec[outCarNum] = gCarInSec[outCarNum];}gInCarCunt = gInCarCunt - 1; // 车辆数量减1gCarOutFlag = 0; // 清除车辆驶出标志位Clear_Rece(); // 清空接收数组}}

个人认为其中有两个地方值得注意一下。一个是计算总计费用的方法。并没有直接用除法计算,减少了运算时间。另一个是在每次车辆驶出后,需要先重新整理之前存储的车辆信息,然后再将总车辆数减1。否则,当在有车辆进入时,会覆盖之前存储的最后一条车辆信息。串口测试结果如下

3.4 费率调整处理函数

// 费率调整处理函数void RateAdjust (void){// 收到费率调整指令if (gRateAdjFlag == 1){// 判断是CNBR还是VNBRif (gReceFifo[0] == 'C'){// 判断是上调还是下调if (gReceFifo[10] == 'U'){gCnbrRate = gCnbrRate + 0.5;}else if (gReceFifo[10] == 'D'){// 判断是否可减if (gCnbrRate > 0.5){gCnbrRate = gCnbrRate - 0.5;}}printf ("OK!CNBR:%.1f元/小时\r\n",gCnbrRate); // 输出当前费率}if (gReceFifo[0] == 'V'){// 判断是上调还是下调if (gReceFifo[10] == 'U'){gVnbrRate = gVnbrRate + 0.5;}else if (gReceFifo[10] == 'D'){// 判断是否可减if (gVnbrRate > 0.5){gVnbrRate = gVnbrRate - 0.5;}}printf ("OK!VNBR:%.1f元/小时\r\n",gVnbrRate); // 输出当前费率}gRateAdjFlag = 0; // 清除费率调整标志位Clear_Rece(); // 清空接收数组}}

串口测试结果如下