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

文章目录

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

串口测试结果如下

费率调整测试

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【服务器数据恢复】raid5故障导致LUN无法访问的数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器中有一组由数块SAS硬盘组建的RAID5阵列&#xff0c;阵列中有1块热备盘&#xff0c;上层部署OA以及Oracle数据库。 服务器故障&#xff1a; 该磁盘阵列中有2块硬盘出现故障先后离线&#xff0c;RAID5阵列瘫痪&#xff0c;上层LUN无法…

【解决openGauss安装后yum、ssh命令无法使用】

【解决openGauss安装后yum、ssh命令无法使用】 &#x1f53b; 一、操作系统及数据库版本&#x1f530; 1.1 操作系统版本&#x1f530; 1.2 openGauss数据库版本 &#x1f53b; 二、关于openGauss安装&#x1f53b; 三、问题详情&#x1f530; 3.1 使用yum命令报错&#x1f530…

【数据库原理与实践】知识点归纳(下)

第6章 规范化理论 一、关系模式设计中存在的问题 关系、关系模式、关系数据库、关系数据库的模式 关系模式看作三元组&#xff1a;R < U,F >&#xff0c;当且仅当U上的一个关系r满足F时&#xff0c;r称为关系模式R < U,F >的一个关系 第一范式&#xff08;1NF&…

python最佳开发环境组合(pycharm+anaconda)

一、pycharmanaconda是python 最佳开发环境组合 1.pycharm与vscode对比 pycharm社区版与pycharm pro pycharm pro 与vscode 二、anaconda Anaconda Python 集成包 工具箱。 所以没有必要下载传统Python (cPython)个人十分不推荐使用传统python做科学计算&#xff0c; 一来…

SpringBoot-集成FTP(上传、下载、删除)

目录 一、引入依赖 二、配置文件 三、Controller层 四、Service层 五、相关工具类 由于服务在内网部署&#xff0c;需要使用ftp服务器管理文件&#xff0c;总结如下 一、引入依赖 <!-- https://mvnrepository.com/artifact/commons-net/commons-net --> <depen…

Web3在HTML中获取 MetaMask 启用的用户列表

当然 我们还是要先启动ganache环境 然后 通过MetaMask 导入一些用户 然后 我们需要在页面中引入 web3.min.js 如果您还没有这个文件 可以查看我的文章web3.js获取导入 然后我访问官网 https://learnblockchain.cn/docs/web3.js/web3-eth.html#getchainid 打开后 先来到 web3.…

apple pencil二代建议买吗?性价比高的触控笔测评

因为ipad的强大功能&#xff0c;不少人已经开始使用ipad了&#xff0c;随之也越来越普及。大屏幕上的学习效果很好&#xff0c;但用来刷剧以及打游戏就没什么意思了。如果你不想买一支价格很贵的苹果电容笔&#xff0c;或是只想用来做笔记&#xff0c;你可以考虑一下平替电容笔…

机器学习——无监督学习

聚类 问题描述 训练数据&#xff1a; D { x 1 , x 2 , ⋯ , x m } D\lbrace x_1,x_2,\cdots,x_m\rbrace D{x1​,x2​,⋯,xm​}&#xff0c;其中每个数据为 n n n 维向量 x i ( x i 1 , x i 2 , ⋯ , x i n ) x_i(x_{i1},x_{i2},\cdots,x_{in}) xi​(xi1​,xi2​,⋯,xin​…

测试背了4年“锅“,测试缺陷总结整理(细致)“锅“终丢掉了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 缺陷分析也是测试…

MySQL——变量与游标

今天我们来一起学习MySQL中的变量&#xff08;系统变量与用户变量&#xff09;&#xff0c;以及什么是游标&#xff0c;游标如何使用&#xff1f; 1. 变量 在 MySQL 数据库的存储过程和函数中&#xff0c;可以使用变量来存储查询或计算的中间结果数据&#xff0c;或者输出最终…

RabbitMQ快速上手(延迟队列)

安装 官网 参考文章&#xff1a; ​ https://blog.csdn.net/miaoye520/article/details/123207661 ​ https://blog.csdn.net/lvoelife/article/details/126658695 安装Erlang&#xff0c;并添加环境变量ERLANG_HOME&#xff0c;命令行运行erl 安装rabbitmq&#xff0c;rab…

时间序列预测的20个基本概念总结

1、时间序列 时间序列是一组按时间顺序排列的数据点 比如&#xff1a; 每小时的气压每年的医院急诊按分钟计算的股票价格 2、时间序列的组成部分 时间序列数据有三个主要组成部分。 趋势季节性残差或白噪声 3、趋势 在时间序列中记录的长期缓慢变化/方向。 4、季节性 …