HAL库速成--嵌入式赛道--蓝桥杯速成

此博客适合有一定基础的同学快速通过蓝桥杯。

项目一:点亮LED灯+熟悉HAL工程创建

需要记住下面这个函数:(大家不熟悉也不用太担心,写点代码,再熟悉一下,到时候记得gotodefinition看定义即可)

cpp 复制代码
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

适配蓝桥杯的话,创建工程Configuration部分记得改SYS为串行总线Debug,RCC为外部晶振,并配置GPIO即可;Clock部分记得输入频率为24MHz,选择HSE,PLLCLK锁相环倍频,并将输出HCLK设置为80MHz(蓝桥杯要求即可);Pinout部分依据原理图如下可以看出LED的八个灯和LCD公用PC8-PC15,另外用PD2锁存。具体锁存和SN74HC573ADWR这个八位锁存器功能如下:

引脚 功能 关键逻辑
LE(11 脚,接 PD2) 锁存使能 高电平时透明(输入 = 输出),低电平时锁存(输出保持)
OE#(1 脚,接 GND) 输出使能 接 GND = 始终使能输出(低电平有效)
1D~8D(2~9 脚) 数据输入 接单片机 PC8~PC15 引脚
1Q~8Q(19~12 脚) 数据输出 接 8 个 LED 的负极
VCC/GND 电源 正常供电

欲使某一个LED灯一直点亮如PC8可有如下代码:

cpp 复制代码
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);   // ① PD2(LE)置高:锁存器进入"透明模式"
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); // ② PC8(1D输入)置低
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET); // ③ PD2(LE)置低:锁存器进入"锁存模式"

图一.LED原理图

另外如果想封装成函数模块,可以进行以下操作,此处建议新建一个组Group并自己重命名(后续自己写的函数文件都放在这个文件夹地下即可),之后点击魔术棒将其加进去.
图二.新建Group后加入路径操作流程

这里我们通过go to definition找到这些变量的定义位置如下:

cpp 复制代码
#define GPIO_PIN_0                 ((uint16_t)0x0001)  /* Pin 0 selected    */
#define GPIO_PIN_1                 ((uint16_t)0x0002)  /* Pin 1 selected    */
#define GPIO_PIN_2                 ((uint16_t)0x0004)  /* Pin 2 selected    */
#define GPIO_PIN_3                 ((uint16_t)0x0008)  /* Pin 3 selected    */
#define GPIO_PIN_4                 ((uint16_t)0x0010)  /* Pin 4 selected    */
#define GPIO_PIN_5                 ((uint16_t)0x0020)  /* Pin 5 selected    */
#define GPIO_PIN_6                 ((uint16_t)0x0040)  /* Pin 6 selected    */
#define GPIO_PIN_7                 ((uint16_t)0x0080)  /* Pin 7 selected    */
#define GPIO_PIN_8                 ((uint16_t)0x0100)  /* Pin 8 selected    */
#define GPIO_PIN_9                 ((uint16_t)0x0200)  /* Pin 9 selected    */
#define GPIO_PIN_10                ((uint16_t)0x0400)  /* Pin 10 selected   */
#define GPIO_PIN_11                ((uint16_t)0x0800)  /* Pin 11 selected   */
#define GPIO_PIN_12                ((uint16_t)0x1000)  /* Pin 12 selected   */
#define GPIO_PIN_13                ((uint16_t)0x2000)  /* Pin 13 selected   */
#define GPIO_PIN_14                ((uint16_t)0x4000)  /* Pin 14 selected   */
#define GPIO_PIN_15                ((uint16_t)0x8000)  /* Pin 15 selected   */
#define GPIO_PIN_All               ((uint16_t)0xFFFF)  /* All pins selected */

故我们可以在新建的group里面创建两个文件,如function.c,function.h两个文件其代码分别如下:

值得注意的是,当我们新建文件的时候,必须要包含下面这个头文件:

cpp 复制代码
#include "stm32g4xx_hal.h"
cpp 复制代码
#include "stm32g4xx_hal.h"
#include "function.h"

