文章目录
前言
十二届难点是如何存入车辆信息,检查是否符合格式和逻辑,并且出停车场如何清除之前存入的信息并且计算费用。
这里存入信息我通过不同的车辆类型进行划分了两个二维数组,串口接收后先进行格式检查,格式正确之后判断车牌号是否相同,不同将车辆信息存入进数组中,相同就检查逻辑无误后输出车辆收费。由于将数组其中某一行清零,所以在输出收费后,我将其后续数组都往前移一位以避免车辆信息无法继续存入。
写程序主要问题:二维数组指针的使用不熟悉导致我耗费大量时间。
另外提一嘴,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];
}
}
}