第六届 蓝桥杯 嵌入式 省赛

发布于:2025-03-30 ⋅ 阅读:(21) ⋅ 点赞:(0)

参考

第八届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)_蓝桥杯嵌入式第八届-CSDN博客

一、CubeMx 配置

第六届 蓝桥杯 嵌入式 省赛 -CSDN博客,一样的配置,把 .ioc 文件拷贝过来就行。

二、代码编写

1. 头文件

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
//#include "adc.h"
#include "rtc.h"
#include "tim.h"
//#include "usart.h"
#include "gpio.h"
#include <stdio.h>   // 提供 sprintf()
#include <string.h>  // 提供 strlen()

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
//#include "lcd.h"
#include "i2c_hal.h"
/* USER CODE END Includes */

2.  全局变量

用户要定义的变量,后面缺啥补啥

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
RTC_TimeTypeDef H_M_S_Time;
RTC_DateTypeDef Y_M_D_Date;

/* USER CODE END PTD */

/* USER CODE BEGIN PV */
//*减速变量
__IO uint32_t uwTick_Key_Set_Point = 0;//控制Key_Proc的执行速度
__IO uint32_t uwTick_Led_Set_Point = 0;//控制Led_Proc的执行速度
__IO uint32_t uwTick_Lcd_Set_Point = 0;//控制Lcd_Proc的执行速度
__IO uint32_t uwTick_Usart_Set_Point = 0;//控制Usart_Proc的执行速度
 
//*按键扫描专用变量
uint8_t ucKey_Val, unKey_Down, ucKey_Up, ucKey_Old;
 
//*LED专用变量
uint8_t ucLed;
 
//*LCD显示专用变量
uint8_t Lcd_Disp_String[21];//最多显示20个字符
 
 
//用户自定义变量区


/* USER CODE END PV */

3. 函数声明

//main.h

/* USER CODE BEGIN EFP */
void Key_Proc(void);
void Led_Proc(void);
void Lcd_Proc(void);
//void Usart_Proc(void);
void Elev_Proc(void);

/* USER CODE END EFP */

4. 主函数

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_ADC1_Init();
  //MX_ADC2_Init();
  MX_TIM2_Init();
  MX_TIM6_Init();
  //MX_USART1_UART_Init();
  MX_RTC_Init();
  MX_TIM3_Init();
  MX_TIM15_Init();
  MX_TIM17_Init();
  /* USER CODE BEGIN 2 */
	LCD_Init();
  LCD_Clear(White);
  LCD_SetBackColor(White);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
		Key_Proc();
		Led_Proc();
		Lcd_Proc();
		//Usart_Proc();
		Elev_Proc();
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

5. 函数实现

5.1 按键

按键永远是逻辑的开端。

重新审题:

4个按键代表4个楼层。

按下后点亮对应的LED灯(LD1-LD4)。

如果此时再按该楼层,则按键无效。

一次可以设置多个目标楼层。

在最后一次按键按完 1 秒之后,模拟电机开始运行。这句话其实隐含了,只能在空闲状态下进行楼层选择。
  • 首先,空闲状态下有 1秒的按键判断。
    • 那么,按键每次按下,都要刷新这 1秒的时间判断。
  • 其次,由于是可以多目标选择,那么逻辑上4个LED灯是求或的。
    • 需要加个前提,不是当前平台。
    • 举个例子:如果当前在2楼,且空闲状态,此时按平台2,是没有效果的。
  • 最后,LED的高4位不要动,因为要作为流水灯的。
