STM32GPIO输出实战-LED模板移植
上一节讲了一个随意驱动Led的模板,本节将带你从0移植
一,Cube建立代码框架
双击打开Cube,点击ACCESS TO MCU SELETOR
选择芯片型号STM32F429ZGTx,出现以下界面
配置时钟
选择调试接口为
配置工程路径,选择编译环境
配置代码生成器
点击生成代码
打开工程文件夹
发现有五个文件,说明代码框架搭建成功
二,移植调度器
在上面的基础上,在新建APP文件
复制以前的调度器文件到APP中
“scheduler.c”
#include "scheduler.h"
// 全局变量,用于存储任务数量
uint8_t task_num;
typedef struct {
void (*task_func)(void);
uint32_t rate_ms;
uint32_t last_run;
} task_t;
void led_task()
{
}
// 静态任务数组,每个任务包含任务函数、执行周期(毫秒)和上次运行时间(毫秒)
static task_t scheduler_task[] =
{
{led_task, 1, 0}, // 定义一个任务,任务函数为 Led_Proc,执行周期为 10 毫秒,初始上次运行时间为 0
};
/**
* @brief 调度器初始化函数
* 计算任务数组的元素个数,并将结果存储在 task_num 中
*/
void scheduler_init(void)
{
// 计算任务数组的元素个数,并将结果存储在 task_num 中
task_num = sizeof(scheduler_task) / sizeof(task_t);
}
/**
* @brief 调度器运行函数
* 遍历任务数组,检查是否有任务需要执行。如果当前时间已经超过任务的执行周期,则执行该任务并更新上次运行时间
*/
void scheduler_run(void)
{
// 遍历任务数组中的所有任务
for (uint8_t i = 0; i < task_num; i++)
{
// 获取当前的系统时间(毫秒)
uint32_t now_time = HAL_GetTick();
// 检查当前时间是否达到任务的执行时间
if (now_time >= scheduler_task[i].rate_ms + scheduler_task[i].last_run)
{
// 更新任务的上次运行时间为当前时间
scheduler_task[i].last_run = now_time;
// 执行任务函数
scheduler_task[i].task_func();
}
}
}
“scheduler.h”
#ifndef SCHEDULER_H
#define SCHEDULER_H
#include "mydefine.h"
void scheduler_init(void);
void scheduler_run(void);
#endif
打开Keil
按下图新建,保存mydefine.h到APP
在Keil中建立一个APP文件
将桌面上的APP文件内容添加的Keil的APP中
将桌面上的APP头文件路径添加到Keil
三,修改移植错误
按下图修改,原因在工程模板那节讲过
四,移植LED模板
1,基础框架介绍
移植之前,先介绍一下Cube生成的代码框架:
1,main.c
发现在main.c中,CubeMX将每种类型的代码位子都规定好了,如1处写用户编写的头文件,2处是Cube自动生成的时钟函数声明(其定义在main.c末尾)
3处是HAL库的初始化函数,内含:
- HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);设置优先级
- HAL_InitTick(TICK_INT_PRIORITY);使用SysTick定时器
- HAL_MspInit();初始化硬件
6处是时钟函数的定义,4处是其的调用
5处是GPIO的初始化(目前只初始化时钟,没有进行其他配置)
2,gpio.c和gpio.h
gpio.c是CubeMX生成的,只有一个gpio初始化的函数,这里只初始化了GPIOA,C,H的时钟,因为刚才用CubeMX生成代码时:配置Debug用到了GPIOA,配置时钟用到了GPIOC,H
gpio.h中也只有gpio初始化函数的声明
2,移植LED模板
1,CubeMX配置LED所需的GPIO
再次打开此工程的CubeMX
按照下图,找到你的Ledl连接的引脚,我选择C13,E6,E5,E4,E3,E2这6个引脚驱动共阴极LED
按照下图配置GPIO:输出低电平(默认关闭led),推挽输出,无上下拉电阻,输出速度为低,用户标签为LED1(将IO口宏定义为LED1)。依次改完其他的LED引脚
配置完GPIO,点击生成代码
生成完毕后,可以根据提示框打开工程
但我的Keil没有关闭,所以直接重新加载了
注意:再次使用CubeMX生成代码时需要重新选择芯片
2,二次框架分析
对GPIO进行配置后,代码框架又发生了什么变化
- main.c没有变化,main.h多出了一下的宏定义
- gpio.c
CubeMX生成的GPIO初始化代码,比刚才对了引脚配置
3,LED代码移植
在桌面创建led_app.c和led_app.h文件
将.c文件添加到Keil中
移植上一节led的模板
led_app.c
#include "led_app.h"
uint8_t ucLed[6] = {0,1,0,1,0,1}; // LED 状态数组 (6个LED)
/**
* @brief 根据ucLed数组状态更新6个LED的显示
* @param ucLed Led数据储存数组 (大小为6)
*/
void led_disp(uint8_t *ucLed)
{
uint8_t temp = 0x00; // 用于记录当前 LED 状态的临时变量 (最低6位有效)
static uint8_t temp_old = 0xff; // 记录之前 LED 状态的变量, 用于判断是否需要更新显示
for (int i = 0; i < 6; i++) // 遍历6个LED的状态
{
// 将LED状态整合到temp变量中,方便后续比较
if (ucLed[i]) temp |= (1 << i); // 如果ucLed[i]为1, 则将temp的第i位置1
}
// 仅当当前状态与之前状态不同的时候,才更新显示
if (temp != temp_old)
{
// 使用HAL库函数根据temp的值设置对应引脚状态 (假设高电平点亮)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, (temp & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 0 (PB12)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, (temp & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 1 (PB13)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, (temp & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 2 (PB14)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, (temp & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 3 (PB15)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, (temp & 0x10) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 4 (PD8)
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_9, (temp & 0x20) ? GPIO_PIN_SET : GPIO_PIN_RESET); // LED 5 (PD9)
temp_old = temp; // 更新记录的旧状态
}
}
/**
* @brief LED 显示处理函数 (主循环调用)
*/
void led_task(void)
{
led_disp(ucLed); // 调用led_disp函数更新LED状态
}
五,修改移植错误
移植编译后发现错误较多,其中很多是引脚未定义(因为模板中的引脚和我们用到的引脚不同,所以对照main.h中的宏定义进行引脚修改
虽然用CubeMX生成了引脚宏定义到main.h中,但是led_app.h没有引用main.h,所以接下来补充led_app.h
led_app.h
#ifndef LED_APP_H
#define LED_APP_H
#include "mydefine.h"
void led_task(void);
#endif
再次编译发现还有一个错误
这是因为led_task()函数定义了两次导致的,一次在调度器模板中,一次在led_app.c中
再次编译,发现还有错误:led_task未定义,但是在led_app.c文件中已经定义了,在led_app.h中也声明了,为什么呢?
双击跳转到错误,在调度器中,查看头文件只有调度器.h文件
跳进调度器头文件,这里只调用了mydefine.h文件
再跳进mydefine.h文件,这里没有led_app.h文件,所以报错,加上即可
六,下载验证
选择芯片型号
选择烧录器
配置烧录器
如果没有Flash Programming Algorithm(闪存编程算法)1,需要手动添加,如果不添加会出现如下警告
下载成功后,led没亮,检查发现main.c中忘记写调度器初始化和运行函数了(蓝桥杯定时器1初始化函数没有写进main.c,底层都打好了,检查半天😔)
加上两函数再编译,又报错了:调用一个函数,却没有事先对该函数进行声明或者定义时,编译器会进行隐式声明。就是没有引用调度器的头文件
加上头文件,再编译,无错误
Flash Programming Algorithm(闪存编程算法)是用于将程序代码或数据写入到微控制器(MCU)内部闪存(Flash Memory)中的一组指令和方法。不同厂商、不同型号的闪存芯片具有不同的电气特性、编程时序和命令集。闪存编程算法会针对具体的闪存芯片进行优化,确保能够正确地将数据写入到闪存中。所以针对不同的芯片,要选择不同的算法
而Load Flash Programming Algorithm就是将闪存编程算法从存储它的地方(可能是烧录工具的安装目录等)读取到内存中,让烧录工具可以调用这个算法去完成对目标芯片闪存的编程操作。 ↩︎