> 文档中心 > STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)

STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)

文章目录

  • 前言
  • 一、CubeMX配置(第十二届模拟题完整版)
  • 二、代码相关定义、声明
    • 1.函数声明
    • 2.宏定义
    • 3.变量定义
  • 三、主要函数
    • 1.按键扫描
    • 2.串口接收中断、定时器中断(接收)
    • 3.数据解析
    • 4.判定数据正误
    • 5.数据更新
    • 6.结算
    • 7.Main函数
  • 四、实验结果
    • 1.数据长度有误
    • 2.数据不合法
    • 3.数据正常
  • 五、源码(转载请注明出处)
  • 总结

前言

相关说明:

开发板:CT117E-M4(STM32G431RB 蓝桥杯嵌入式比赛板)
开发环境: CubeMX+Keil5
涉及题目:第十二届蓝桥杯嵌入式省赛
题目难点:停车管理系统逻辑编写;数据接收,解析,判定,更新。
代码思路:(使用usart1时需要修改引脚为PA8 PA9 PA10)串口接收到数据后,先判定数据接收长度是否正确,即每接收到一个字节都重新开启定时器,最后一字节数据接收完且进入定时器中断后判断接收数据长度,准确无误则进行数据解析;解析时将数据分段保存:车类型,车牌号,时间;保存好后再对数据的合法性进行判定,车类型是否为规定类型之一,类型、车牌号数据长度是否为四位。时间是否合法(月份对应天数,时分秒对应进制);最后是存储数据的更新,车牌号是否已经存在?不存在的话判断是否还有空余车位?有则将类型、车牌号、时间等数据存储在数组中;存在的话考虑现在接收时间是否大于到达时间?时间合法则对存储在数组组中的数据进行计算和输出。


CubeMX配置、主要函数代码及说明:

一、CubeMX配置(第十二届模拟题完整版)

1.使能外部高速时钟:在这里插入图片描述

2.配置时钟树:在这里插入图片描述

3.GPIO:

在这里插入图片描述

4.TIM3(PWM):在这里插入图片描述在这里插入图片描述

5.TIM6(串口在接收到最后一字节数据5us后进入定时器中断函数):在这里插入图片描述

6.USART1:在这里插入图片描述

7.NVIC(中断配置):在这里插入图片描述

二、代码相关定义、声明

1.函数声明

main.cvoid Car_Change(char *type,char *carNum,time_t *time,char *str);//Car数组改变uint8_t Dat_Check(char *type,char *carNum);//判断接收数据正误(格式 时间)void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//定时器6中断 数据接收超时判定(避免一个合法数据分多次发送)void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//串口接收中断函数(每接收一字节中断一次)void Settle_Accounts(struct car outCar);//结账void Switch_RecBuff(char *type,char *carNum,time_t *time,char *timStr);//CNBR:A392:200202120000 数据解析(类型 车牌 时间)void LCD_Init_Show();//LCD初始化显示void LCD_Refresh(uint8_t page);//LCD更新显示void LED_Change();//LED状态改变gpio.hvoid KEY_Scan(void);//按键扫描void LED_AllClose(uint8_t *LCD_Close);//LED显示更新

2.宏定义

#define LED_GPIO_PORTGPIOC#define LED1_GPIO_PINGPIO_PIN_8#define LED2_GPIO_PINGPIO_PIN_9#define LED3_GPIO_PINGPIO_PIN_10#define LED4_GPIO_PINGPIO_PIN_11#define LED5_GPIO_PINGPIO_PIN_12#define LED6_GPIO_PINGPIO_PIN_13#define LED7_GPIO_PINGPIO_PIN_14#define LED8_GPIO_PINGPIO_PIN_15#define ON GPIO_PIN_RESET#define OFFGPIO_PIN_SET#define LED1(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED1_GPIO_PIN,a)#define LED2(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED2_GPIO_PIN,a)#define LED3(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED3_GPIO_PIN,a)#define LED4(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED4_GPIO_PIN,a)#define LED5(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED5_GPIO_PIN,a)#define LED6(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED6_GPIO_PIN,a)#define LED7(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED7_GPIO_PIN,a)#define LED8(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED8_GPIO_PIN,a)#define KEY1_GPIO_PORT GPIOB#define KEY1_GPIO_PINGPIO_PIN_0#define KEY2_GPIO_PORT GPIOB#define KEY2_GPIO_PINGPIO_PIN_1#define KEY3_GPIO_PORT GPIOB#define KEY3_GPIO_PINGPIO_PIN_2#define KEY4_GPIO_PORT GPIOA#define KEY4_GPIO_PINGPIO_PIN_0

