参考
第八届蓝桥杯嵌入式省赛程序设计题解析(基于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~B1else 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[*] --> IdleIdle --> 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__)))