STM32实战项目—停车计费系统


🎀 文章作者:二土电子

🌸 关注文末公众号获取其他资料和工程文件!

🐸 期待大家一起学习交流!


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

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

一、任务要求

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;   // 车辆数量减1
			
		gCarOutFlag = 0;   // 清除车辆驶出标志位
		Clear_Rece();   // 清空接收数组
	}
}

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

车辆驶出测试

3.4 费率调整处理函数

// 费率调整处理函数
void RateAdjust (void)
{
	// 收到费率调整指令
	if (gRateAdjFlag == 1)
	{
		// 判断是CNBR还是VNBR
		if (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();   // 清空接收数组
	}
}

串口测试结果如下

费率调整测试

  • 8
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二土电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值