蓝桥杯嵌入式学习笔记----第十二届停车场收费

本文详细描述了一个基于STM32的项目,涉及串口DMA接收、车辆信息的格式验证、逻辑检查、停车费计算以及二维数组操作。作者分享了如何处理界面转换、按键输入和串口数据解析,以及遇到的技术挑战和解决方案。
摘要由CSDN通过智能技术生成

文章目录


前言

十二届难点是如何存入车辆信息,检查是否符合格式和逻辑,并且出停车场如何清除之前存入的信息并且计算费用。

这里存入信息我通过不同的车辆类型进行划分了两个二维数组,串口接收后先进行格式检查,格式正确之后判断车牌号是否相同,不同将车辆信息存入进数组中,相同就检查逻辑无误后输出车辆收费。由于将数组其中某一行清零,所以在输出收费后,我将其后续数组都往前移一位以避免车辆信息无法继续存入。

写程序主要问题:二维数组指针的使用不熟悉导致我耗费大量时间。

另外提一嘴,ChatGPT永远的神。


一、界面转换

 界面转换我习惯使用枚举变量定义界面,在按键中写入Interface = (enum Interface_m)((Interface + 1) % 2);( 2表示两个界面)。就可以实现界面转换。

enum Interface_m{
	Data = 0,
	Para = 1
};
enum Interface_m Interface = Data;

static void Data_Interface()
{
	uint8_t display[20];
	sprintf((char *)display,"       Data");
	LCD_DisplayStringLine(Line2, display);
	
	sprintf((char *)display,"   CNBR:%d",CarData.Cnbr);
	LCD_DisplayStringLine(Line4, display);
	
	sprintf((char *)display,"   VNBR:%d",CarData.Vnbr);
	LCD_DisplayStringLine(Line6, display);
	
	sprintf((char *)display,"   IDLE:%d",CarData.Ldle);
	LCD_DisplayStringLine(Line8, display);
}
static void Para_Interface()
{
	uint8_t display[20];
	sprintf((char *)display,"       Para");
	LCD_DisplayStringLine(Line2, display);
	
	sprintf((char *)display,"   CNBR:%.2f",CarData.CnbrCost);
	LCD_DisplayStringLine(Line4, display);
	
	sprintf((char *)display,"   VNBR:%.2f",CarData.VnbrCost);
	LCD_DisplayStringLine(Line6, display);
}
static void LCD_Prosess()
{
	if(Interface == Data) Data_Interface();
	else if(Interface == Para) Para_Interface();
	
}

二、按键

按键我是将其放到循环中,并且在定时器中断中设定标志位,隔20ms读取一次。 

代码如下:

typedef enum 
{
	False,
	True
}Bool;
Bool KeyFlg;
void ReadKey()
{
	static uint8_t b1Flg,b2Flg,b3Flg,b4Flg;
	if(KeyFlg)
	{
		KeyFlg = False;
		if(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == 0 && b1Flg == 1)
		{
			b1Flg = 0;
			Interface = (enum Interface_m)((Interface + 1) % 2);
			LCD_ClearLine(Line4);LCD_ClearLine(Line6);LCD_ClearLine(Line8);
		}
		
		if(Interface == Para)
		{
			if(HAL_GPIO_ReadPin(B2_GPIO_Port,B2_Pin) == 0 && b2Flg == 1)
			{
				b2Flg = 0;
				CarData.CnbrCost += 0.5f;CarData.VnbrCost += 0.5f;
			}
			
			if(HAL_GPIO_ReadPin(B3_GPIO_Port,B3_Pin) == 0 && b3Flg == 1)
			{
				b3Flg = 0;
				CarData.CnbrCost += 0.5f;CarData.VnbrCost += 0.5f;
				if(CarData.CnbrCost <= 0.0f) CarData.CnbrCost = 0.0f;
				if(CarData.VnbrCost <= 0.0f) CarData.VnbrCost = 0.0f;
			}
			
		}
		
		
		if(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin) == 0 && b4Flg == 1)
		{
			b4Flg = 0;
			PwmState_Flg = (Bool)((PwmState_Flg + 1) % 2);
			if(PwmState_Flg == True) 
			{
				htim17.Instance->CCR1 = 100;
				LD2Flg = True;
			}
			else if(PwmState_Flg == False) 
			{
				htim17.Instance->CCR1 = 0;
				LD2Flg = False;
			}
			HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);
		}
		
		if(HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == 1) b1Flg = 1;
		if(HAL_GPIO_ReadPin(B2_GPIO_Port,B2_Pin) == 1) b2Flg = 1;
		if(HAL_GPIO_ReadPin(B3_GPIO_Port,B3_Pin) == 1) b3Flg = 1;
		if(HAL_GPIO_ReadPin(B4_GPIO_Port,B4_Pin) == 1) b4Flg = 1;
	}
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim16)
	{
		KeyFlg = True;
	}
}