void LED_light(uint16_t ledshow){
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,ledshow,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
	
cpp 复制代码
#ifndef _function_h
#define _function_h
#include "stm32g4xx_hal.h"
void LED_light(uint16_t ledshow);

#endif

自此我们便可以在主函数里面调用这个函数,记得添加头文件先。注意:只改了main.c里面的这几个位置如下。语句功能分别是:导入头文件,保证初始状态不点亮,点亮PC8。

cpp 复制代码
/* USER CODE BEGIN Includes */
#include "function.h"
/* USER CODE END Includes */


  /* USER CODE BEGIN Init */
	LED_light(0x00);
  /* USER CODE END Init */

/* USER CODE BEGIN WHILE */
  while (1)
  {
		LED_light(GPIO_PIN_8);

    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

项目二:按键操作。

需要记住下面这个函数的使用:(回顾一下按键清零的这里)

cpp 复制代码
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

原理图可知按键按下为低电平,CubeMX中配置为input,和pull-up默认上拉电阻高电平。

此处编写代码可以继续在前面的CubeMX工程下进行,只要你前面的代码写在begin,end之间就不会被覆盖,图见下:

下面在function.c函数里面继续添加功能函数如key_scan(),如下:

cpp 复制代码
#include "stm32g4xx_hal.h"
#include "function.h"

void LED_light(uint16_t ledshow){
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOC,ledshow,GPIO_PIN_RESET);
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

uint8_t A0;
uint8_t B0;
uint8_t B1;
uint8_t B2;

void key_state_led(){
	A0=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
	B0=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
	B1=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
	B2=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
	if(A0==0){
		LED_light(GPIO_PIN_8);//按下对应按键便点亮对应LED灯
	}
	if(B0==0){
		LED_light(GPIO_PIN_9);
	}
	if(B1==0){
		LED_light(GPIO_PIN_10);
	}
	if(B2==0){
		LED_light(GPIO_PIN_11);
	}
}
	

头文件如下:

cpp 复制代码
#ifndef _function_h
#define _function_h
#include "stm32g4xx_hal.h"
void LED_light(uint16_t ledshow);
void key_state_led();
#endif

小问题:如果使用全局变量怎么写代码呢?

项目三:LCD显示屏。

这里的lcd.c,lcd.h,fonts.h官方会提供给我们,我们放到前面自己定义的文件夹和group里面即可.

需要记住的函数:

cpp 复制代码
void LCD_Init(void);
void LCD_SetTextColor(vu16 Color);
void LCD_SetBackColor(vu16 Color);
void LCD_DisplayStringLine(u8 Line, u8 *ptr);//等等函数

初始化main函数这里,注意颜色是首字母大写即可:

cpp 复制代码
  /* USER CODE BEGIN Init */
	LCD_Init();
	LCD_Clear(Blue);
	LCD_SetTextColor(White);
	LCD_SetBackColor(Black);
  /* USER CODE END Init */

Q:对于C基础不太好的同学,这里着重讲一下:如果我传入一个数字,想把它居中显示我应该如何写代码呢?

A:首先知道蓝桥杯给定的lcd屏有10行,20列,且都是从0开始的。有两种方法:

cpp 复制代码
//方法一,纯手动拼接
void LCD_Show1(int count1=1000)//假设都是count1四位数
{
    char text[20];//代表每行有20列个元素

    // 第一行:纯字符串,直接赋值
    strcpy(text, "        PARA        ");
    LCD_DisplayStringLine(Line1, (uint8_t*)text);

    // 第二行:手动把数字转成字符
    text[0] = ' '; text[1] = ' '; text[2] = ' '; text[3] = ' ';
    text[4] = 'P'; text[5] = 'D'; text[6] = '=';
    // 把count1=1000拆成字符
    text[7] = '0' + count1 / 1000;    // 千位:1
    text[8] = '0' + (count1 / 100)%10;// 百位:0
    text[9] = '0' + (count1 / 10)%10; // 十位:0
    text[10] = '0' + count1 % 10;     // 个位:0
    text[11] = 'H'; text[12] = 'z';
    text[13] = '\0'; // 必须加字符串结束符!
    LCD_DisplayStringLine(Line3, (uint8_t*)text);
}

//方法二,使用c标准库的stdio.h里面sprintf函数,函数原型如下,记得添加头文件
int sprintf(char *str, const char *format, ...);

//使用如下:
char text[20];  // 申请20字节的字符数组,用来存最终的字符串
int count1=1000;

sprintf(text, "    PD=%dHz    ", count1);

项目四:按键切换LCD显示。

要注意的地方就是,lcd显示不同内容时候,记得全部清零或者每次显示的时候把所有行都显示数据。

cpp 复制代码
void key_state_led(){
	A0=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
	B0=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
	B1=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
	B2=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
	if(A0==0){
		LED_light(GPIO_PIN_8);
		lcd_show();
	}
	else if(B0==0){
		LED_light(GPIO_PIN_9);
		lcd_show_2();
	}
	else if(B1==0){
		LED_light(GPIO_PIN_10);
	}
	else if(B2==0){
		LED_light(GPIO_PIN_11);
	}
	else
    {
        LED_light(0x0000);  // ???? ? ????
    }
}


//task3
int count1=1000;
int count2=2000;
int count3=3000;
void lcd_show(){
	LCD_Clear(Blue);//清零
		LCD_SetTextColor(White);
	LCD_SetBackColor(Black);
	char str[20];
	sprintf(str,"        PARA        ");
	LCD_DisplayStringLine(Line1,(uint8_t*)str);
	sprintf(str,"      PD=%dHz     ",count1);
	LCD_DisplayStringLine(Line3,(uint8_t*)str);
	sprintf(str,"      PH=%dHz     ",count2);
	LCD_DisplayStringLine(Line4,(uint8_t*)str);
	sprintf(str,"      PX=%dHz     ",count3);
	LCD_DisplayStringLine(Line5,(uint8_t*)str);
}

//task4
int count4=3;
int count5=1;
int count6=0;
int count7=2;
void lcd_show_2(){
	LCD_Clear(Blue);
		LCD_SetTextColor(White);
	LCD_SetBackColor(Black);
	char str[20];
	sprintf(str,"        RECD        ");
	LCD_DisplayStringLine(Line1,(uint8_t*)str);
	sprintf(str,"     NDA=%d          ",count4);
	LCD_DisplayStringLine(Line3,(uint8_t*)str);
	sprintf(str,"     NDB=%d          ",count5);
	LCD_DisplayStringLine(Line4,(uint8_t*)str);
	sprintf(str,"     NHA=%d          ",count6);
	LCD_DisplayStringLine(Line5,(uint8_t*)str);
	sprintf(str,"     NHB=%d          ",count7);
	LCD_DisplayStringLine(Line6,(uint8_t*)str);
}

项目五:led闪烁(定时器中断+HAL库配置).

定时器知识点简单回顾一下:最基本的时基单元由16位计数器+预分频器PSC+自动重装寄存器ARR三部分组成,依据输入时钟频率(此板子是80MHz)便可以得到定时时间。

应用很多:蓝桥杯常考的由定时中断(如led闪烁,按键长短按),输入捕获(测量信号频率周期),输出比较(输出PWM),编码器接口(应该不咋考吧?)。

对于STM32G431RBT6定时器引脚为:

高级定时器:TIM1、TIM8

通用定时器:TIM2、TIM3、TIM4、TIM15、TIM16、TIM17(有输入捕获输出比较)

基本定时器:TIM6、TIM7(有定时中断).

具体结构图和使用请自行寻找教程,在此不赘述。

下面开始进行led闪烁工程:

依据上面公式,如果需要0.1s的定时时间,又80MHz,可以令PSC=799,ARR=9999即可,CubeMX配置如下:(第一张图里面arp自动重装enable和disable在这里都可以没影响,区别在于改周期时是立即生效还是晚会生效)

去下面这里找到回调函数的全称,

代码怎么写呢?

1.首先需要在main函数加一句话,启动定时器 2 的更新中断,让定时器开始计数,溢出时触发中断

cpp 复制代码
	HAL_TIM_Base_Start_IT(&htim2);	
  • HAL_TIM:HAL 库定时器相关函数前缀
  • Base基本定时器模式(对应定时器的「计数溢出中断」,也就是我们用的定时功能)
  • Start:启动定时器
  • _ITInterrupt(中断),表示以「中断模式」启动,而不是轮询模式
  • &htim2:定时器 2 的句柄指针,htim2 是 CubeMX 自动生成的 TIM2 配置结构体

2.再去你的功能函数里面加一个定时器中断回调函数,定时器中断触发后,自动执行的用户代码,实现定时任务(这里是 LED 闪烁),

为什么必须写?

  • 定时器只是「到点触发中断」,但中断发生后要做什么,必须由你自己定义
  • 这个回调函数就是「中断发生后的处理逻辑」,没有它,定时器只会空转,不会有任何效果
cpp 复制代码
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM2){
		led_mode=!led_mode;
	}
}

