使用 STM32CubeMX 与 HAL 库实现 LED 流水灯及 Proteus 仿真

引言

在嵌入式开发学习过程中,LED 流水灯是一个经典的入门实验,类似于编程语言中的 "Hello World"。本实验将使用 STM32CubeMX 工具配置 STM32F103C8T6 单片机,通过 HAL 库编写控制程序,并在 Proteus 软件中进行仿真验证。通过这个实验,我们可以掌握 STM32 的 GPIO 配置、基本延时函数使用以及定时器中断的应用,为后续更复杂的项目打下基础。

一、准备工作

在开始实验前,我们需要准备以下工具和软件:

  1. STM32CubeMX:ST 公司推出的图形化配置工具,用于快速生成 STM32 初始化代码
  2. Keil MDK-ARM:ARM 架构的微控制器开发工具,用于编写、编译和调试程序
  3. Proteus:一款电子设计自动化软件,用于电路设计和仿真
  4. 基本的 C 语言编程知识和嵌入式开发概念

二、使用 STM32CubeMX 创建工程

2.1 新建工程并选择芯片

  1. 打开 STM32CubeMX 软件,点击 "New Project" 创建新工程
  2. 在弹出的芯片选择窗口中,搜索 "STM32F103C8",选择 "STM32F103C8T6" 型号
  3. 点击 "Start Project" 进入工程配置界面

2.2 配置系统时钟和调试接口

  1. 配置 SYS(系统):

    • 在左侧配置栏中找到 "SYS" 选项并点击
    • 在 "Debug" 下拉菜单中选择 "Serial Wire",这将启用 SWD 调试接口
    • 其他选项保持默认设置
  2. 配置 RCC(时钟控制):

    • 在左侧配置栏中找到 "RCC" 选项并点击
    • 在 "HSE Configuration" 中选择 "Crystal/Ceramic Resonator"(外部晶振)
    • 此时,Pinout 视图中会显示 HSE 对应的引脚被启用

2.3 配置 GPIO 引脚

本实验将使用三个 LED,分别连接到以下引脚:

  • LED1:GPIOB_PIN_9
  • LED2:GPIOA_PIN_15
  • LED3:GPIOC_PIN_13

配置步骤:

  1. 在 Pinout 视图中找到上述三个引脚
  2. 分别点击每个引脚,在弹出的菜单中选择 "GPIO_Output"
  3. 点击左侧 "GPIO" 选项,进入 GPIO 配置界面
  4. 对于每个已配置的引脚:
    • 在 "GPIO output level" 中选择 "High"(初始状态为高电平,LED 灭)
    • 在 "GPIO mode" 中选择 "Output Push-Pull"(推挽输出模式)
    • 在 "GPIO Pull-up/Pull-down" 中选择 "No pull-up and no pull-down"(无上下拉)
    • 在 "Maximum output speed" 中选择 "Low"(低速即可,节省功耗)

2.4 配置时钟树

  1. 点击界面上方的 "Clock Configuration" 进入时钟配置界面
  2. 在 "HSE" 处选择 "Crystal Oscillator"
  3. 将 PLL 锁相环的输入时钟设置为 HSE
  4. 配置 PLL 参数,使系统时钟 (SYSCLK) 达到 72MHz:
    • PLLMUL 选择 "x9"
    • APB1 Prescaler 选择 "/2"
  5. 确保各个外设时钟都已正确配置
  6. 点击 "OK" 保存配置

2.5 生成工程文件

  1. 点击界面上方的 "Project Manager" 进入工程设置界面

  2. 在 "Project" 选项卡中:

    • 设置 "Project Name"(工程名称)
    • 选择 "Project Location"(工程保存路径)
    • 在 "Toolchain/IDE" 中选择 "MDK-ARM"
    • 设置 "Toolchain Version" 为适合你的 Keil 版本
  3. 在 "Code Generator" 选项卡中:

    • 勾选 "Generate peripheral initialization as a pair of '.c/.h' files per peripheral"
    • 勾选 "Use micro LIB"
    • 其他选项保持默认
  4. 点击 "Generate Code" 生成工程文件

  5. 生成完成后,可以选择 "Open Project" 直接在 Keil 中打开工程

三、在 Keil 中编写流水灯代码

3.1 基本循环实现方式

打开生成的工程后,找到并打开 "main.c" 文件,在 main 函数的 while (1) 循环中添加以下代码:

c

运行

// 点亮LED1,其余LED熄灭
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);  // LED1亮(低电平有效)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);    // LED2灭
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);    // LED3灭
HAL_Delay(1000); // 延时1秒

// 点亮LED2,其余LED熄灭
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);      // LED1灭
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);  // LED2亮
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);    // LED3灭
HAL_Delay(1000); // 延时1秒

// 点亮LED3,其余LED熄灭
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);      // LED1灭
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);    // LED2灭
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);  // LED3亮
HAL_Delay(1000); // 延时1秒

代码说明:

  • HAL_GPIO_WritePin 函数用于设置 GPIO 引脚的电平状态
  • GPIO_PIN_RESET 表示输出低电平,GPIO_PIN_SET 表示输出高电平
  • 由于我们在 CubeMX 中设置了 LED 初始状态为高电平(灭),所以当输出低电平时 LED 点亮
  • HAL_Delay 函数用于产生毫秒级延时,参数为延时时间(毫秒)

