FreeRTOS学习之路1:开发工具链验证:基于Cubemx实现按键控制LED灯(裸机)

一、课程目标

  1. 掌握STM32CubeMX工具链的基础使用

  2. 理解裸机开发中GPIO输入/输出的配置原理

  3. 实现按键控制LED的完整功能(按键按下点亮,松开熄灭)

  4. 掌握Keil MDK基础调试技巧

  5. 建立代码规范意识,为后续RTOS开发打基础


二、知识准备

  1. 裸机开发概念

    • 无操作系统,直接操作硬件寄存器

    • 代码运行在main()的超级循环中

    • 适合简单、实时性要求不高的场景

  2. CubeMX工具定位

    • ST官方图形化配置工具

    • 自动生成初始化代码(HAL/LL库)

    • 可视化配置时钟树、外设、中间件等

  3. GPIO工作模式

    模式适用场景
    Output Push-PullLED驱动
    Input Floating按键检测(无上下拉)
    Input Pull-Up/Down按键检测(内置电阻)

二、技术选型说明

技术栈选型理由
STM32CubeMX图形化配置工具,自动生成初始化代码,缩短开发周期
HAL库硬件抽象层降低开发难度,跨平台移植性强
Keil MDK行业主流IDE,支持STM32全系芯片调试
ST-Link调试器官方推荐调试工具,支持实时变量监控

三、实战步骤

1. CubeMX工程创建
  1. 芯片选型

    • 选择实际使用的STM32型号(STM32F103C8T6)

    • 建议使用主流开发板对应的芯片型号

  2. 时钟配置

    • 启用外部高速时钟(HSE)

    • SYS 选项卡勾选Serial Wire,不勾选可能会使得无法使用stlink或jlink无法下载。

    • RCC选项卡中将HSE设置为晶振。

    • 配置系统时钟为最大频率(如72MHz)

    • 验证APB总线分频比(决定GPIO速度)

    • 外设时钟的分配原理外设以设定的组别挂在0x0000 0000到0xFFFF FFFF的内存区域中。

  3. GPIO配置

    • LED引脚(PA5):

      • Mode: Output Push-Pull

      • Label: USER_LED

    • 按键引脚(PC13):

      • Mode: Input Floating

      • Label: USER_KEY

  4. 生成代码

    • Toolchain: MDK-ARM V5

    • 勾选Generate peripheral initialization as a pair of .c/.h files

  5. GPIO的两个输出HAL库函数:     

    1. HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) * 控制一个GPIO引脚输出高或者低电平

   2. HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) * 控制一个GPIO引脚电平翻转,调用一次电平翻转一次


2. 代码开发

 1. main.c 超级循环实现

// 包含HAL库头文件
#include "main.h"

// 定义按键状态检测宏(防抖动基础版)
#define KEY_PRESSED() (HAL_GPIO_ReadPin(USER_KEY_GPIO_Port, USER_KEY_Pin) == GPIO_PIN_RESET)

int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();

  while(1) {
    if(KEY_PRESSED()) {
      HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_SET); // 点亮LED
    } else {
      HAL_GPIO_WritePin(USER_LED_GPIO_Port, USER_LED_Pin, GPIO_PIN_RESET); // 熄灭LED
    }
    
    // 简单延时防止CPU跑满
    HAL_Delay(10); 
  }
}

2. 按键消抖优化(状态机版)

typedef enum {
  KEY_STATE_RELEASED,
  KEY_STATE_DEBOUNCE,
  KEY_STATE_PRESSED
} KeyState;

KeyState key_state = KEY_STATE_RELEASED;
uint32_t key_press_tick = 0;

void Key_Handler(void) {
  switch(key_state) {
    case KEY_STATE_RELEASED:
      if(KEY_PRESSED()) {
        key_press_tick = HAL_GetTick();
        key_state = KEY_STATE_DEBOUNCE;
      }
      break;
      
    case KEY_STATE_DEBOUNCE:
      if(HAL_GetTick() - key_press_tick > 20) { // 20ms消抖
        if(KEY_PRESSED()) {
          key_state = KEY_STATE_PRESSED;
          // 触发LED动作
          HAL_GPIO_TogglePin(USER_LED_GPIO_Port, USER_LED_Pin);
        } else {
          key_state = KEY_STATE_RELEASED;
        }
      }
      break;
      
    case KEY_STATE_PRESSED:
      if(!KEY_PRESSED()) {
        key_state = KEY_STATE_RELEASED;
      }
      break;
  }
}

// 在main循环中调用Key_Handler()

总结:

        1. 按键按下的瞬间引脚的电平不是马上从高电平变为低电平或者从低电平变成高电平; 而是有一个抖动;

        2. 可以使用加延时判断的办法消除这个抖动;

        3. 缺点:延时时间不好掌握,容易让程序对按键的反应过于灵敏或者迟钝; 


四、调试技巧

  1. Keil Debug基础操作

    • 断点设置:在关键代码行按F9

    • 查看寄存器:Peripherals → GPIO

    • 逻辑分析仪:

      // 添加测试引脚
      #define DEBUG_PIN  GPIO_PIN_0
      HAL_GPIO_TogglePin(GPIOA, DEBUG_PIN); // 在按键处理中插入
  2. 常见问题排查

    现象排查步骤
    LED不亮1. 检查GPIO模式是否为输出
    2. 测量引脚电压
    3. 确认LED电路极性
    按键检测不稳定1. 增加硬件消抖电路(10kΩ电阻+104电容)
    2. 优化软件消抖算法
    代码卡死1. 检查时钟配置是否正确
    2. 排查未初始化的外设