另外根据自己需求去添加全局变量和修改功能函数即可,这里着重讲解一下后面定时器中断相关必须记住的一些核心函数。

启动和停止必备

函数名 作用 适用场景
HAL_TIM_Base_Start_IT(&htimx) 中断模式启动定时器 x 定时中断、LED 闪烁、PWM 中断等(你现在用的就是这个)
HAL_TIM_Base_Stop_IT(&htimx) 停止定时器 x 的中断 暂停定时任务
HAL_TIM_Base_Start(&htimx) 轮询模式启动定时器 x 不需要中断,只需要计数的场景(极少用)
HAL_TIM_Base_Stop(&htimx) 停止定时器 x 停止轮询模式的定时器

核心回调函数

函数名 作用 触发条件
HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 定时器更新 / 溢出中断回调 定时器计数到 ARR 值,触发更新中断时自动调用(你现在用的就是这个)
HAL_TIM_PWM_PulseFinishedCallback PWM 脉冲完成回调 PWM 输出完成时触发(PWM 模式用)
HAL_TIM_IC_CaptureCallback 输入捕获回调 输入捕获模式下,捕获到信号时触发(测频率、脉宽用)

其他常用函数

函数名 作用
__HAL_TIM_GET_COUNTER(&htimx) 获取定时器 x 的当前计数值(用于精确计时、延时)
__HAL_TIM_SET_COUNTER(&htimx, value) 手动设置定时器 x 的计数值(重置计数)
__HAL_TIM_SET_AUTORELOAD(&htimx, value) 修改定时器 x 的自动重装载值(动态修改定时时间)
__HAL_TIM_SET_PRESCALER(&htimx, value) 修改定时器 x 的预分频值(动态修改定时时间)

