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库的初始化函数,内含:

  1. HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);设置优先级
  2. HAL_InitTick(TICK_INT_PRIORITY);使用SysTick定时器
  3. 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进行配置后,代码框架又发生了什么变化

  1. main.c没有变化,main.h多出了一下的宏定义
    在这里插入图片描述
  2. 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,底层都打好了,检查半天😔)
在这里插入图片描述
加上两函数再编译,又报错了:调用一个函数,却没有事先对该函数进行声明或者定义时,编译器会进行隐式声明。就是没有引用调度器的头文件
在这里插入图片描述
加上头文件,再编译,无错误
在这里插入图片描述


  1. Flash Programming Algorithm(闪存编程算法)是用于将程序代码或数据写入到微控制器(MCU)内部闪存(Flash Memory)中的一组指令和方法。不同厂商、不同型号的闪存芯片具有不同的电气特性、编程时序和命令集。闪存编程算法会针对具体的闪存芯片进行优化,确保能够正确地将数据写入到闪存中。所以针对不同的芯片,要选择不同的算法
    而Load Flash Programming Algorithm就是将闪存编程算法从存储它的地方(可能是烧录工具的安装目录等)读取到内存中,让烧录工具可以调用这个算法去完成对目标芯片闪存的编程操作。 ↩︎

### FreeRTOS项目实例与STM32 F103 HAL库教程 在嵌入式系统开发中,FreeRTOS是一个广泛使用的轻量级实时操作系统(RTOS),适用于微控制器应用。对于基于STM32F103系列MCU的项目而言,利用HAL(Hardware Abstraction Layer)库可以简化硬件操作并提高代码可移植性。 #### 创建FreeRTOS工程框架 为了启动一个带有FreeRTOS支持的新项目,开发者通常会从官方提供的模板或者已有的例子出发。这些初始设置包含了必要的配置文件和源码结构来集成RTOS功能到应用程序里[^1]。 ```c // main.c entry point of the application using FreeRTOS and STM32 HAL Library #include "main.h" #include "stm32f1xx_hal.h" /* Private variables */ osThreadId defaultTaskHandle; int main(void){ /* Initialize all configured peripherals */ HAL_Init(); SystemClock_Config(); osKernelInitialize(); // Initialize CMSIS-RTOS // Create tasks here... defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL); osKernelStart(); // Start thread execution } ``` #### 配置FreeRTOS参数 当涉及到具体任务创建时,需要定义每个线程的任务函数以及优先级等属性。此外,在`FreeRTOSConfig.h`中还可以调整调度算法、队列长度以及其他重要的运行期行为选项。 #### 使用HAL API实现外设控制 通过调用由STMicroelectronics维护的标准固件包中的APIs(Application Programming Interfaces),能够轻松完成诸如GPIO读写、UART通信等功能模块的设计工作。这不仅减少了底层驱动程序编写的工作量,而且增强了系统的稳定性和安全性。 ```c void DefaultTaskFunction(void const * argument){ while(1){ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Toggle LED connected to PA5 pin osDelay(500); // Delay for half a second between toggles } } static void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* USER CODE BEGIN RTOS_THREADS */ osThreadDef(defaultTask, DefaultTaskFunction, osPriorityNormal, 0, configMINIMAL_STACK_SIZE); defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL); /* USER CODE END RTOS_THREADS */ } ``` #### 实现多任务并发处理机制 借助于FreeRTOS所提供的同步原语如信号量(semaphores)、互斥锁(mutexes) 和消息队列(message queues),可以在不同执行单元间安全有效地共享数据资源或协调事件触发逻辑。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值