STM32 C语言编程如何快速上手?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文

第一部分:核心概念与准备工作

在开始写代码之前,你需要理解几个核心概念。

什么是 STM32?

STM32 是意法半导体推出的基于 ARM Cortex-M 内核的 32 位微控制器系列,它功能强大、外设丰富、性价比高,是嵌入式开发领域非常主流的选择。

STM32 编程的核心思想:寄存器 vs. 库函数

直接操作 STM32 的寄存器(GPIO、USART、TIM 等)就像直接用汇编语言操作硬件,非常繁琐且容易出错,厂商和社区提供了更高级的抽象方式。

  • 寄存器操作:直接读写硬件寄存器,优点是代码效率高,缺点是开发慢、可读性差、移植性差。
  • 标准库/外设库:ST 官方提供的一套 C 语言库,将复杂的寄存器操作封装成简单的函数调用(如 GPIO_SetBits()),这是最经典的开发方式,学习它能让你深刻理解硬件工作原理。
  • HAL 库:ST 推出的现代硬件抽象层库,优点是跨系列(F1, F4, L4 等代码兼容性好)、代码结构清晰、错误处理机制完善,是目前 ST 官方主推的方式。
  • LL 库:介于寄存器和 HAL 之间的一套轻量级库,直接调用寄存器,但提供了更友好的函数接口,性能接近寄存器操作。
  • RTOS:实时操作系统,如 FreeRTOS,用于管理复杂的任务。

本教程将以最经典的 标准库 为例,因为它能让你最直观地理解 STM32 的工作原理,是学习其他所有方式的基础。

开发环境搭建

你需要两个主要软件:

  1. IDE (集成开发环境):用于编写、编译、调试代码。

    • Keil MDK (ARMCC/ARMCLANG):最传统、最稳定、支持最广的 STM32 开发工具,有免费版。
    • STM32CubeIDE:ST 官方推出的免费 IDE,基于 Eclipse 和 GCC,集成了 STM32CubeMX 工具,非常方便。
    • VS Code + PlatformIO:现代化的轻量级方案,插件丰富,跨平台。
  2. 芯片支持包

    • STM32CubeMX:ST 官方图形化配置工具,可以帮你自动生成初始化代码(支持 HAL/LL 库)。
    • STM32标准外设库:如果你选择标准库,需要单独下载这个库文件。

推荐新手路线

  • 硬件:一块 STM32F103C8T6 的“蓝丸”开发板(便宜、资料多)。
  • 软件:安装 STM32CubeIDESTM32CubeMX(它们通常捆绑安装)。

第二部分:点亮一个 LED(最经典的入门程序)

我们将以 STM32F103C8T6 为例,通过标准库实现让板载 LED 闪烁。

步骤 1:硬件分析

  • 查看你的开发板原理图,找到 LED 连接到了哪个引脚。
  • 对于常见的“蓝丸”板,LED 通常连接在 PC13 引脚。
  • STM32 的 GPIO 引脚可以配置为推挽输出、开漏输出等模式,点亮 LED,我们需要将 PC13 设置为推挽输出,然后输出低电平(因为大多数板子是 LED 低电平点亮)。