3.变量定义

main.cuint8_t CNBR=0;//CNBR类型车辆数uint8_t VNBR=0;//VNBR类型车辆数uint8_t IDLE=8;//空闲位置double CNBR_Price=3.5;//CNBR类型停车费用double VNBR_Price=2.0;//VNBR类型停车费用char str[30];//用于组合字符串uint8_t LED_Close[3]={1,0,1};//LED关闭数组uint8_t recDatBuff[3][20]={0,0,0,0};//数据接收数组(recDatBuff[0]存储停车类型,recDatBuff[1]存储车牌号,recDatBuff[2]存储时间,冒号存储在数组0,1行的最后一个位置)uint8_t recDex=0; //接收数组下标uint8_t recNum=0;//接收数组行号uint8_t recDat;//本次接收数据(一次一字节)uint32_t recLong=0;//数接收长度uint8_t firstByte=1;//接收到本次传输数据的第一个字节(用于判定传输超时)uint8_t switch_flag=0;//接收数据转换标志,完整接收一次数据后置1,随后进行转换、判断、存储int Error;//接收数据错误标志int recYear;//接收年int recMon;//接收月int recDay;//接收日int recHour;//接收时int recMin;//接收分int recSec;//接收秒struct car//车辆结构体{char num[10];//车牌号char type[10];//车辆类型int dftime;//时间差double EndPrice;//最终价格double type_price;//停车单价(元/小时)char reach_time[60];//到达时间字符串char leave_time[60];//离开时间字符串int reach;//到达时间时间戳int leave;//离开时间时间戳};struct car car[9];//车辆存储结构体数组uint8_t car_dex=0;//存储数组下标

三、主要函数

首先是按键按下对数据以及输出PWM的更改;更改PWM输出时,按键按下后,先判断LED_Close[2]存储的状态,为灭则使LED2亮,开启PWM,输出1KHz信号。为亮则使LED2灭,暂停PWM,输出持续的低电平。(PWM在MX的配置为1KHz的输出。)

1.按键扫描

gpio.cvoid Data_Change(uint8_t mode)//数据改变{switch(mode){case ADD:CNBR_Price+=Price_step;VNBR_Price+=Price_step;break;case SUB:CNBR_Price-=Price_step;VNBR_Price-=Price_step;break;}}void Setting_Mode()//设置模式{uint8_t delay=0;while(1){if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)//change mode{HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET){while(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET);LCD_Refresh(1);break;}}else if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)//++{HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET){while(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET);Data_Change(ADD);LCD_Refresh(2);}}else if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET)//--{HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET){while(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET);Data_Change(SUB);LCD_Refresh(2);}}}}void KEY_Scan()//按键扫描{if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)//change mdoe{HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET){while(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET);LCD_Refresh(2);Setting_Mode();}}else if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)//PWM{HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET){while(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET);if(LED_Close[2]==1)//如果灭 即输出低电平{LED_Close[2]=0;HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);}else{LED_Close[2]=1;HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2);HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_RESET);}}}}

接下来是数据的处理,分别为接收,解析,判定和更新。

2.串口接收中断、定时器中断(接收)

