此博客适合有一定基础的同学快速通过蓝桥杯。
项目一:点亮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:启动定时器_IT:Interrupt(中断),表示以「中断模式」启动,而不是轮询模式&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 的预分频值(动态修改定时时间) |
综上:
定时器中断的完整流程(写的代码对应每一步)
- CubeMX 配置 :设置 TIM2 的时钟、预分频(PSC)、自动重装载值(ARR),开启更新中断 → 生成
htim2结构体和MX_TIM2_Init() - 启动定时器 :
HAL_TIM_Base_Start_IT(&htim2)→ 定时器开始计数 - 计数溢出 :定时器从 0 计数到 ARR,触发更新中断 → 硬件进入中断服务函数
TIM2_IRQHandler - HAL 库处理 :
TIM2_IRQHandler调用HAL_TIM_IRQHandler,清除中断标志,判断中断类型 - 调用回调函数 :HAL 库自动调用
HAL_TIM_PeriodElapsedCallback→ 执行你写的 LED 翻转代码 - 循环往复:定时器自动重装载,继续计数,重复上述流程
写的两段代码,正好对应 **「启动定时器」和「中断处理逻辑」** 两个核心环节,缺一不可。
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 |
| 输入捕获 | CAPTURE 或 IC |
| PWM | PWM |
| 开始定时器 | START |
| 中断 | IT |
或者去搜索:__HAL_TIM_也可以。(另外如果找不到一些.h文件也不太要紧,去.c里面找吧,也能找得到就是慢一点。。。我也不知道我为何没有.h。。。不知道是因为版本低还是因为固件库版本低。。。)