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

news2024/11/18 11:42:48

文章目录

  • 一、任务要求
    • 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;   // 车辆数量减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();   // 清空接收数组
	}
}

串口测试结果如下

费率调整测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/700060.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4-Python如何创建等比数列?【视频版】

目录 问题视频解答 问题 视频解答 点击观看&#xff1a; 4-如何创建等比数列&#xff1f;

windows无法启动RemoteDesktopServices服务(位于本地计算机上)。错误126:找不到指定的模块。

win10的搜索栏输入 注册表编辑器。打开&#xff0c;找到如下路径 计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TermService\Parameters 将指定数值项ServiceDll的数值数据改成默认值&#xff1a; %SystemRoot%\System32\termsrv.dll 再重新尝试就好了。 …

Java(七):项目部署

项目部署 运行容器解决Centos8中yum命令遇到的问题打包项目拷贝.jar到容器中安装jdk后台运行.jar后台运行.jar并输入日志实时查看日志查看/杀死运行程序目录结构日志配置 运行容器 $ docker run -d -p 8001:8001 -p 8081:8081 -p 8082:8082 --namelocal_centos --privilegedtr…

DigiCert SSL证书有什么优势?

DigiCert是全球领先的SSL证书颁发机构&#xff0c;2017年收购赛门铁克数字证书业务后&#xff0c;成为全球市场占有率领先的SSL证书提供商&#xff0c;提供高保证TLS/SSL证书和自动化解决方案&#xff0c;也是沃通CA在全球信任数字证书业务方面的重要合作伙伴。 与全球其他品牌…

AI聊天对话工具,让沟通更简单轻松

人工智能技术的发展不断为我们带来新的惊喜和变革&#xff0c;其中之一就是ai聊天对话应用。这种应用利用自然语言处理、机器学习和对话管理等技术&#xff0c;在智能手机、电脑等设备上实现了人机对话&#xff0c;让人们更轻松地与计算机之间进行交流和互动。随着移动互联网的…

基于SpringBoot的电脑商城项目

基于SpringBoot的电脑商城项目 — 参考B站袁庭新老师项目 Maven多聚合项目 技术栈&#xff1a; Spring boot、MyBtis、Bootstrap、Layui&#xff0c;Redis&#xff0c;内网穿透等 功能&#xff1a; 后台&#xff1a;管理员登录、商品信息管理、订单管理、用户信息管理、统计图…

springbootbatis

1、maven <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.…

在 Maya、ZBrush、Substance 3D 和 UE5 中创建理发椅

今天瑞云渲染小编给大家带来Kevin J. Coulman 分享的理发椅项目背后的工作流程&#xff0c;详细介绍了如何在 Maya 和 ZBrush 中为道具建模&#xff0c;分享了制作准确材质的技巧&#xff0c;并解释了为什么选择 UE5 进行渲染。 介绍 大家好! 我的名字是Mehdi Benmansour&…

如何搭建和使用minio?保姆级教程

目录 前言搜索镜像找到bitnami/minio镜像拉取镜像查看下载好的镜像创建数据卷目录&#xff0c;并提升权限根据镜像创建一个minio容器参数说明 查看 minio 容器的启动日志查看 minio 容器的信息开放安全组端口访问minio进入首页创建桶设置权限上传文件 前言 如果公司想要自己搭…

ASEMI代理艾赛斯MOS管IXFH4N100Q的性能与应用

编辑-Z 在电子元件领域&#xff0c;MOS管是一种重要的半导体器件&#xff0c;它在电子设备中起着至关重要的作用。今天&#xff0c;我们将重点介绍一款特别的MOS管——IXFH4N100Q&#xff0c;探讨其性能特点和应用领域。 首先&#xff0c;让我们了解一下什么是MOS管。MOS管&am…

Linux--进入一个路径:cd

Linux系统中&#xff0c;磁盘上的文件和目录被组成一棵目录树&#xff0c;每个节点都是目录或文件 cd是change directory的简写 语法&#xff1a; cd 目录名 功能&#xff1a; 改变工作目录。将当前工作目录改变到指定的目录下。 举例&#xff1a; cd .. : 返回上级目录&…

BFS (Java) 广度优先搜索 简单介绍、模板、案例(一)

一. BFS的简单介绍 深度优先搜索DFS和广度优先搜索BFS是经常使用的搜索算法&#xff0c;在各类题目中都有广泛的应用。 深度优先搜索算法&#xff08;英语&#xff1a;Depth-First-Search&#xff0c;DFS&#xff09;是一种用于遍历或搜索树或图的算法。其过程简要来说是对每一…

学习c++第01天

学习c的第01天 前言1、变量是声明&#xff1f;2.建议定义数据都对其进行初始化3.有符号数和无符号数4.进制间的相互转换5.原反补码6.const 、register 、volatile和typedef关键字7.数据类型的自动转换8.左移<< &右移操作>>9.将data的指定位数进行0、1转化的应用…

javaee 使用监听器统计当前在线用户列表

ServletContextListener 和 HttpSessionBindingListener 需要配和使用 TestServletContextListener package com.yyy.listener;import java.util.ArrayList; import java.util.List;import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import …

复现论文ChineseBERT(ONTONOTES数据集)

记录一下自己复现论文《ChineseBERT: Chinese Pretraining Enhanced by Glyph and Pinyin Information》的过程&#xff0c;最近感觉老在调包&#xff0c;一天下来感觉什么也没干&#xff0c;就直播记录一下跑模型的过程吧 事前说明&#xff0c;这是跑项目的实况&#xff0c;如…

实用类详解

第二章 实用类介绍 目录 第二章 实用类介绍 1.枚举 2.包装类及其构造方法 3.Math类 4.Random类 5.String类 总结 内容仅供学习交流&#xff0c;如有问题请留言或私信&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 有空您就点点赞 1.枚举 枚举指由一…

python-注册nacos服务

一、首页 Nacos&#xff08;Naming and Configuration Service&#xff09;是一个用于实现服务注册和发现的开源项目。Nacos注册服务的主要作用是帮助微服务架构中的各个服务实例进行注册和发现&#xff0c;以便于服务之间的通信和协作&#xff0c;另外&#xff0c;也可以在nac…

基于高性能的STM32G031K4T6、STM32G031K6T6、STM32G031K8T6(ARM微控制器)64MHz 闪存 32-LQFP

STM32G0 32位微控制器 (MCU) 适合用于消费、工业和家电领域的应用&#xff0c;并可随时用于物联网 (IoT) 解决方案。这些微控制器具有很高的集成度&#xff0c;基于高性能ARM Cortex-M0 32位RISC内核&#xff0c;工作频率高达64MHz。该器件包含内存保护单元 (MPU)、高速嵌入式内…

大数据赋能交通业务管理——远眺智慧交通集成管控系统

随着交通管理需求的不断提升&#xff0c;原有系统管理模式的缺点逐渐显露&#xff0c;各业务系统的相互独立、各自为战&#xff0c;成为交通管理人员全局把控交通资源、实现交通综合管控的壁垒。 智慧交通集成管控平台通过统一标准&#xff0c;集成交警各类业务系统、整合相关数…

libevent(6)windows上使用iocp网络模型

windows操作系统上不能使用epoll模型&#xff0c;只能使用iocp网络模型。这里我把怎么在windows上使用iocp的代码直接贴上&#xff1a; #include <iostream> #include <signal.h> #include <event2/event.h> #include <event2/listener.h> #include &l…