综上:

定时器中断的完整流程(写的代码对应每一步)

  1. CubeMX 配置 :设置 TIM2 的时钟、预分频(PSC)、自动重装载值(ARR),开启更新中断 → 生成htim2结构体和MX_TIM2_Init()
  2. 启动定时器HAL_TIM_Base_Start_IT(&htim2) → 定时器开始计数
  3. 计数溢出 :定时器从 0 计数到 ARR,触发更新中断 → 硬件进入中断服务函数TIM2_IRQHandler
  4. HAL 库处理TIM2_IRQHandler调用HAL_TIM_IRQHandler,清除中断标志,判断中断类型
  5. 调用回调函数 :HAL 库自动调用HAL_TIM_PeriodElapsedCallback → 执行你写的 LED 翻转代码
  6. 循环往复:定时器自动重装载,继续计数,重复上述流程

写的两段代码,正好对应 **「启动定时器」「中断处理逻辑」** 两个核心环节,缺一不可。

3.另外注意htim2这个结构体里面有两个我们必须会的成员:

cpp 复制代码
//注意htim是指针,所以用->,不用.
htim->Instance

//如果是普通结构体变量比如htim2,可以用.
htim2.Init.Prescaler = 7999;
htim2.Init.Period    = 999;//修改和查看这两个值

项目六:按键长按和短按(定时器中断+HAL库配置),考的相对不是很多。

先回顾一下c语言知识:

  • .h 里放类型定义(struct)、函数声明、宏定义
  • .c 里放变量定义、函数实现
  • (extern int a;)放在 .h 文件,告诉其他文件 "这个变量在别处定义了"同时其他.c文件只要包含了这个.h头文件,就可以调用这个变量,但是不建议用其实.

判断长按和短按方法:配置一个0.01s的定时器中断即10ms去扫描按键消抖的话可以让

  • 按下开始计时

  • < 70ms → 抖动,不算

  • 70ms ~ 700ms → 短按

  • > 700ms → 长按

  • 松开后触发标志

,用TIM3,同项目四一样在main函数里面加一句话启动TIM3,并在原有的回调函数里面加一个判断是否是TIM3的定时器中断,不妨使用一个简单的状态机代码(初始状态,消抖状态,判断长按短按状态)。定义一个结构体,里面有多个成员变量如下:

