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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本文详细介绍了一个停车计费系统的任务要求,实现思路。最后,给出了详细的程序设计和测试结果。


🎀 文章作者:二土电子
🐸 期待大家一起学习交流!


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

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

一、任务要求

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();   // 清空接收数组
    }
}

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

串口测试一下效果

c273822648656ba309c23fb9ddc7a03f_681d205b2d0640ea9e873ffc687ae679.png

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。否则,当在有车辆进入时,会覆盖之前存储的最后一条车辆信息。串口测试结果如下

8fe3b40495c0b483d7370c85290dbe81_ead8eb2f94f146118d1d3209f8a1843e.png

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();   // 清空接收数组
    }
}

串口测试结果如下

b68fafe8e84875c5dfb4f02a0d01e457_8c9d229ea5c446b6b11e9aeaff8a86d1.png

相关文章
|
2月前
|
传感器 监控 物联网
基于STM32+微波雷达设计的非接触式睡眠监控系统
本项目开发一种非接触式的睡眠监控系统,该系统利用先进的60GHz毫米波雷达技术和STM32微控制器,实现了对人体在睡眠过程中的存在感知、运动感知以及生理指标如呼吸频率、心率的实时监测。系统能够自动评估睡眠质量,并在用户睡眠周期结束时提供睡眠评分。为了确保用户能够在任何地点了解自己的睡眠状况,系统集成了Wi-Fi模块,可以将收集到的数据上传至华为云物联网平台,并通过专门设计的移动应用程序供用户远程访问。此外,系统还具备超阈值报警功能,当检测到异常的生理指标时会发出警报提醒。本地1.44寸TFT LCD显示屏用于实时显示监测到的信息,包括生理指标和环境数据。为了全面监测用户的健康状况,系统还加入了
289 0
基于STM32+微波雷达设计的非接触式睡眠监控系统
|
2月前
|
存储 机器学习/深度学习 编解码
基于STM32的车牌识别系统
基于STM32的车牌识别系统
104 0
|
2月前
|
传感器 网络协议 物联网
基于STM32的环境监测系统 (esp8267)(下)
基于STM32的环境监测系统 (esp8267)(下)
108 0
|
2月前
|
传感器 测试技术 芯片
基于STM32的环境监测系统 (esp8266)(上)
基于STM32的环境监测系统 (esp8266)(上)
351 0
|
3月前
|
存储 传感器 Linux
STM32微控制器为何不适合运行Linux系统的分析
总的来说,虽然技术上可能存在某些特殊情况下将Linux移植到高端STM32微控制器上的可能性,但从资源、性能、成本和应用场景等多个方面考虑,STM32微控制器不适合运行Linux系统。对于需要运行Linux的应用,更适合选择ARM Cortex-A系列处理器的开发平台。
276 0
|
7月前
|
内存技术 存储 Android开发
MCU最小系统电路设计(以STM32F103C8T6为例)-3
MCU最小系统电路设计(以STM32F103C8T6为例)
MCU最小系统电路设计(以STM32F103C8T6为例)-3
|
7月前
|
芯片
MCU最小系统电路设计(以STM32F103C8T6为例)-1
MCU最小系统电路设计(以STM32F103C8T6为例)
MCU最小系统电路设计(以STM32F103C8T6为例)-1
|
7月前
|
传感器
基于STM32与FreeRTOS的四轴机械臂项目-3
基于STM32与FreeRTOS的四轴机械臂项目
基于STM32与FreeRTOS的四轴机械臂项目-3
|
5月前
|
前端开发 安全
stm32f407探索者开发板(十一)——SystemInit时钟系统初始化剖析
stm32f407探索者开发板(十一)——SystemInit时钟系统初始化剖析
184 0
|
5月前
stm32f407探索者开发板(十)——时钟系统精讲
stm32f407探索者开发板(十)——时钟系统精讲
203 0