串口在接收到一个字节数据时进入串口中断函数,每次进入串口中断函数都需要重新开启判定,在最后一字节数据接收完5us后进入定时器中断函数,在定时器中断函数中判定接收数据长度是否符合要求,不符合则返回Error,符合要求则按照设定好的规则进行保存。我使用的是二维数组,遇到冒号就换行,最后根据数据长度来设定接收结束的标志(接收结束后将开始解析)即可。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器6中断 数据接收长度判定{HAL_TIM_Base_Stop_IT(&htim6);//关闭判定HAL_UART_Receive_IT(&huart1,&recDat,sizeof(recDat));//重新开启串口接收中断if(recLong!=22){Error=1;//错误标志位置1}else{switch_flag=1;//字符串转换标志位置1}recLong=0;//接收长度重置recNum=0;//recNum重置recDex=0;//recDex重置}void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口接收中断函数(每接收一字节中断一次) 在最后一字节接收完5us后判断数据长度{HAL_TIM_Base_Stop_IT(&htim6);//关闭判定recLong++;recDatBuff[recNum][recDex++]=recDat;//将接收到的数据存储进数组if(recDat==':')//如果本次接收数据为冒号则数组换行,下标置为0{recNum++;recDex=0;} TIM6->CNT=0;HAL_TIM_Base_Start_IT(&htim6);//重新开启判定HAL_UART_Receive_IT(&huart1,&recDat,sizeof(recDat));//重新开启串口接收中断}

3.数据解析

将保存在二维数组中的数据进行字符串组合,数组中第0行数据为车的类型+冒号,第1行数据为车牌号+冒号,第2行数据为时间
这里用到time.h中的函数对时间进行转换,先将时间进行类型转换(字符型转整型),转换后根据mktime函数规则进行调整(年份减1900,月份减1),再将调整后的结果赋值给时间结构体,再调用mktime函数进行时间转换,转换后的时间为自1970年1月1日以来持续时间的秒数 (为什么用一个错误的时间测试,mktime不会返回-1,欢迎懂的大佬留言)

void Switch_RecBuff(char *type,char *carNum,time_t *time,char *timStr)//CNBR:A392:200202120000 数据解析 类型 车牌 时间{struct tm timeTem;//定义时间结构体sprintf(type,"%c%c%c%c",recDatBuff[0][0],recDatBuff[0][1],recDatBuff[0][2],recDatBuff[0][3]);//解析类型sprintf(carNum,"%c%c%c%c",recDatBuff[1][0],recDatBuff[1][1],recDatBuff[1][2],recDatBuff[1][3]);//解析车牌号sprintf(timStr,"%c%c%c%c%c%c%c%c%c%c%c%c",recDatBuff[2][0],recDatBuff[2][1],recDatBuff[2][2],recDatBuff[2][3],recDatBuff[2][4],recDatBuff[2][5],recDatBuff[2][6],recDatBuff[2][7],recDatBuff[2][8],recDatBuff[2][9],recDatBuff[2][10],recDatBuff[2][11]);//解析时间recYear=2000+(recDatBuff[2][0]-48)*10+(recDatBuff[2][1]-48)-1900;//将时间转换为int类型数据 如'0'要转换成0,字符0对应的ASCII码为48,则0为'0'-48 1~9以此类推recMon=(recDatBuff[2][2]-48)*10+(recDatBuff[2][3]-48)-1;//时间结构体存储规则,月份减1,年份减1900recDay=(recDatBuff[2][4]-48)*10+(recDatBuff[2][5]-48);recHour=(recDatBuff[2][6]-48)*10+(recDatBuff[2][7]-48);recMin=(recDatBuff[2][8]-48)*10+(recDatBuff[2][9]-48);recSec=(recDatBuff[2][10]-48)*10+(recDatBuff[2][11]-48);timeTem.tm_year=recYear;//一一对应赋值timeTem.tm_mon=recMon;timeTem.tm_mday=recDay;//刚开始这里成员选成了tm_yday,需要注意,排查了好久,yday是代表一年中的第几天,mday代表一月中的第几天timeTem.tm_hour=recHour;timeTem.tm_min=recMin;timeTem.tm_sec=recSec;*time=mktime(&timeTem);//将时间结构体用mktime函数转化为自1970年1月1日以来持续时间的秒数 (为什么用一个错误的时间测试,mktime不会返回-1,欢迎懂的大佬留言)}

4.判定数据正误

车类型是否为规定类型之一,类型、车牌号数据长度是否为四位(这里用冒号的位置进行判断)。时间是否合法(月份对应天数是否准确,,2月份还需考虑闰年;时分秒对应进制有误);

uint8_t Dat_Check(char *type,char *carNum)//判定接收数据正误 格式 时间{recMon+=1;//月份在上面为了转换减了1 这里需要加回来 recYear同理recYear-=100;if(strcmp(type,"CNBR")!=0 && strcmp(type,"VNBR")!=0)//如果类型不是其中之一(判断类型){return 1;}if(recDatBuff[0][4]!=':' || recDatBuff[1][4]!=':')//数组最后一个是否为:(判断格式){return 1;}if(recMon>12 || recMon<0)//判断时间合法性 下同 很好理解{return 1;}else if(recMon==2)//2月{if(recYear%4==0)//闰年{if(recDay>28 ||recDay<0){return 1;}}else//非闰年{if(recDay>29 ||recDay<0){return 1;}}}else if(recMon==1 || recMon==3 || recMon==5 || recMon==7 || recMon==8 || recMon==10 || recMon==12)//大月{if(recDay>31 ||recDay<0){return 1;}}else if(recMon==4 || recMon==6 || recMon==9 || recMon==11)//小月{if(recDay>30 ||recDay<0){return 1;}}if(recHour>23 || recHour<0)//时{return 1;}if(recMin>59 || recMin<0)//分{return 1;}if(recSec>59 || recSec<0)//秒{return 1;}return 0;//无误返回0}

5.数据更新

接收数据判定无误后进入数据更新步骤。首先判断车牌号是否已经存在,如不存在则为进入,进入时需要判断是否有空余车位,有则将接收数据保存在数组中,并更新车位信息;如果车牌号存在则为离开,离开需判断时间是否大于车辆到达时间,若合法则将离开车辆信息传递给结算函数进行结算并更新车位信息,不合法返回Error。

void Car_Change(char *type,char *carNum,time_t *time,char *str)//Car数组改变{uint8_t i;uint8_t dir=1;//方向标志位 1为进 0为出uint8_t outcar_dex;//离开车辆下标for(i=0;i<9;i++)//判断停车场是否存在该车{if(strcmp(carNum,car[i].num)==0)//如存在{dir=0;//方向为出outcar_dex=i;//记录下标if(*time<car[outcar_dex].reach)//与到达时间对比 判断时间是否合法{printf("Error\n");return;}break;}}if(dir==1)//in{if(IDLE==0)//无空闲车位{return;}if(strcmp(type,"CNBR")==0)//如果车辆类型为CNBR{CNBR++;IDLE--;car[car_dex].type_price=CNBR_Price;//存储价格}else if(strcmp(type,"VNBR")==0)//如果车辆类型为VNBR{VNBR++;IDLE--;car[car_dex].type_price=VNBR_Price;//存储价格}strcpy(car[car_dex].type,type);//存储车类型strcpy(car[car_dex].num,carNum);//存储车牌号strcpy(car[car_dex].reach_time,str);//存储车到达时间字符串car[car_dex].reach=*time;//存储车到达时间car_dex++;//下标++ 为储存下一辆车做准备}else//out{if(strcmp(type,"CNBR")==0){CNBR--;IDLE++;}else if(strcmp(type,"VNBR")==0){VNBR--;IDLE++;}strcpy(car[outcar_dex].leave_time,str);//存储车离开时间字符串car[outcar_dex].leave=*time;//存储车离开时间Settle_Accounts(car[outcar_dex]);//结算for(i=outcar_dex;i<car_dex;i++)//存储数组前移{car[i]=car[i+1];}car_dex--;//下标-- 为储存下一辆车做准备}}

6.结算

打印到达和离开信息,并用difftime函数计算时间差,单位为秒,再将时间单位化为小时,最后计算费用并将时间和费用信息进行打印。

void Settle_Accounts(struct car outCar)//结账{printf("%s:%s:%s\n",outCar.type,outCar.num,outCar.reach_time);//串口打印到达信息printf("%s:%s:%s\n",outCar.type,outCar.num,outCar.leave_time);//串口打印离开信息outCar.dftime=difftime(outCar.leave,outCar.reach)/60/60;//difftime函数返回两时间的差值 单位为秒if(outCar.dftime==0)outCar.dftime=1;//不足一小时记为一小时 outCar.EndPrice=outCar.dftime*outCar.type_price;  //计算费用printf("%s:%s:%d:%.2f\n\n",outCar.type,outCar.num,outCar.dftime,outCar.EndPrice);//串口打印时长及费用信息}

7.Main函数

在主循环之前需要做好初始化工作。
1.要先重置定时器更新标志位(TIMX->SR=0),否则程序运行后将立刻进入定时器中断函数。
2.开启串口接收中断
3.LCD初始化显示
4.PWM初始化
主循环逻辑大体就是实时更新LCD和LED,并检测是否需要对数据进行转换,转换完后判断数据是否合法,合法的话是车辆进入还是车辆离开。

int main(void){  /* USER CODE BEGIN 1 */time_t time;//保存传输的时间char type[10];//保存传输的类型char carNum[20];//车牌号char timStr[60];//时间字符串//memset(&time, 0, sizeof(time));  /* USER CODE END 1 */  /* MCU Configuration--------------------------------------------------------*/  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */  HAL_Init();  /* USER CODE BEGIN Init */  /* USER CODE END Init */  /* Configure the system clock */  SystemClock_Config();  /* USER CODE BEGIN SysInit */  /* USER CODE END SysInit */  /* Initialize all configured peripherals */  MX_GPIO_Init();  MX_TIM3_Init();  MX_USART1_UART_Init();  MX_TIM6_Init();  /* USER CODE BEGIN 2 */LCD_Init();//LCD初始化LCD_Init_Show();//LCD初始化显示HAL_UART_Receive_IT(&huart1,&recDat,sizeof(recDat));//开启串口接收中断TIM6->SR=0;//中断标志位清零LED_Close[2]=1;//LED2默认为灭HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_2);//PWM关闭HAL_GPIO_WritePin(GPIOA,GPIO_PIN_7,GPIO_PIN_RESET);//输出持续低电平  /* USER CODE END 2 */  /* Infinite loop */  /* USER CODE BEGIN WHILE */  while (1)  {    /* USER CODE END WHILE */    /* USER CODE BEGIN 3 */KEY_Scan();//按键扫描LCD_Refresh(1);//LCD更新显示if(switch_flag==1 && !Error)//接收数组转换标志为1并且无错误就继续执行{switch_flag=0;//转换标志位重置Switch_RecBuff(type,carNum,&time,timStr);//进行数据转换Error=Dat_Check(type,carNum);//判定接收数据是否合法if(!Error)//如果无错误{Car_Change(type,carNum,&time,timStr);//对存储车辆信息进行更新}else//接收数据不合法{Error=0;//重置错误标志位printf("Error\n");//串口输出提示信息}LED_Change();}else if(Error)//接收数据长度不符{Error=0;//重置错误标志位printf("Error\n");//串口输出提示信息}LED_AllClose(LED_Close);//LED显示更新  }  /* USER CODE END 3 */}

四、实验结果

1.数据长度有误

a.数据过长
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)
b.数据过短
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)
c.返回
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)

2.数据不合法

a.类型错误
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)
b.时间不合法
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)
c.离开时间小于到达时间
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)

STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)
d.返回
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)

3.数据正常

a.输入车辆到达信息
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)
b.输入车辆离开信息
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)
c.返回
STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)

五、源码(转载请注明出处)

STM32综合-基于HAL库(第十二届蓝桥杯嵌入式省赛)


总结

以上就是全部内容,如有错误请批评指正。