文章目录
- 1.题目解析
- 1.1 分而治之,藕断丝连
- 1.2 模块化思维导图
- 1.3 模块解析
- 1.3.1 KEY模块
- 1.3.2 LED模块
- 1.3.3 LCD模块
- 1.3.4 TIM模块
- 1.3.5 UART模块
- 1.3.5.1 uart数据解析
- 2.源码
- 3.第十二届题目
前言:STM32G431RBT6实现嵌入式组第十二届题目解析+源码,本文默认读者具备基础的stm32知识。文章末尾有第十二届题目。
1.题目解析
第十二届虽说题目长,难度集中体现在uart接收的数据处理上。
1.1 分而治之,藕断丝连
还是那句话,将不同模块进行封装,通过变量进行模块间的合作。
函数将模块分而治之,变量使模块间藕断丝连。
1.2 模块化思维导图
下图根据题目梳理。还是使用思维导图。
1.3 模块解析
1.3.1 KEY模块
还是控制按一次处理一次。老朋友了我们就不多说了,题目限制了按键消抖和单次处理,所以我们要加上消抖,和第十一届的处理一模一样。
具体实现看源码
1.3.2 LED模块
ld1:有空闲车位亮,否则灭
ld2:PWM占空比20%输出亮,输出低电平灭
解决办法,设置一个标志位代表ld1~ld8,改变对应位的的值,再将标志位写入ODR寄存器中来控制led的亮灭。
具体实现看源码
1.3.3 LCD模块
lcd显示两个界面,注意首次切换的时候得清屏。
根据B1界面1和界面2切换;
状态0:参数界面;
状态1:费用设置界面;
具体实现看源码
1.3.4 TIM模块
TIM2产生1s时基。PSC:16999,ARR:9999;
TIM17通道1产生2kHzPWM。PSC:16,ARR:4999;
PSC和ARR计算公式(计算周期就是频率的倒数):
/* 定时开启uart接收中断1s */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_UARTEx_ReceiveToIdle_IT(&huart1, uart_rx_data, 24);
}
/* pa7pwm输出 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if(pa7_pwm_ctrl == 1) TIM17->CCR1 = 999;
else TIM17->CCR1 = 0;
}
1.3.5 UART模块
12届题目的难度就在uart的数据处理上。
1.单片机接收来自电脑固定格式的数据,我们就需要数据限制条件来写解析接收的数据,数据长度,停车类别,车牌格式,时间格式。
2.如果格式错误返回Error,else判断是停车还是取车。
2.1 应该先判断取车,这大家都能理解。如果是取车,计算收费,时间,返回给电脑。
2.2 如果是存车,先要判断是否有空闲车位,如果有存车,如果没有不做处理。
具体其他涉及函数代码请看源码
/* uart接收数据处理 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(analyze_uart_str(uart_rx_data)) //分析数据格式是否正确,错误传输Error
{
HAL_UART_Transmit_IT(&huart1, (uint8_t*)"Error", 7);
}
/*
格式正确处理顺序 :
判断是否是取车(停车场已有该车)-> 是: 取车+计算收费, 否: 判断是否有空闲车位 -> 是:停车计时,否:不做处理
*/
else{
uint8_t i = find_car_in_parking(parking_flag_temporary);
if(i != 8){ //表示取车
if(!strcmp(parking_flag_temporary.parking_type, "VNBR")){ //计算停车费
parking_fee = caculate_parking_time(i)*V_money;
parking_spaces.VNBR--;
parking_spaces.IDLE++;
}else if(!strcmp(parking_flag_temporary.parking_type, "CNBR")){
parking_fee = caculate_parking_time(i)*C_money;
parking_spaces.CNBR--;
parking_spaces.IDLE++;
}
sprintf((char*)parking_fee_str, "%4s:%4s:%u:%.2f", parking_flag_temporary.parking_type,
parking_flag_temporary.car_num, caculate_parking_time(i), parking_fee);
HAL_UART_Transmit_IT(&huart1, parking_fee_str, strlen((char*)parking_fee_str));
memset(&parking_flag[i], 0, sizeof(parking_flag[i]));
memset(&parking_time[i], 0, sizeof(parking_time[i]));
}
else{ //停车
i = find_free_parking();
if(i!=8){ //有空闲车位
parking_flag[i] = parking_flag_temporary;
parking_time[i] = parking_time_temporary;
if(!strcmp(parking_flag_temporary.parking_type, "CNBR")){
parking_spaces.CNBR++;
parking_spaces.IDLE--;
}
else if(!strcmp(parking_flag_temporary.parking_type, "VNBR")){
parking_spaces.VNBR++;
parking_spaces.IDLE--;
}
}
}
}
}
1.3.5.1 uart数据解析
我们可以使用指针加for单个字符判断,也可以使用string.h库中的字符处理函数,strcmp(), strcpy(),strncmp(), strncpy()等函数,将数据先切段,再通过各段的限制条件进行格式判断。
/**
* @brief 判断uart接收到的数据格式是否正确,这里只有长度判断、车位类型、车位号码、时间格式的判断,
* 还可以加时间的大小比如月份只能是1~12月。还有解析时间的时候,假如说要取车,那取车时间肯定大于停车时间……
* @para str: uart接收数据
* @retval 1:数据格式错误,0:正确
*/
uint8_t analyze_uart_str(uint8_t *str)
{
if(strlen((char*)str) != 24){ //
return 1;
}
else{
uint8_t *p = str;
if(*p == 'C' || *p == 'V' && !strncasecmp((char*)p+1, "NBR", 3)) {
strncpy(parking_flag_temporary.parking_type, (char*)str, 4);
}
else return 1;
for(p = str+5;p<str+9;p++){
if(*p<0 || *p>127) return 1;
}
strncpy(parking_flag_temporary.car_num, (char*)str+5, 4);
for(p = str+10; *p!='\0';p++){
if(*p<'0' || *p > '9') return 1;
}
strncpy(parking_flag_temporary.time, (char*)str+10, 14);
sscanf(parking_flag_temporary.time,"%4hu%2hhu%2hhu%2hhu%2hhu%2hhu",
&parking_time_temporary.years, &parking_time_temporary.dates, &parking_time_temporary.days,
&parking_time_temporary.hours, &parking_time_temporary.minutes, &parking_time_temporary.seconds);
}
return 0;
}
2.源码
我所有的实现都在main.c文件中。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "lcd.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//按键四种状态
enum{
key_released = 0U,
key_reduction,
key_pressed,
key_wait_released,
};
//停车位状态
typedef struct {
uint8_t CNBR;
uint8_t VNBR;
uint8_t IDLE;
} parking_t;
parking_t parking_spaces = {0, 0, 8};
//存储解析uart数据
typedef struct {
char parking_type[5];
char car_num[5];
char time[15];
}parking_flag_t;
parking_flag_t parking_flag[8] = {0}, parking_flag_temporary = {0};
//将时间字符串解析成数据
typedef struct{
uint16_t years;
uint8_t dates;
uint8_t days;
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
}calendar_t;
calendar_t parking_time[8] = {0},parking_time_temporary = {0};
/*
lcd_show_conv: lcd界面切换
pa7_pwm_ctrl: pwm输出控制
lcd_clear_flag: 清屏标志
ld_flag: ld状态标志
*/
uint8_t lcd_show_conv = 0, pa7_pwm_ctrl = 0, lcd_clear_flag = 0, ld_flag = 0;
/*
uart_rx_data: 接收来自串口的24字节的数据
lcd_str: lcd显示
parking_fee_str: 串口回复收费信息buff
*/
uint8_t uart_rx_data[25] = {0}, lcd_str[21] = {0}, parking_fee_str[25] = {0};
//消抖时间标记
uint32_t key_redu_tim = 0;
/*
keys_volt: 按键电平信息
keys_state: 按键状态信息
*/
uint8_t keys_volt[4] = {0}, keys_state[4] = {0};
/*
C_money: CNBR停车收费元/小时
V_money: VNBR停车收费元/小时
parking_fee: 计算停车费
*/
float C_money = 3.5f, V_money = 2.0f, parking_fee = 0.0f;
void key_state_gain();
void key_process();
void lcd_process();
uint8_t analyze_uart_str(uint8_t *str);
uint8_t find_free_parking();
uint8_t find_car_in_parking(parking_flag_t p);
uint32_t caculate_parking_time(uint8_t i);
void led_process();
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
LCD_Init();
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
/* 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_TIM2_Init();
MX_USART1_UART_Init();
MX_TIM17_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_PWM_Start_IT(&htim17, TIM_CHANNEL_1);
HAL_UARTEx_ReceiveToIdle_IT(&huart1, uart_rx_data, 24);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
key_state_gain();
key_process();
lcd_process();
led_process();
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV6;
RCC_OscInitStruct.PLL.PLLN = 85;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* 获取按键状态 */
void key_state_gain()
{
keys_volt[0] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
keys_volt[1] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
keys_volt[2] = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
keys_volt[3] = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
for(int i=0;i<4;i++)
{
if(keys_volt[i] == 0)
{
if(keys_state[i] == key_released){
key_redu_tim = HAL_GetTick();
keys_state[i] = key_reduction;
}
else if(keys_state[i] == key_reduction){
if(HAL_GetTick() - key_redu_tim>=10){
keys_state[i] = key_pressed;
}
}
else if(keys_state[i] == key_pressed)
keys_state[i] = key_wait_released;
}
else{
if(keys_state[i] == key_wait_released || keys_state[i] == key_pressed){
key_redu_tim = HAL_GetTick();
keys_state[i] = key_reduction;
}
else if(keys_state[i] == key_reduction){
if(HAL_GetTick() - key_redu_tim>=10){
keys_state[i] = key_released;
}
}
else keys_state[i] = key_released;
}
}
}
/* 根据按键状态设置对应标志 */
void key_process()
{
if(keys_state[0] == key_pressed){ //界面切换
lcd_show_conv ^= 1;
}
if(lcd_show_conv == 1){ //B2B3只在界面1起作用
if(keys_state[1] == key_pressed){ //++
C_money+= 0.5;
V_money+= 0.5;
}
if(keys_state[2] == key_pressed){ //--
C_money-= 0.5;
V_money-= 0.5;
if(V_money < 0) {
C_money = 1.5;
V_money = 0.0f;
}
}
}
if(keys_state[3] == key_pressed){ //pwm控制
pa7_pwm_ctrl ^= 1;
}
}
/* lcd两种状态 */
void lcd_process()
{
switch(lcd_show_conv)
{
case 0:
if(lcd_clear_flag == 1)
{
lcd_clear_flag = 0;
LCD_Clear(Black);
}
sprintf((char*)lcd_str, " Data ");
LCD_DisplayStringLine(Line2, lcd_str);
sprintf((char*)lcd_str, " CNBR:%d ", parking_spaces.CNBR);
LCD_DisplayStringLine(Line4, lcd_str);
sprintf((char*)lcd_str, " VNBR:%d ", parking_spaces.VNBR);
LCD_DisplayStringLine(Line6, lcd_str);
sprintf((char*)lcd_str, " IDLE:%d ", parking_spaces.IDLE);
LCD_DisplayStringLine(Line8, lcd_str);
break;
case 1:
if(lcd_clear_flag == 0)
{
lcd_clear_flag = 1;
LCD_Clear(Black);
}
sprintf((char*)lcd_str, " Data ");
LCD_DisplayStringLine(Line2, lcd_str);
sprintf((char*)lcd_str, " CNBR:%.2f ", C_money);
LCD_DisplayStringLine(Line4, lcd_str);
sprintf((char*)lcd_str, " VNBR:%.2f ", V_money);
LCD_DisplayStringLine(Line6, lcd_str);
break;
}
}
/**
* @brief 判断uart接收到的数据格式是否正确,这里只有长度判断、车位类型、车位号码、时间格式的判断,
* 还可以加时间的大小比如月份只能是1~12月。还有解析时间的时候,假如说要取车,那取车时间肯定大于停车时间……
* @para str: uart接收数据
* @retval 1:数据格式错误,0:正确
*/
uint8_t analyze_uart_str(uint8_t *str)
{
if(strlen((char*)str) != 24){ //
return 1;
}
else{
uint8_t *p = str;
if(*p == 'C' || *p == 'V' && !strncasecmp((char*)p+1, "NBR", 3)) {
strncpy(parking_flag_temporary.parking_type, (char*)str, 4);
}
else return 1;
for(p = str+5;p<str+9;p++){
if(*p<0 || *p>127) return 1;
}
strncpy(parking_flag_temporary.car_num, (char*)str+5, 4);
for(p = str+10; *p!='\0';p++){
if(*p<'0' || *p > '9') return 1;
}
strncpy(parking_flag_temporary.time, (char*)str+10, 14);
sscanf(parking_flag_temporary.time,"%4hu%2hhu%2hhu%2hhu%2hhu%2hhu",
&parking_time_temporary.years, &parking_time_temporary.dates, &parking_time_temporary.days,
&parking_time_temporary.hours, &parking_time_temporary.minutes, &parking_time_temporary.seconds);
}
return 0;
}
/* 查找是否有空闲车位 */
uint8_t find_free_parking()
{
for(uint8_t i=0;i<8;i++){
if(parking_flag[i].parking_type[0] != 'V' && parking_flag[i].parking_type[0] != 'C'){
return i;
}
}
return 8;
}
/* 查找是否该车要取车 */
uint8_t find_car_in_parking(parking_flag_t p)
{
for(uint8_t i=0;i<8;i++){
if(!strncasecmp(parking_flag[i].parking_type, p.parking_type, 4) &&
!strncasecmp(parking_flag[i].car_num, p.car_num, 4)){
return i;
}
}
return 8;
}
/* 停车时间计算 */
uint32_t caculate_parking_time(uint8_t i)
{
uint32_t hour = 0;
hour = (parking_time_temporary.years - parking_time[i].years)*365*24;
hour += (parking_time_temporary.dates - parking_time[i].dates)*30*24;
hour += (parking_time_temporary.days - parking_time[i].days)*24;
hour += (parking_time_temporary.hours - parking_time[i].hours);
if((parking_time_temporary.minutes-parking_time[i].minutes) >0 ||
(parking_time_temporary.seconds-parking_time[i].seconds) > 0){
hour++;
}
return hour;
}
/* led状态更新 */
void led_process()
{
if(parking_spaces.IDLE > 0){
ld_flag = 1;
}else ld_flag = 0;
if(pa7_pwm_ctrl == 1){
ld_flag += 1<<1;
}else ld_flag += 0<< 1;
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, 1);
GPIOC->ODR = 0xffff ^ ld_flag << 8;
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2, 0);
}
/* 定时开启uart接收中断1s */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_UARTEx_ReceiveToIdle_IT(&huart1, uart_rx_data, 24);
}
/* pa7pwm输出 */
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if(pa7_pwm_ctrl == 1) TIM17->CCR1 = 999;
else TIM17->CCR1 = 0;
}
/* uart接收数据处理 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(analyze_uart_str(uart_rx_data)) //分析数据格式是否正确,错误传输Error
{
HAL_UART_Transmit_IT(&huart1, (uint8_t*)"Error", 7);
}
/*
格式正确处理顺序 :
判断是否是取车(停车场已有该车)-> 是: 取车+计算收费, 否: 判断是否有空闲车位 -> 是:停车计时,否:不做处理
*/
else{
uint8_t i = find_car_in_parking(parking_flag_temporary);
if(i != 8){ //表示取车
if(!strcmp(parking_flag_temporary.parking_type, "VNBR")){ //计算停车费
parking_fee = caculate_parking_time(i)*V_money;
parking_spaces.VNBR--;
parking_spaces.IDLE++;
}else if(!strcmp(parking_flag_temporary.parking_type, "CNBR")){
parking_fee = caculate_parking_time(i)*C_money;
parking_spaces.CNBR--;
parking_spaces.IDLE++;
}
sprintf((char*)parking_fee_str, "%4s:%4s:%u:%.2f", parking_flag_temporary.parking_type,
parking_flag_temporary.car_num, caculate_parking_time(i), parking_fee);
HAL_UART_Transmit_IT(&huart1, parking_fee_str, strlen((char*)parking_fee_str));
memset(&parking_flag[i], 0, sizeof(parking_flag[i]));
memset(&parking_time[i], 0, sizeof(parking_time[i]));
}
else{ //停车
i = find_free_parking();
if(i!=8){ //有空闲车位
parking_flag[i] = parking_flag_temporary;
parking_time[i] = parking_time_temporary;
if(!strcmp(parking_flag_temporary.parking_type, "CNBR")){
parking_spaces.CNBR++;
parking_spaces.IDLE--;
}
else if(!strcmp(parking_flag_temporary.parking_type, "VNBR")){
parking_spaces.VNBR++;
parking_spaces.IDLE--;
}
}
}
}
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
3.第十二届题目