步骤 2:项目创建与代码编写(使用纯标准库,不依赖 CubeMX)

  1. 创建项目文件夹

    • 创建一个文件夹,STM32_LED_Blink
    • 在里面创建 user 文件夹(存放你的代码)和 Libraries 文件夹(存放标准库)。
  2. 添加标准库文件

    • 从 ST 官网下载标准库(STM32F10x_StdPeriph_Lib_V3.5.0)。
    • 将以下文件夹和文件复制到你的项目目录中:
      • Libraries/STM32F10x_StdPeriph_Driver:核心驱动库。
      • Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x:设备支持文件(如 stm32f10x.h, system_stm32f10x.c)。
      • Libraries/CMSIS/CM3/CoreSupport:CMSIS 核心文件。
  3. 编写代码: 在 user 文件夹下创建 main.cstm32f10x_it.c(中断文件,暂时不用,可以留空)。

    main.c 代码详解:

    /* 包含头文件 */
    #include "stm32f10x.h"       // STM32F10x 的寄存器定义和外设访问结构体
    #include "misc.h"             // 杂项函数(如 NVIC 配置)
    /* 函数声明 */
    void delay(volatile uint32_t count);
    int main(void) {
        /* 1. 使能 GPIOC 的时钟 */
        // STM32 的所有外设(如 GPIO, USART, TIM)在使用前都必须先开启其对应的时钟
        // APB2ENR 寄存器的第 4 位控制 GPIOC 的时钟
        RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // 使用位操作或 |= 来设置,不影响其他位
        /* 2. 配置 PC13 引脚为推挽输出,最大速度 50MHz */
        // GPIOC 的 CRL 寄存器控制低 8 位引脚 (PC0-PC7)
        // CRH 寄存器控制高 8 位引脚 (PC8-PC15)
        // PC13 是高 8 位,所以操作 CRH 寄存器
        // CRH 寄存器的每一位组 (4 bits) 控制一个引脚
        // 对于 PC13,它位于 CRH 的 [13*4 : 13*4+3] = [52:55] 位
        // 配置模式: 00=输入, 01=推挽输出, 10=开漏输出, 11=复用功能
        // 配置速度: 00=2MHz, 01=10MHz, 10=50MHz
        // 我们需要配置为 01 (推挽输出) 和 10 (50MHz),所以值为 0b1011 (0xB)
        // 先清零原来的设置 (0xFFFFFFF0)
        GPIOC->CRH &= ~(0xF << 4 * (13 - 8)); 
        // 再设置新的值 (0xB << 4 * (13 - 8))
        GPIOC->CRH |= (0xB << 4 * (13 - 8));
        /* 3. 主循环 */
        while (1) {
            /* 点亮 LED: 设置 PC13 为低电平 */
            // ODR 寄存器的第 13 位控制 PC13 的输出电平
            // 0: 低电平 (点亮), 1: 高电平 (熄灭)
            GPIOC->BSRR = GPIO_BSRR_BR13; // 使用 BSRR 寄存器可以原子性地置位(清零)引脚,非常安全
            delay(500000); // 延时
            /* 熄灭 LED: 设置 PC13 为高电平 */
            // 使用 BSRR 寄存器原子性地置位(置位)引脚
            GPIOC->BSRR = GPIO_BSRR_BS13;
            delay(500000); // 延时
        }
    }
    /* 简单的延时函数 */
    void delay(volatile uint32_t count) {
        volatile uint32_t i;
        for (i = 0; i < count; i++); // 循环空转,消耗 CPU 时间
        // volatile 关键字防止编译器优化掉这个空循环
    }

步骤 3:编译与烧录

  1. 编译:在 STM32CubeIDE 中创建一个 C/C++ Executable 项目,将上述文件添加到工程中,并配置好包含路径(Include Paths)指向你添加的库文件,然后编译,生成 .hex.elf 文件。
  2. 烧录:使用 ST-Link/V2 下载器将程序烧录到 STM32 芯片中,STM32CubeIDE 集成了这个功能。

如果一切顺利,你就能看到板载 LED 开始闪烁了!


第三部分:进阶学习与关键模块

点亮 LED 只是开始,STM32 的强大在于其丰富的外设。

串口通信

串口是单片机与 PC 或其他设备通信最常用的方式。

  • 核心步骤
    1. 使能时钟:使能 USART1 的时钟(通常是 APB2ENR 的第 14 位)和 GPIOA 的时钟(因为 USART1 的 TX/RX 通常在 PA9/PA10)。
    2. 配置 GPIO:将 PA9 配置为复用功能推挽输出,PA10 配置为浮空输入,它们的复用功能都是 USART1。
    3. 配置 USART:通过 USART1 的寄存器(如 CR1, CR2, BRR)设置波特率(如 115200)、数据位(8)、停止位(1)、校验位(无)等。
    4. 使能 USART:设置 UE 位使能 USART,设置 TERE 位使能发送和接收。
    5. 发送数据:向 DR (数据寄存器) 写入要发送的字符。
    6. 接收数据:检查 SR (状态寄存器) 的 RXNE (读数据寄存器非空) 标志位,如果置位,则从 DR 读取数据。

定时器