cpp 复制代码
	HAL_TIM_Base_Start_IT(&htim3);	

长短按代码相对比较复杂,大家见下方完整代码即可:

cpp 复制代码
#ifndef _function_h
#define _function_h
#include "lcd.h"
#include "stm32g4xx_hal.h"
void LED_light(uint16_t ledshow,uint8_t mode);
void key_state_led();
void lcd_show();
void lcd_show_2();
void key_scan(void);     
void key_process(void);   

struct keys{
	uint8_t key_start;
	uint8_t key_time;
	uint8_t key_dan_flag;
	uint8_t key_long_flag;
};

extern struct keys key[4];
#endif
cpp 复制代码
#include "stm32g4xx_hal.h"
#include "function.h"
#include "lcd.h"
#include "stdio.h"


uint8_t led_mode=0;
struct keys key[4]={0,0,0,0};

//task1
//依据mode情况判断是否点亮ledshow对应的led灯
void LED_light(uint16_t ledshow,uint8_t mode){
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
  HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);
	if(mode){
	HAL_GPIO_WritePin(GPIOC,ledshow,GPIO_PIN_RESET);}
	else{
	HAL_GPIO_WritePin(GPIOC,ledshow,GPIO_PIN_SET);}
	HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}


//task2
//按下对应按键就点亮对应灯并显示对应lcd屏幕界面
/*

uint8_t A0;
uint8_t B0;
uint8_t B1;
uint8_t B2;

void key_state_led(){
	A0=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
	B0=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
	B1=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
	B2=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
	if(A0==0){
		LED_light(GPIO_PIN_8,1);
		lcd_show();
	}
	else if(B0==0){
		LED_light(GPIO_PIN_9,1);
		lcd_show_2();
	}
	else if(B1==0){
		LED_light(GPIO_PIN_10,1);
	}
	else if(B2==0){
		LED_light(GPIO_PIN_11,1);
	}
	else
    {
        LED_light(0x0000,0);  // ???? ? ????
    }
}
*/

//task3
//待显示的静态界面1
int count1=1000;
int count2=2000;
int count3=3000;
void lcd_show(){
	LCD_Clear(Blue);
		LCD_SetTextColor(White);
	LCD_SetBackColor(Black);
	char str[20];
	sprintf(str,"        PARA        ");
	LCD_DisplayStringLine(Line1,(uint8_t*)str);
	sprintf(str,"      PD=%dHz     ",count1);
	LCD_DisplayStringLine(Line3,(uint8_t*)str);
	sprintf(str,"      PH=%dHz     ",count2);
	LCD_DisplayStringLine(Line4,(uint8_t*)str);
	sprintf(str,"      PX=%dHz     ",count3);
	LCD_DisplayStringLine(Line5,(uint8_t*)str);
	LED_light(GPIO_PIN_8,led_mode);
}

//task4
//待显示的静态界面2,用于后面按键切换使用
int count4=3;
int count5=1;
int count6=0;
int count7=2;
void lcd_show_2(){
	LCD_Clear(Blue);
	LCD_SetTextColor(White);
	LCD_SetBackColor(Black);
	char str[20];
	sprintf(str,"        RECD        ");
	LCD_DisplayStringLine(Line1,(uint8_t*)str);
	sprintf(str,"     NDA=%d          ",count4);
	LCD_DisplayStringLine(Line3,(uint8_t*)str);
	sprintf(str,"     NDB=%d          ",count5);
	LCD_DisplayStringLine(Line4,(uint8_t*)str);
	sprintf(str,"     NHA=%d          ",count6);
	LCD_DisplayStringLine(Line5,(uint8_t*)str);
	sprintf(str,"     NHB=%d          ",count7);
	LCD_DisplayStringLine(Line6,(uint8_t*)str);
	LED_light(GPIO_PIN_9,led_mode);
}
//task5
//定时器中断回调函数,每当触发中断便会回来执行这个函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
	if(htim->Instance == TIM2){//0.1s扫描,如果一直调用LED_light函数,会因为每0.1s翻转led_mode让led灯闪烁.
		led_mode=!led_mode;
	}
	if(htim->Instance == TIM3){//0.01s扫描,一直检测key状态
		key_scan();
	}
}

