蓝桥杯嵌入式第8届真题(完成) STM32G431
题目
分析和代码
对比第六届和第七届,这届的题目在逻辑思维上确实要麻烦不少,可以从题目看出,这届题目对时间顺序的要求很严格,所以就可以使用状态机的思想来编程,拿到类似题目不要急着写代码,一定要先分析好步骤,想出状态转换的逻辑后,在根据逻辑写代码写起来就很快了。
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "rtc.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "key.h"
#include "led.h"
#include "string.h"
#include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
extern struct Key key[4];//4个按键
uint8_t lcdtext[30]; //lcd显示的内容
uint8_t status[30]; //lcd显示的内容
uint8_t led;//LED状态
uint8_t setfloor[5]={0,0,0,0,0};//四层楼,为了方便1-4所以数组大小为5
uint32_t key_time_1s; //按键按下后等待的1s
uint32_t open_or_close_door_time_4s; //开门或者关门所需时间
uint32_t up_or_down_dir_time_6s; //电梯往上一楼或者往下一楼所需的时间
uint32_t wait_time_2s; //电梯在每层楼等待时间
uint8_t current_floor = 1;//当前所在层
RTC_DateTypeDef D; //用于显示日期
RTC_TimeTypeDef T; //用于显示时间
uint8_t process_status = 0;//执行的状态
uint8_t dir = 2;//是上还是下,默认停止
//下面用于流水灯的部分
uint32_t lastUpdateTime = 0; // 上次更新LED状态的时间
const uint32_t updateInterval = 200; // 更新间隔,以毫秒为单位
uint8_t flow_led_enable = 0; //是否打开流水灯
/* 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 */
void led_process(void);
void key_process(void);
void lcd_process(void);
void status_process(void);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* 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 */
/* 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_RTC_Init();
MX_TIM16_Init();
MX_TIM17_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
LCD_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
LCD_Clear(Black);
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
LED_display(0x00);
sprintf((char *)lcdtext," %d",current_floor); //默认为1楼
LCD_DisplayStringLine(Line3,lcdtext);
while (1)
{
led_process();
lcd_process();
key_process();
status_process();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
RCC_OscInitStruct.PLL.PLLN = 20;
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_2) != HAL_OK)
{
Error_Handler();
}
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
void key_process(void)
{
if((process_status==0||process_status==1))//没有按键按下时,和按键按下1s内还可以继续按
{
sprintf((char *)status," wait key ");
if(key[0].key_flag&¤t_floor!=0+1) //不能是当前楼层
{
key[0].key_flag = 0;
setfloor[1] = 1;//设置目标楼层
led|=0x01; //设置对应led打开
LED_display(led);//打开对应led
process_status = 1;//按键按下后进入1s等待状态
key_time_1s = uwTick; //更新按键按下时时间
}
if(key[1].key_flag&¤t_floor!=1+1)
{
key[1].key_flag = 0;
setfloor[2] = 1;
led|=0x02;
LED_display(led);
process_status = 1;
key_time_1s = uwTick;
}
if(key[2].key_flag&¤t_floor!=2+1)
{
key[2].key_flag = 0;
setfloor[3] = 1;
led|=0x04;
LED_display(led);
process_status = 1;
key_time_1s = uwTick;
}
if(key[3].key_flag&¤t_floor!=3+1)
{
key[3].key_flag = 0;
setfloor[4] = 1;
led|=0x08;
LED_display(led);
process_status = 1;
key_time_1s = uwTick;
}
}
}
void lcd_process(void)
{
HAL_RTC_GetDate(&hrtc,&D,RTC_FORMAT_BIN);
HAL_RTC_GetTime(&hrtc,&T,RTC_FORMAT_BIN);
sprintf((char *)lcdtext," FLOOR");
LCD_DisplayStringLine(Line1,lcdtext);
// sprintf((char *)lcdtext," %d",current_floor);
// LCD_DisplayStringLine(Line3,lcdtext);
sprintf((char *)lcdtext," %02d:%02d:%02d",T.Hours,T.Minutes,T.Seconds);
LCD_DisplayStringLine(Line4,lcdtext);
//LCD_ClearLine(Line7);
sprintf((char *)lcdtext,"%s",status);
LCD_DisplayStringLine(Line7,lcdtext);
sprintf((char *)lcdtext," floors:%d-%d-%d-%d",setfloor[1],setfloor[2],setfloor[3],setfloor[4]);
LCD_DisplayStringLine(Line8,lcdtext);
}
void status_process(void)
{
if(process_status)
{
switch(process_status)
{
case 1: //状态1:等待1s内是否有按键按下
{
sprintf((char *)status," wait key 1s ");
if((uwTick-key_time_1s)>=1000) //如果1s到了,进入下一个关门状态
{
sprintf((char *)status," key_1s_yes ");
process_status = 2;
}
}break;
case 2: //状态2:开始关门
{
sprintf((char *)status," close door ");
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);//关门
__HAL_TIM_SET_COMPARE(&htim17,TIM_CHANNEL_1,250); //占空比50%
HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);
process_status = 3; //进入等待关门状态
open_or_close_door_time_4s = uwTick;//更新关门时间
}break;
case 3://状态3:4s关门时间等待,关完门开始判断电梯是上行还是下行
{
sprintf((char *)status,"wait close door 4s ");
if(uwTick-open_or_close_door_time_4s>=4000) //关门后,开始判断上下行
{
sprintf((char *)status,"close door yes ");
int up = 0, down = 0;
HAL_TIM_PWM_Stop(&htim17,TIM_CHANNEL_1);
// 检查上行
for(int i = current_floor + 1; i < 5; i++) { //如果电梯数组中存在比当前楼层高的楼层被设置
if(setfloor[i] == 1) {
up = 1;
break;
}
}
// 检查下行
for(int i = current_floor - 1; i >= 1; i--) { //如果电梯数组中存在比当前楼层低的楼层被设置
if(setfloor[i] == 1) {
down = 1;
break;
}
}
// 判断方向
if(up && !down) { //只有往上
dir = 1;
} else if(down && !up) { //只有往下
dir = 0;
} else if(up && down) { //上下都有,先向上
dir = 1;
} else {
dir = 2; //都没有
}
sprintf((char *)status," move ");
sprintf((char *)lcdtext," dir:%d--%d",up,down);
LCD_DisplayStringLine(Line9,lcdtext);
if(dir==1)//上行
{
flow_led_enable = 1;
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim16,TIM_CHANNEL_1,800); //占空比80%
HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);
up_or_down_dir_time_6s = uwTick;//更新上行时间
process_status = 4;
}else if(dir==0)//下行
{
flow_led_enable = 1;
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim16,TIM_CHANNEL_1,600); //占空比80%
HAL_TIM_PWM_Start(&htim16,TIM_CHANNEL_1);
up_or_down_dir_time_6s = uwTick;//更新上行时间
process_status = 4;
}else if(dir==2)//既不需要上行也不需要下行,回到电梯状态
{
process_status = 0;
}
}
}break;
case 4://状态4:等待6s上下行时间
{
sprintf((char *)status," wait move 6s ");
if(uwTick-up_or_down_dir_time_6s>=6000) //上下行时间到了
{
sprintf((char *)status," move yes ");
if(dir==1) //如果是上行
{
current_floor+=1;
}
else{
current_floor-=1;
}
HAL_TIM_PWM_Stop(&htim16,TIM_CHANNEL_1);//
setfloor[current_floor] = 0;//已到达该层
// 显示楼层号并闪烁两次
for(int i = 0; i < 2; i++) // 闪烁两次
{
sprintf((char *)lcdtext," %d",current_floor);
LCD_DisplayStringLine(Line3, lcdtext); // 显示楼层号
HAL_Delay(500); // 延时500ms
LCD_ClearLine(Line3); // 清除楼层号显示
HAL_Delay(500); // 延时500ms
}
// 再次显示楼层号
sprintf((char *)lcdtext," %d",current_floor);
LCD_DisplayStringLine(Line3, lcdtext);
switch(current_floor)
{
case 1:
{
led &= ~0x01; // 使用位清除操作关闭LED1
}break;
case 2:
{
led &= ~0x02; // 关闭LED2
}break;
case 3:
{
led &= ~0x04; // 关闭LED3
}break;
case 4:
{
led &= ~0x08; // 关闭LED4
}break;
}
LED_display(led); // 更新LED显示
flow_led_enable = 0; // 关闭流水灯
led &= 0x0F; // 保持低四位状态不变,关闭高四位LED
LED_display(led); // 更新LED显示
open_or_close_door_time_4s = uwTick;//更新开门时间
process_status =5;
}
}break;
case 5 ://状态5:等待开门4s时间到
{
sprintf((char *)status," wait open door 4s ");
if(uwTick-open_or_close_door_time_4s>=4000)//打开门
{
sprintf((char *)status," open door yes ");
wait_time_2s = uwTick;//更新等待时间
process_status = 6;
}
}break;
case 6 ://状态5:等待开门4s时间到
{
sprintf((char *)status," wait 2s ");
if(uwTick-wait_time_2s>=2000)//每层停留时间
{
sprintf((char *)status," wait 2s yes ");
wait_time_2s = uwTick;//更新等待时间
process_status = 2;
}
}break;
}
}
}
void led_process(void)
{
static uint8_t flow_led_state = 0; // 初始状态为0,表示流水灯未激活
if((uwTick - lastUpdateTime) >= updateInterval && flow_led_enable)
{
lastUpdateTime = uwTick; // 更新最后一次更新时间
if(flow_led_state == 0) // 如果流水灯未激活,根据方向初始化流水灯状态
{
flow_led_state = (dir == 1) ? 0x08 : 0x01; // 从左侧或右侧开始
}
else
{
if(dir == 1) // 上行:从右到左流水
{
flow_led_state <<= 1; // 向左移动
if(flow_led_state > 0x08) // 如果超过了最左侧,重置到最右侧
{
flow_led_state = 0x01;
}
}
else if(dir == 0) // 下行:从左到右流水
{
flow_led_state >>= 1; // 向右移动
if(flow_led_state < 0x01) // 如果超过了最右侧,重置到最左侧
{
flow_led_state = 0x08;
}
}
}
// 更新LED状态,仅修改高四位,保持低四位不变
// 注意:这里假设flow_led_state只影响一个LED,需要根据实际情况调整
led = (led & 0x0F) | (flow_led_state << 4); // 将流水灯状态左移4位,合并到led的高四位
LED_display(led); // 更新LED显示
}
}
/* 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 */
/* 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,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
key.c
#include "key.h"
struct Key key[4] = {0,0,0,0};
extern uint8_t process_status;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM2)
{
if((process_status==0||process_status==1))
{
key[0].key_gpio = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key[1].key_gpio = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key[2].key_gpio = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key[3].key_gpio = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
for(int i = 0;i<4;i++)
{
switch(key[i].key_index)
{
case 0:
{
if(key[i].key_gpio==0)
{
key[i].key_index=1;
}
}break;
case 1:
{
if(key[i].key_gpio==0)
{
key[i].key_flag =1;
key[i].key_index=2;
}else{
key[i].key_index=0;
}
}break;
case 2:
{
if(key[i].key_gpio==1)
{
key[i].key_index=0;
}
}break;
}
}
}
}
}
我一共将这个题目分为了6个状态
- 状态0
此状态用于等待按键按下以设置目标楼层,只要右按键按下,就将对应的楼层数组置1,打开对应的led,记录当前按键按下的时间,最后进入状态1
- 状态1:
此状态的进入是由于存在按键被按下,设置了目标楼层,该状态一直等待1s的到来,在状态0和状态1状态下按键仍然可以按下,因为某个按键按下后,在1s内仍然可以设置目标楼层,每一次重新按下按键,倒计时都会刷新,同样为了防止其余状态下按键仍然可以按下导致flag置1,在key.c的定时器回调函数中也只有状态0和状态1才能判断按键是否按下
- 状态2:
此状态为开始关门状态,根据题目要求,将PA5置低电平,同时设置TIM17通道1的占空比为50% ,更新关门时间,进入状态3
- 状态3:
题目要求开关门都需要4s,所以此状态为等待关门状态,等待结束后,开始根据setfloor数组确定当前电梯是向上,还是向下,设置的楼层会导致4种状态
- 只有比当前楼层高的楼层被设置,dir=1,往上走
- 只有比当前楼层低的楼层被设置,dir=0,往下走
- 比当前楼层高的和低的都设置,例如当前楼层是2层,比2层低的1层和比2层高的3,4层都被设置,dir=1,同样先向上走,再往下走
- 默认状态都没有被设置,在按键时已经限制条件不能设置当前楼层,dir=2,保持在当前楼层
根据dir的取值,设置上行还是下行,并打开对应PWM输出和电平信号,更新电梯开始上下行时间,或者是就在当前层然后,回到状态0,等待设置目标楼层
- 状态4:
题目要求上行或者下行都需要6s,该状态是为了等待6s到来,时间到来后,根据dir将当前楼层+1或者-1,同时当前楼层闪烁两次,关闭当前楼层对应的led灯,关闭流水灯,然后更新开始开门时间,进入状态5
- 状态5:
等待开门时间4s,时间到达后,更新每层停留的2s时间,进入下一个状态,状态6
- 状态6:
每层的等待时间2s,等待完后回到,状态2开始关门,继续按顺序执行,直到没有目标楼层,回到状态0等待按键按下。
led.c
#include "led.h"
void LED_display(uint8_t led)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,led<<8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
led_process用于处理流水灯,根据电梯运行方向确定,流水灯方向,led = (led & 0x0F) | (flow_led_state << 4); 这句的意思是首先保持低四位的保持不变,然后将流水灯的状态左移4位,与之相或从而实现不影响低四位led的效果
为了便于观察状态,添加了一些表示状态的信息显示在lcd上,时间控制大部分使用滴答定时器uwTick以防止delay阻塞程序