定时器用于精确定时、产生 PWM 波形、或作为外部事件计数器。

  • 核心步骤 (以 TIM2 为例)
    1. 使能时钟:使能 TIM2 的时钟(通常是 APB1ENR 的第 0 位)。
    2. 配置预分频器PSC 寄存器,它将来自总线的时钟频率进行分频。TIMx_CLK / (PSC + 1) 得到定时器计数时钟。
    3. 配置自动重装载值ARR 寄存器,计数器从 0 开始向上计数,当计数值等于 ARR 时,产生中断事件并清零。
    4. 计算定时时间定时时间 = ((PSC + 1) * (ARR + 1)) / TIMx_CLK
    5. 使能定时器:设置 CEN 位开始计数。
    6. 中断配置:如果需要定时器中断,还需配置 DIER 寄存器使能更新中断,并在 NVIC 中配置中断向量。

中断

中断是提高 CPU 效率的关键,当某个事件(如定时器溢出、串口收到数据)发生时,CPU 会暂停当前任务,去执行一个预先写好的中断服务函数

  • 核心步骤
    1. 配置外设:如上所述,配置好串口或定时器,并使能它们的中断。
    2. 配置 NVIC:NVIC 是 ARM Cortex-M 内核自带的中断控制器。
      • 使能对应的中断通道(如 USART1_IRQn)。
      • 设置中断优先级。
    3. 编写中断服务函数:在 stm32f10x_it.c 中,函数名必须是固定的(如 void USART1_IRQHandler(void))。
    4. 清除中断标志:在 ISR 中,必须手动清除导致中断的标志位,否则会一直进入中断。

第四部分:从标准库到 HAL 库的过渡

当你熟悉了标准库后,强烈建议学习 HAL 库,ST 已经不再大力推荐标准库了。

HAL 库的特点

  • 统一性HAL_GPIO_WritePin(), HAL_UART_Transmit() 等函数在不同系列的 STM32 上调用方式基本一致。
  • 初始化简化:使用 STM32CubeMX 图形化配置,一键生成初始化代码。
  • 错误处理:函数返回 HAL_StatusTypeDef 类型(HAL_OK, HAL_ERROR),便于错误检测。

用 HAL 库点亮 LED 的代码示例:

/* main.c */
#include "main.h" // CubeMX 生成的头文件
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
int main(void) {
  HAL_Init(); // 初始化 HAL 库
  SystemClock_Config(); // 配置系统时钟 (由 CubeMX 生成)
  MX_GPIO_Init();      // 初始化 GPIO (由 CubeMX 生成)
  while (1) {
    // 点亮 LED
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // RESET = 低电平
    HAL_Delay(500);
    // 熄灭 LED
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);   // SET = 高电平
    HAL_Delay(500);
  }
}
// 由 CubeMX 生成的 GPIO 初始化代码
static void MX_GPIO_Init(void) {
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  /* 使能 GPIOC 时钟 */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  /* 配置 PC13 */
  GPIO_InitStruct.Pin = GPIO_PIN_13;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出模式
  GPIO_InitStruct.Pull = GPIO_NOPULL;        // 无上拉下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;// 低速
  HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
// ... 其他由 CubeMX 生成的代码 ...

可以看到,HAL 库的代码更简洁,硬件配置的工作被交给了 CubeMX,开发者可以更专注于业务逻辑。


总结与学习路径

  1. 打好基础:熟悉 C 语言,特别是指针、结构体、位操作。
  2. 理解硬件:学习数字电路基础,了解 GPIO、UART、Timer 等外设的基本原理。
  3. 从标准库开始:亲手用标准库实现 LED 闪烁、串口打印、定时器中断,这个过程虽然繁琐,但能让你对 STM32 的寄存器和工作方式有最深刻的理解。
  4. 掌握工具:熟练使用 STM32CubeMX 进行引脚和时钟配置,这是现代 STM32 开发的必备技能。
  5. 转向 HAL 库:在理解了底层原理后,转向 HAL 库,提高开发效率和代码的可移植性。
  6. 探索高级主题:学习 DMA(直接内存访问,解放 CPU)、ADC(模数转换)、I2C/SPI(通信协议)、RTOS(实时操作系统)等。

STM32 的学习曲线相对陡峭,但只要你一步一个脚印,多动手实践,多看官方参考手册和原理图,很快就能上手并掌握它,祝你学习顺利!

-- 展开阅读全文 --
头像
dede递增序列号
« 上一篇 今天
先来先服务算法C语言如何实现?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]