//task6
// ==============================
//扫描并记录key状态
void key_scan(void)
{
    // 读取四个按键引脚
    uint8_t key_val[4] = {
        HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0),    // KEY0
        HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0),    // KEY1
        HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1),    // KEY2
        HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2)     // KEY3
    };

    for(int i=0; i<4; i++)
    {
        switch(key[i].key_start)
        {
            case 0:  // 等待按下
                if(key_val[i] == 0)
                {
                    key[i].key_start = 1;
                    key[i].key_time = 0;
                }
                break;

            case 1:  // 按下消抖
                if(key_val[i] == 0)
                {
                    key[i].key_time++;
                    if(key[i].key_time >= 7)  // 70ms 消抖
                    {
                        
                        key[i].key_start = 2;
                    }
                }
                else
                {
                    key[i].key_start = 0;
                }
                break;

            case 2:  // 长按判断
                if(key_val[i] == 1)  // 松开了
                {
                    if(key[i].key_time < 70)  // 小于700ms → 短按
                    {
                        key[i].key_dan_flag = 1;
                    }
                    key[i].key_start = 0;
                    
                }
                else
                {
                    key[i].key_time++;
                    if(key[i].key_time >= 70)  // 大于700ms → 长按
                    {
                        key[i].key_long_flag = 1;
                    }
                }
                break;
        }
    }
}

// ==============================
// 按键处理函数(判断长短按),并执行对应操作
// ==============================
void key_process(void)
{
    // -------- KEY0 短按 / 长按 --------
    if(key[0].key_dan_flag)
    {
        key[0].key_dan_flag = 0;
        // 这里写短按逻辑
        lcd_show();
    }
    if(key[0].key_long_flag)
    {
        key[0].key_long_flag = 0;
        // 这里写长按逻辑
        // 例如:计数+1
    }

    // -------- KEY1 短按 / 长按 --------
    if(key[1].key_dan_flag)
    {
        key[1].key_dan_flag = 0;
        lcd_show_2();
    }
    if(key[1].key_long_flag)
    {
        key[1].key_long_flag = 0;
    }

    // KEY2、KEY3 同理
}

项目七:定时器输出比较输出PWM波(必考)

输出比较:比较CNT和CRR寄存器的数值,对输出电平置1,0和翻转功能。

PWM:频率(1/周期Ts),占空比(Ton/Ts),分辨率(占空比变化步距)三个参数记住。

需要记住几个公式:

具体参数:依据十四届真题,PA1输出TIM2_CH2的PWM波,1kHz,CubeMX配置如下:

代码书写:在main里面初始化和设置初始占空比。(注释掉的那句话是教程里面的,它使用的是1.6.0的固件库,而我使用的是1.3.0的固件库,没有那个函数,所以如果我们不知道具体函数名的话,建议去对应的库文件里面搜索,以设置占空比为例,Drivers/ └── STM32G4xx_HAL_Driver/ └── stm32g4xx_hal_tim.h ← 这里!!! └── stm32g4xx_hal_tim.c,再去搜索SET COMPARE或者PWM即可,其他的同理。)开头是两个下划线不是一个,千万别写错!!!

cpp 复制代码
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,50);//arr是99,初始化占空比50%
	//_HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,50);//

这里结束后就可以产生对应的pwm波了,需要注意的是输出捕获不需要去启动定时器中断和写回调函数,但因为我是在前面的工程上继续改的,所以还是启动了定时器2和3的中断以及写了回调函数,但是要注意这里因为我输出pwm波用的是定时器2,更改了arr和psc,所以原来的定时器2中断的定时时间也会变,定时器3无影响。

项目八:定时器输入捕获(必考)