五、课后总结

1. 项目立项到交付的基本流程;

2. STM32CubeMX除了帮助我们初始化外设生成代码之外,还可以用来做什么?

3. STM32CubeMX中的Debug选项如果不设置的话会有什么后果?

4. 如何将STM32F103的系统时钟配置为最大值72MHz?

       关键配置点

  1. 时钟源:使用外部晶振(HSE,通常8MHz)
  2. PLL倍频:将HSE通过PLL倍频至72MHz(8MHz × 9 = 72MHz)
  3. 总线分频
    • AHB总线(HCLK)无分频(72MHz)
    • APB1总线(PCLK1)二分频(36MHz,不超过最大限制)
    • APB2总线(PCLK2)无分频(72MHz)
  4. Flash等待周期:设置为2个等待周期(72MHz需此配置)

5. 使用STM32CubeMX配置GPIO的步骤;

6. 使用STM32CubeMX配置UART/USART的步骤;

7. 使用STM32CubeMX生成工程之前,有哪些配置值得注意?

        1. 工程保存路径,不可有特殊字符,中文 

        2. 时钟源选择,考虑有无外部时钟,精度问题

        3. 时钟树分频与倍频系数选择,确保配置界面无红色警告

        4. 代码生成选项,勾选Generate peripheral initialization as a pair of .c/.h files,为每个外设生成独立文件,便于维护

        5.调试接口配置,在SYS中选择调试模式(如SWD需配置Serial Wire),否则无法通过ST-Link下载程序

        6. 中间件与RTOS集成,若需使用FreeRTOS或文件系统(如FATFS),需在Middleware中启用并配置任务堆栈等参数

        7. 生成后注意事项,用户代码需写在/* USER CODE BEGIN */和/* USER CODE END */之间,避免重新生成时被覆盖

        8.对于读取按键,有没有更好的方法?

读取按键的方法汇总(更新中)-优快云博客在main()循环中持续检测GPIO电平状态通过延时消抖或状态机处理抖动配置GPIO为中断触发模式(上升沿/下降沿/双边沿)在中断服务程序(ISR)中标记按键事件使用硬件定时器(如TIM)周期性触发中断在定时器中断中采样按键状态配合状态机实现消抖和动作识别定义按键状态(空闲、消抖、按下、释放等)通过状态转移处理长按、双击等复杂事件使用硬件电路消除抖动配合简单软件检测。 https://blog.youkuaiyun.com/weixin_45781584/article/details/145631231?fromshare=blogdetail&sharetype=blogdetail&sharerId=145631231&sharerefer=PC&sharesource=weixin_45781584&sharefrom=from_link

六、一些思考

1.为什么选择浮空输入模式而不是上拉输入?

2.HAL_Delay()是如何实现的?有什么缺点?如何不使用HAL_Delay()的情况下实现精确延时?

HAL_Delay原理+优化方案-优快云博客HAL_Delay() 是 STM32 HAL 库提供的阻塞式延时函数,其核心实现依赖,具体流程如下:// HAL 库关键代码片段// 空循环等待​// 系统滴答计时器(SysTick)的中断服务函数// 全局变量 uwTick 自增​// 获取当前系统时间戳:默认使用 1ms 中断周期(通过设置):通过while循环持续检查时间差,占用 CPU 资源__weak修饰符允许用户重写实现// 在中断服务函数中使用 HAL_Delay() 会导致灾难性后果// 将阻塞所有低优先级中断。 https://blog.youkuaiyun.com/weixin_45781584/article/details/145665121?spm=1001.2014.3001.5502

系统架构视角总结嵌入式延时方案-优快云博客文章浏览阅读67次,点赞2次,收藏2次。最近在开发一款智能穿戴设备,需要兼顾低功耗和实时响应。移植RTOS环境。涉及到的延时操作比较多:抽象延时接口,裸机下使用TIM3定时器+状态机非阻塞方案,RTOS环境下桥接到vTaskDelayUntil,空闲时段启用STOP模式+RTC唤醒。复盘项目的时候,站在系统架构的层面问自己,为什么要选择这个延时方案,回顾我学习的资料,发现延时这版块讲得都很零碎,缺乏宏观视角,正好今天从系统架构的视角来梳理一下。SysTick寄存器查询(HAL_Delay本质) https://blog.youkuaiyun.com/weixin_45781584/article/details/145632485?spm=1001.2014.3001.5502

3.如何测量GPIO翻转的最大频率(使用示波器或逻辑分析仪)

如何测量GPIO翻转的最大频率(用示波器和逻辑分析仪)-优快云博客首先,我们需要理解什么是GPIO翻转频率。GPIO是通用输入输出端口,翻转是指从高电平变到低电平,或者反过来。最大频率是指这个引脚在单位时间内能切换多少次状态。比如说,如果频率是1MHz,那每秒钟可以切换一百万次。需要确认在程序中已经将GPIO配置为最高速度模式,关闭任何可能限制速度的选项,比如施密特触发器的迟滞,或者输入滤波等。还要确定GPIO所在外设总线的预分配设置,确保频率是最大状态。示波器主要是用来观察电压随时间变化的波形,而逻辑分析仪则用来捕捉数字信号的状态变化,更适合分析多个信号线的时序。 https://blog.youkuaiyun.com/weixin_45781584/article/details/145665565?spm=1001.2014.3001.5502

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值