三、串口接收处理(难点)

1.串口DMA接收

这里我使用串口DMA循环接收,使用定长接收22个字符数据。主要接收的是字符而不是数字,进行数据比较时可以减去‘0’,或者&0x0f。并且由于日期是两位第一位还需要乘10.

代码如下:

HAL_UART_Receive_DMA(&huart1,RxData, sizeof(RxData));
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart == &huart1)
	{
			Usart_Flg = True;
	}
}

2.数据处理

void Usart_Chanlder()
{
	if(Usart_Flg)
	{
		Usart_Flg = False;
		if(CheckFormat(RxData) == True) //检查格式是否正确
		{
			if(RxData[0] == 'C') Check(CarData.CnbrNum,&CarData.Cnbr);
			else if(RxData[0] == 'V') Check(CarData.VnbrNum,&CarData.Vnbr);
			CarData.Ldle = 8 - CarData.Cnbr - CarData.Vnbr;
			
		}
		else 
			HAL_UART_Transmit(&huart1,Error,6,100);
	}
	
}

 3.格式检查

Bool CheckFormat(const uint8_t *data)
{
	uint8_t i;
	int8_t num;
    // 检查长度是否为22
    if (strlen((char*)data) != 22)
        return False;
		
		if(data[1] != 'N' || data[2] != 'B'|| data[3] != 'R')
				return False;
		if(data[5] < 'A' || data[5] > 'Z')
				return False;
		
    // 检查每个字符是否为数字并且在预期范围内
    for (i = 10; i < 22; i++)
    {
				// 在年份、月份、日期、小时和分钟位置上应该是数字
				if (data[i] < '0' || data[i] > '9')
						return False;
        
        else if (i == 13 || i == 15)
        {
            // 日期和月份应该在合理范围内
            num = (data[i - 1] - '0') * 10 + (data[i] - '0');
            if (num < 1 || num > (i == 13 ? 12 : 31))
                return False;
        }
        else if (i == 17 || i == 19)
        {
            // 小时和分钟应该在合理范围内
            num = (data[i - 1] - '0') * 10 + (data[i] - '0');
            if (num < 0 || num > (i == 17 ? 23 : 59))
                return False;
        }
        else if (i == 22)
        {    // 秒应该在合理范围内
             num = (data[i - 1] - '0') * 10 + (data[i] - '0');
            if (num < 0 || num > 59)
                return False;
            
        }
    }

    // 所有检查通过,返回真
    return True;
}

4.车辆信息存入与输出

形参中使用uint8_t arry[][22] 将二维数组首地址传入,而uint8_t (*arry)[22](逻辑检查函数形参)是将某一行的首地址传入,相当于一个一维数组。使用指针的好处是不需要在函数调用时将数值复制进去,避免浪费CPU处理时间和栈内存。

/* 
  处理数组,匹配就清零,不匹配就将RxData存入二维数组
	传入整个数组和表示二维数组的哪一行的CarData.Cnbr
	CarData.Cnbr同时表示Cnbr这一类下有几辆车
*/

void Check(uint8_t arry[][22],int16_t *index)//行数改变
{
	int16_t exist;uint8_t i,y,stopFlg = 0;
	if(*index == 0)//第一次接收
	{
		memcpy(arry[*index],RxData, 22);//自带库将RxData内容复制进数组中
		(*index) = 1;
	}
	else
	{
		for(y = 0;(y < (*index)) && (stopFlg == 0);y++)//行数循环
		{
			for(i = 5;i <= 8;i++)//列数循环
			{
				if(RxData[i] != arry[y][i])//不匹配
				{
					exist = 1;
					break;
				}
				else if(RxData[i] == arry[y][i])//匹配
				{
					exist = -1;
				}
				if(i == 8 && exist == -1) //发现匹配的车牌
				{
					stopFlg = 1;//跳出外层循环
					break;//跳出内层循环
				}
			}

		}
		if(exist > 0 && (CarData.Ldle == 0)) exist = 0;//车位已满
		if(exist > 0 && y == (*index) && (CarData.Ldle != 0)) //有新的车辆进入并且车位不满
		{
			memcpy(arry[*index],RxData, 22);//复制
			memset(RxData, 0, 22);//清空RxData
		}
		else if(exist < 0)//有相同车牌,出停车场
		{
			if(CheckLogic(&arry[y]))//检查是否有逻辑错误
			{
				CalculateFee(&arry[y]);//输出计费信息
				memset(arry[y], 0, 22);//清空出停车场车辆信息
				ShiftArry(arry,(*index),y);//清空后将后续数组的信息往前挪一位
				
			}
			else //有逻辑错误
			{
				memset(RxData, 0, 22);//清空RxData
				HAL_UART_Transmit(&huart1,Error,6,100);//发送Error
				exist = 0;
			}
		}
		stopFlg = 0;
		(*index) = (*index) + exist;
//		exist = 0;
	}
	
	
	
}

 5.逻辑检查