void Key_Proc(void)
{
	if((uwTick -  uwTick_Key_Set_Point)<50)	return;//减速函数
		uwTick_Key_Set_Point = uwTick;
 
	ucKey_Val = Key_Scan();
	unKey_Down = ucKey_Val & (ucKey_Old ^ ucKey_Val); 
	ucKey_Up = ~ucKey_Val & (ucKey_Old ^ ucKey_Val);	
	ucKey_Old = ucKey_Val;
	if(state == ELEV_IDLE)//空闲状态
	{
		if(unKey_Down == 1)//B1按压
		{
			if(ucPlat != 1) ucSet |= 0x01; //ucPlat的1234表示当前所处的平台
		}	//ucSet用于记录几号按键按下去了。只用低4位,分别对应LD4~LD1,按键B4~B1
		else if(unKey_Down == 2)//B2按压
		{
			if(ucPlat != 2) ucSet |= 0x02; 
		}	
		else if(unKey_Down == 3)//B3按压
		{
			if(ucPlat != 3) ucSet |= 0x04; 	
		}		
		else if(unKey_Down == 4)//B4按压
		{		
			if(ucPlat != 4) ucSet |= 0x08; 	
		}		
		
		ucLed &= 0xF0;
		ucLed |= ucSet;		
		if(unKey_Down != 0)//当有按键按下去,启动计时
		{
			uwTick_Set_Point = uwTick;
		}		
	}
}

5.2 流水灯

题目没说灯的流动方向和上下方向的关系。这里就设定:

上升的时候,灯从右往左

下降的时候,灯从左往右

枚举方向

typedef enum {
    DIR_UP = 1,
    DIR_DOWN = 2
} Direction;
static Direction dir = DIR_UP;
void Led_Proc(void)
{
	if((uwTick -  uwTick_Led_Set_Point)<200)	return;//减速函数
		uwTick_Led_Set_Point = uwTick;
	LED_Disp(ucLed);
}

uint8_t Flow = 0x10;//流水的变量

void UpdateFlowLED(Direction dir) {
    ucLed &= 0x0F;
    ucLed |= Flow;
    if (dir == DIR_UP) {
        Flow = (Flow >> 1);
				if(Flow == 0x08)
						Flow = 0x80;
    } else {
        Flow = (Flow << 1);
				if(Flow == 0x00)
						Flow = 0x10;
    }
    LED_Disp(ucLed);
}

//void LED_Disp(uint8_t ucLed)在gpio.c中定义了

5.3 LCD

void Lcd_Proc(void)
{
	if((uwTick -  uwTick_Lcd_Set_Point)<100)	return;//减速函数
		uwTick_Lcd_Set_Point = uwTick;
	
	//开机屏幕测试代码
	sprintf((char *)Lcd_Disp_String, " Current Platform");
	LCD_DisplayStringLine(Line1, Lcd_Disp_String);	
	
	sprintf((char *)Lcd_Disp_String, "          %1d",(unsigned int)ucPlat);
	LCD_DisplayStringLine(Line3, Lcd_Disp_String);		
	//*RTC内容显示
	HAL_RTC_GetTime(&hrtc, &H_M_S_Time, RTC_FORMAT_BIN);//读取日期和时间必须同时使用
	HAL_RTC_GetDate(&hrtc, &Y_M_D_Date, RTC_FORMAT_BIN);
	sprintf((char *)Lcd_Disp_String, "       %02d-%02d-%02d",(unsigned int)H_M_S_Time.Hours,(unsigned int)H_M_S_Time.Minutes,(unsigned int)H_M_S_Time.Seconds);
	LCD_DisplayStringLine(Line6, Lcd_Disp_String);		
}

5.4 状态机

stateDiagram
    [*] --> Idle
    Idle --> DoorClosing: 有目标楼层
    DoorClosing --> DoorClosed: 关门完成
    DoorClosed --> Moving: 启动电机
    Moving --> Arrived: 到达目标层
    Arrived --> DoorOpening: 停止电机
    DoorOpening --> DoorOpened: 开门完成
    DoorOpened --> Idle: 无其他目标
    DoorOpened --> WaitingNext: 有其他目标
    WaitingNext --> DoorClosing: 等待结束

 枚举状态