3.2 编译生成 HEX 文件

  1. 点击 Keil 工具栏中的 "Build" 按钮进行编译
  2. 编译完成后,确认输出窗口显示 "0 Error (s), 0 Warning (s)"
  3. 配置生成 HEX 文件:
    • 点击 "Options for Target" 按钮(魔术棒图标)
    • 切换到 "Output" 选项卡
    • 勾选 "Create HEX File"
    • 点击 "OK" 保存设置
  4. 再次编译工程,此时会在工程目录的 "Debug" 文件夹下生成 HEX 文件

 

四、使用定时器中断实现流水灯

除了基本的循环方式,我们还可以使用定时器中断来实现流水灯效果,这种方式可以释放 CPU 资源,让其在延时期间处理其他任务。

4.1 在 CubeMX 中配置定时器

  1. 返回 STM32CubeMX 工程

  2. 在左侧配置栏中找到 "TIM6"(基本定时器)并点击

  3. 在 "Mode" 选项卡中:

    • 勾选 "Activated" 启用定时器
    • 设置 "Prescaler"(预分频器)为 71
    • 设置 "Counter Period"(自动重装载值)为 999
    • 这样配置后,定时器的中断周期为 (71+1)*(999+1)/72000000 = 1ms
  4. 在 "NVIC Settings" 选项卡中:

    • 勾选 "TIM6 global interrupt" 使能定时器中断
    • 可以设置适当的中断优先级
  5. 重新生成代码,更新到 Keil 工程中

4.2 编写中断相关代码

  1. 在 main.c 文件的顶部定义全局变量:

c

运行

// 全局变量
volatile uint8_t led_state = 0;    // LED状态变量,0-2分别代表三个LED
volatile uint32_t led_counter = 0; // 计数器,用于控制LED切换频率
  1. 在 main 函数中启动定时器中断:

c

运行

int main(void)
{
  // 初始化HAL库
  HAL_Init();
  
  // 初始化系统时钟
  SystemClock_Config();
  
  // 初始化所有配置的外设
  MX_GPIO_Init();
  MX_TIM6_Init();
  
  // 启动定时器中断
  HAL_TIM_Base_Start_IT(&htim6);
  
  // 主循环
  while (1)
  {
    // 主循环中可以添加其他任务
    // LED控制逻辑在中断中实现
  }
}
  1. 实现定时器中断回调函数:

c

运行

// 定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM6)  // 确认是TIM6产生的中断
  {
    led_counter++;  // 每1ms计数器加1
    
    // 每1000ms(1秒)切换一次LED状态
    if (led_counter >= 1000)
    {
      led_counter = 0;  // 重置计数器
      
      // 关闭所有LED
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);
      HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
      
      // 根据状态变量点亮对应的LED
      switch(led_state)
      {
        case 0:
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);  // 点亮LED1
          break;
        case 1:
          HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);  // 点亮LED2
          break;
        case 2:
          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);  // 点亮LED3
          break;
      }
      
      // 更新状态,循环切换
      led_state = (led_state + 1) % 3;
    }
  }
}

代码说明:

  • 定时器每 1ms 产生一次中断,在中断回调函数中计数器加 1
  • 当计数器达到 1000 时(即 1 秒后),切换 LED 状态
  • 使用 switch-case 语句根据状态变量控制不同的 LED 点亮
  • led_state 变量在 0、1、2 之间循环切换,实现流水灯效果

五、Proteus 仿真

5.1 创建 Proteus 仿真电路

  1. 打开 Proteus 软件,新建一个工程

  2. 在元件库中添加所需元件:

    • STM32F103C8T6:主控制器
    • LED-RED:红色 LED(3 个)
    • RES:电阻(3 个,220Ω 左右)
    • CRYSTAL:晶振(8MHz)
    • CAP:电容(若干,用于晶振和复位电路)
    • BUTTON:按钮(用于复位)
  3. 按照以下方式连接电路:

    • 将 STM32 的 PA15、PB9、PC13 引脚分别通过限流电阻连接到 LED 的阳极
    • LED 的阴极连接到 GND
    • 为 STM32 配置 8MHz 外部晶振电路
    • 配置复位电路(可以使用按钮实现手动复位)
    • 连接 VCC 和 GND,确保电源正常

5.2 加载程序并运行仿真

  1. 在 Proteus 中双击 STM32F103C8T6 芯片
  2. 在弹出的对话框中,点击 "Program File" 后的文件夹图标
  3. 选择之前在 Keil 中生成的 HEX 文件
  4. 点击 "OK" 完成设置
  5. 点击 Proteus 工具栏中的 "Run Simulation" 按钮开始仿真
  6. 观察 LED 的变化,应该看到三个 LED 按照 1 秒的间隔依次点亮和熄灭,形成流水灯效果

 

六、实验总结

通过本实验,我们学习了:

  1. 使用 STM32CubeMX 配置 STM32 单片机的基本方法,包括 GPIO、时钟和定时器
  2. 两种实现 LED 流水灯的编程方法:
    • 基本循环 + 延时函数的简单方法
    • 定时器中断的高效方法
  3. 在 Proteus 中搭建 STM32 仿真电路并进行程序验证

对比两种实现方法,基本循环方式代码简单直观,但在延时期间 CPU 处于阻塞状态,无法执行其他任务;而定时器中断方式可以让 CPU 在延时期间处理其他事务,提高了系统的效率和响应性,更适合实际项目应用。

这个实验虽然简单,但涵盖了嵌入式开发的基本流程和重要概念,掌握这些内容对于进一步学习更复杂的 STM32 应用具有重要意义

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