Bool CheckLogic(uint8_t (*arry)[22])
{
	uint8_t i,num[2];
	uint8_t bigCount = 0;//BigCount作为局部变量初值不确定,要赋初值
	for (i = 11; i < 22; i+=2)
	{
		num[0] = ((*arry)[i - 1] - '0') * 10 + ((*arry)[i] - '0');
		num[1] = (RxData[i - 1] - '0') * 10 + (RxData[i] - '0');
		if(num[0] <= num[1]) bigCount++;//一共六个时间
                           //不符合逻辑就是有一个时间大于之前存入的时间
		
	}
	if(bigCount < 6)//小于6,证明至少有一个时间小于进停车场时间
	{
		bigCount = 0;
		return False;
	}		
	else //符合逻辑
	{
		bigCount = 0;return True;
	}
}

6.停车费计算

/*
  得到时间的差值
	newTime:出停车场时间 oldTime:进停车场时间 highTime:前一个时间点 range;该时间的范围
	返回值:进出停车场时间的差值
*/
uint8_t GetDiffValue(uint8_t newTime,uint8_t oldTime,uint8_t highTime,uint8_t range)
{
	uint8_t diffValue;
	if(highTime == 0) diffValue = newTime - oldTime;
	else diffValue = range - oldTime + newTime;
	return diffValue;
}
/* 
  计算停车费并且发送出去
*/
void CalculateFee(uint8_t (*arry)[22])
{
	uint8_t usartOutPut[18];
	uint8_t year,month,day,hour,minute,sec;
	float fee;
	
	year   = GetDiffValue(((RxData[10]  & 0x0f) * 10 + (RxData[11] & 0x0f)),(((*arry)[10] & 0x0f) * 10 + ((*arry)[11] & 0x0f)),0,10);
	month  = GetDiffValue(((RxData[12]  & 0x0f) * 10 + (RxData[13] & 0x0f)),(((*arry)[12] & 0x0f) * 10 + ((*arry)[13] & 0x0f)),year,12);
	day    = GetDiffValue(((RxData[14]  & 0x0f) * 10 + (RxData[15] & 0x0f)),(((*arry)[14] & 0x0f) * 10 + ((*arry)[15] & 0x0f)),month,30);
	hour   = GetDiffValue(((RxData[16]  & 0x0f) * 10 + (RxData[17] & 0x0f)),(((*arry)[16] & 0x0f) * 10 + ((*arry)[17] & 0x0f)),day,24);
	minute = GetDiffValue(((RxData[18]  & 0x0f) * 10 + (RxData[19] & 0x0f)),(((*arry)[18] & 0x0f) * 10 + ((*arry)[19] & 0x0f)),hour,60);
	sec    = GetDiffValue(((RxData[20]  & 0x0f) * 10 + (RxData[21] & 0x0f)),(((*arry)[20] & 0x0f) * 10 + ((*arry)[21] & 0x0f)),minute,60);
	
	if(minute > 0) hour += 1;//不足一个小时按一个小时计算
	else if(minute == 0 && sec > 0) hour += 1;
	if(RxData[0] == 'C') fee = CarData.CnbrCost * hour;//计算不同车型停车费
	else if(RxData[0] == 'V') fee = CarData.VnbrCost * hour;
	sprintf((char *)usartOutPut,"%cNBR:%c%c%c%c:%d:%.2f",RxData[0],RxData[5],RxData[6],RxData[7],RxData[8],hour,fee);
	HAL_UART_Transmit(&huart1,usartOutPut,18,100);
}

 7.数组移位

/* 移位函数,将匹配的一行后的数值往前移一行*/
void ShiftArry(uint8_t arry[][22],uint8_t index,uint8_t insert)
{
	uint8_t i,y;
	for(y = insert;y < index;y++)
	{
		for(i = 0;i < 22;i++)
		{
			arry[y][i] = arry[y+1][i];
		}
	}
}

总结

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值