typedef enum {
    ELEV_IDLE,              // 空闲状态(等待按键)
    ELEV_DOOR_CLOSING,      // 关门中
    ELEV_DOOR_CLOSED,       // 门已关
    ELEV_MOVING,            // 运行中(上下行)
    ELEV_ARRIVED,           // 到达目标层
    ELEV_DOOR_OPENING,      // 开门中
    ELEV_DOOR_OPENED,       // 门已开
    ELEV_WAITING_NEXT      // 等待下一目标
} ElevState;

static ElevState state = ELEV_IDLE;

 PWM 宏定义

/* USER CODE BEGIN PD */
// 硬件配置宏(便于修改)
#define DOOR_PWM_TIM        htim17   //PA7
#define DOOR_PWM_CHANNEL    TIM_CHANNEL_1
#define MOTOR_PWM_TIM       htim3    //PA6
#define MOTOR_PWM_CHANNEL   TIM_CHANNEL_1

/* USER CODE END PD */

实现

// 主状态机函数
void Elev_Proc(void) {
    if (!ucSet) return;  // 无目标层时退出

    switch (state) {
        //------------------ 空闲状态 ------------------//
        case ELEV_IDLE:
            if ((uwTick - uwTick_Set_Point) >= 1000) {
                state = ELEV_DOOR_CLOSING;
            }
            break;

        //------------------ 关门过程 ------------------//
        case ELEV_DOOR_CLOSING:
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);  // 关门方向
            __HAL_TIM_SET_COMPARE(&DOOR_PWM_TIM, DOOR_PWM_CHANNEL, 250);  // 50%占空比
            HAL_TIM_PWM_Start(&DOOR_PWM_TIM, DOOR_PWM_CHANNEL);
            LCD_ShowMessage(Line8, "Door Closing");
            
            stateEnterTime = uwTick;
            state = ELEV_DOOR_CLOSED;
            break;

        case ELEV_DOOR_CLOSED:
            if ((uwTick - stateEnterTime) >= 4000) {
                HAL_TIM_PWM_Stop(&DOOR_PWM_TIM, DOOR_PWM_CHANNEL);
                LCD_ShowMessage(Line8, "Door Closed");
                state = ELEV_MOVING;
            }
            break;

        //------------------ 运行过程 ------------------//
        case ELEV_MOVING: {
            uint8_t targetMask = (1 << (ucPlat - 1));
            if (ucSet > targetMask) {
                // 上行
                dir = DIR_UP;
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
                __HAL_TIM_SET_COMPARE(&MOTOR_PWM_TIM, MOTOR_PWM_CHANNEL, 800);  // 80%占空比
                HAL_TIM_PWM_Start(&MOTOR_PWM_TIM, MOTOR_PWM_CHANNEL);
                LCD_ShowMessage(Line8, "Elev Upping");
            } else if (ucSet < targetMask) {
                // 下行
                dir = DIR_DOWN;
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
                __HAL_TIM_SET_COMPARE(&MOTOR_PWM_TIM, MOTOR_PWM_CHANNEL, 600);  // 60%占空比
                HAL_TIM_PWM_Start(&MOTOR_PWM_TIM, MOTOR_PWM_CHANNEL);
                LCD_ShowMessage(Line8, "Elev Downing");
            }

            stateEnterTime = uwTick;
            state = ELEV_ARRIVED;
            break;
        }

        case ELEV_ARRIVED:
            if ((uwTick - stateEnterTime) >= 6000) {
                // 更新楼层
                ucPlat += (dir == DIR_UP) ? 1 : -1;
								sprintf((char *)Lcd_Disp_String, "          %1d",(unsigned int)ucPlat);
								LCD_DisplayStringLine(Line3, Lcd_Disp_String);					
			 
								sprintf((char *)Lcd_Disp_String, "Elev Runned 1 Floor        ");
								LCD_DisplayStringLine(Line8, Lcd_Disp_String);	


                // 检查是否到达目标层
                if (ucSet & (1 << (ucPlat - 1))) {
                    HAL_TIM_PWM_Stop(&MOTOR_PWM_TIM, MOTOR_PWM_CHANNEL);
                    state = ELEV_DOOR_OPENING;
                } else {
                    // 未到达,继续移动
                    stateEnterTime = uwTick;
                    state = ELEV_MOVING;
                }
            } else {
                // 运行中流水灯效果
                UpdateFlowLED(dir);
                HAL_Delay(300);
            }
            break;

        //------------------ 开门过程 ------------------//
        case ELEV_DOOR_OPENING:
			HAL_Delay(300);					
			sprintf((char *)Lcd_Disp_String, "             ");
			LCD_DisplayStringLine(Line3, Lcd_Disp_String);	
			HAL_Delay(300);								
			sprintf((char *)Lcd_Disp_String, "          %1d",(unsigned int)ucPlat);
			LCD_DisplayStringLine(Line3, Lcd_Disp_String);						
			HAL_Delay(300);					
			sprintf((char *)Lcd_Disp_String, "             ");
			LCD_DisplayStringLine(Line3, Lcd_Disp_String);	
			HAL_Delay(300);								
			sprintf((char *)Lcd_Disp_String, "          %1d",(unsigned int)ucPlat);
			LCD_DisplayStringLine(Line3, Lcd_Disp_String);	
				
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);    // 开门方向
            __HAL_TIM_SET_COMPARE(&DOOR_PWM_TIM, DOOR_PWM_CHANNEL, 300);  // 60%占空比
            HAL_TIM_PWM_Start(&DOOR_PWM_TIM, DOOR_PWM_CHANNEL);
            LCD_ShowMessage(Line8, "Door Opening");

            stateEnterTime = uwTick;
            state = ELEV_DOOR_OPENED;
            break;

        case ELEV_DOOR_OPENED:
            if ((uwTick - stateEnterTime) >= 4000) {
                HAL_TIM_PWM_Stop(&DOOR_PWM_TIM, DOOR_PWM_CHANNEL);
                LCD_ShowMessage(Line8, "Door Opened");

                // 清除当前目标层标志
                ucSet &= ~(1 << (ucPlat - 1));
                ucLed &= 0xF0;
                ucLed |= ucSet;
                LED_Disp(ucLed);

                state = (ucSet) ? ELEV_WAITING_NEXT : ELEV_IDLE;
            }
            break;

        //------------------ 等待下一目标 ------------------//
        case ELEV_WAITING_NEXT:
            if ((uwTick - stateEnterTime) >= 2000) {
                LCD_ShowMessage(Line8, "                  ");
                state = ELEV_DOOR_CLOSING;  // 继续下一目标
            }
            break;
    }
}