需要记住的函数:(所有这些需要记住的函数都是在对应库文件能找到的,不用太担心,熟悉熟悉即可,到时候去对应文件.h里面找到声明用即可, 所有库函数的顺序永远是:上面是结构体定义最下面是函数列表, 需要 handle 指针 → 填 &htimx,已经是指针 → 直接填 htim

cpp 复制代码
uint32_t HAL_TIM_ReadCapturedValue(TIM_HandleTypeDef *htim, uint32_t Channel);

输入捕获:通道输入引脚出现指定电平跳变时,当前CNT的值被锁存到CCR中。

测量频率方法:

具体结构,测周法为例:通过检测上升和下降沿并存入当时的CNT进CCR1,2得到频率和占空比。

项目配置:依据蓝桥杯十四届真题,我们需要用过PA7输入捕获测量自己产生的PWM波的频率,同时要用PB4引脚输入捕获测量信号发生器的频率。计数单位是1us此时,arr不用动默认65535即可大点反倒不会提前溢出无所谓。

代码:(记得配置好后生成代码再继续去改)注意IC输入捕获和前面的TIM_Base启动的Start需要用IT版本的,因为需要中断回调;但是PWM不需要回调所以不用IT后缀版本的函数。

cpp 复制代码
	HAL_TIM_IC_Start_IT(&htim16,TIM_CHANNEL_1);
	HAL_TIM_IC_Start_IT(&htim17,TIM_CHANNEL_1);

第二步需要在function.c中编写对应的中断回调函数,(回调函数不需要在.h声明)去tim.h翻到最下面可以找到函数声明如下。

cpp 复制代码
/** @defgroup TIM_Exported_Functions_Group9 TIM Callbacks functions
  *  @brief   TIM Callbacks functions
  * @{
  */
/* Callback in non blocking modes (Interrupt and DMA) *************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_PeriodElapsedHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_IC_CaptureHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_TriggerHalfCpltCallback(TIM_HandleTypeDef *htim);
void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim);

而后写出如下代码:另外值得注意的是,请在function.c最上面包含"tim.h"头文件(这个是配置好CubeMX后生成的句柄文件,而前面我们必须添加的"stm32g4xx_hal.h"这个头文件是芯片内核+基础驱动),否则htim16,17会报错无法使用.(前面main使用tim2可以直接使用是因为CubeMX在配置生成的时候在main.c里面添加了tim.h了)

cpp 复制代码
//task7
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
		if(htim->Instance==TIM17){
			capture_value1=HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);//读当前计数值
			__HAL_TIM_SET_COUNTER(&htim17,0);//将数据清零
			fre1=80000000/(80*capture_value1);
	}
	if(htim->Instance==TIM16){
			capture_value2=HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);//读当前计数值
			__HAL_TIM_SET_COUNTER(&htim16,0);//将数据清零
			fre1=80000000/(80*capture_value2);
	}
}

一些没有定义的变量大家自行在文件顶上定义为全局变量即可,不再赘述。

一些需要记住的小点:

  • 不记得函数 → 去库的 .h 最下面找!
  • 不记得参数 → 看函数定义!
  • 需要指针 → 加 &htimx
  • 本身是指针 → 直接写 htim
  • htimx 是结构体,&htimx 是地址
  • 哪个 .c 用 htimx,哪个 .c 包含 tim.h
  • .h 里尽量少包含别的头文件
你想干嘛 搜什么(只搜这个)
设置比较值 / 占空比 COMPARE
设置计数器 COUNTER
输入捕获 CAPTUREIC
PWM PWM
开始定时器 START
中断 IT

或者去搜索:__HAL_TIM_也可以。(另外如果找不到一些.h文件也不太要紧,去.c里面找吧,也能找得到就是慢一点。。。我也不知道我为何没有.h。。。不知道是因为版本低还是因为固件库版本低。。。)

相关推荐
打瞌睡的朱尤3 小时前
4.1蓝桥杯训练
职场和发展·蓝桥杯
Little At Air3 小时前
LeetCode 30. 串联所有单词的子串 | 困难 C++实现
算法·leetcode·职场和发展
a里啊里啊3 小时前
常见面试题目集合
linux·数据库·c++·面试·职场和发展·操作系统
生信研究猿4 小时前
leetcode 121.买卖股票的最佳时机
算法·leetcode·职场和发展
And_Ii4 小时前
[蓝桥杯 2023 省 A] 更小的数
蓝桥杯
aqiu1111114 小时前
【算法日记 09】蓝桥杯实战:突破整数极限,拥抱“字符串思维”
算法·职场和发展·蓝桥杯
And_Ii4 小时前
[蓝桥杯 2023 省 A] 平方差
蓝桥杯
一轮弯弯的明月1 天前
贝尔数求集合划分方案总数
java·笔记·蓝桥杯·学习心得
迈巴赫车主1 天前
蓝桥杯1458双向排序
java·数据结构·算法·职场和发展·蓝桥杯