5.5 PWM

计数器输出频率

计数器占空比

题目要求PA6,也就是TIM3配成1kHz 。

PA7,也就是TIM17配置成2kHz。最好ARR配置成 1000,会更规范点。


__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 800);//修改占空比的基本操作 D=0.8
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);	//PA6   1khz  绿色波形
/**
  * @brief  Set the TIM Capture Compare Register value on runtime without calling another time ConfigChannel function.
  * @param  __HANDLE__ TIM handle.
  * @param  __CHANNEL__ TIM Channels to be configured.
  *          This parameter can be one of the following values:
  *            @arg TIM_CHANNEL_1: TIM Channel 1 selected
  *            @arg TIM_CHANNEL_2: TIM Channel 2 selected
  *            @arg TIM_CHANNEL_3: TIM Channel 3 selected
  *            @arg TIM_CHANNEL_4: TIM Channel 4 selected
  *            @arg TIM_CHANNEL_5: TIM Channel 5 selected
  *            @arg TIM_CHANNEL_6: TIM Channel 6 selected
  * @param  __COMPARE__ specifies the Capture Compare register new value.
  * @retval None
  */
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
  (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_4) ? ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_5) ? ((__HANDLE__)->Instance->CCR5 = (__COMPARE__)) :\
   ((__HANDLE__)->Instance->CCR6 = (__COMPARE__)))


网站公告

今日签到

点亮在社区的每一天
去签到