STM32 HAL库实战指南:从零开始构建嵌入式应用系统

STM32 HAL库实战指南:从零开始构建嵌入式应用系统

目录


前言:为什么选择HAL库

在嵌入式开发的世界里,STM32系列微控制器以其强大的性能和丰富的外设资源,成为了无数工程师的首选。然而,面对如此庞大的功能集合,如何高效地进行开发?STM32 HAL库(Hardware Abstraction Layer)应运而生,它为我们提供了一套统一、易用的API接口,让复杂的硬件操作变得简单直观。

HAL库的优势

1. 跨系列兼容性

HAL库为STM32全系列(F0/F1/F3/F4/F7/H7等)提供了统一的编程接口。这意味着:

  • 学习一套API,适用于多个系列
  • 代码移植成本低,易于维护
  • 团队协作更高效

2. 丰富的功能支持

HAL库不仅支持基本外设,还提供了:

  • 高级功能(如USB、以太网、CAN等)
  • 中间件支持(FreeRTOS、FatFS等)
  • 完善的错误处理机制

3. 强大的工具链支持

  • STM32CubeMX:图形化配置工具,自动生成初始化代码
  • STM32CubeIDE:集成开发环境,一站式开发
  • 丰富的例程:官方提供大量示例代码

本文档的学习路径

本文档采用"理论+实践"的方式,通过12个章节,从基础到高级,从理论到实战,带你全面掌握STM32 HAL库开发。每个章节都包含:

  • 原理讲解:深入理解底层机制
  • 代码示例:实际可运行的代码
  • 实战项目:完整的应用案例
  • 常见问题:避坑指南

让我们开始这段精彩的STM32 HAL库学习之旅吧!


第一章:STM32 HAL库基础架构深度解析

1.1 HAL库的整体架构

STM32 HAL库采用分层设计,从上到下分为:

复制代码
应用层(Application Layer)
    ↓
HAL库层(HAL Layer)
    ↓
外设驱动层(Peripheral Driver Layer)
    ↓
硬件抽象层(Hardware Abstraction Layer)
    ↓
硬件层(Hardware)

各层的作用:

  1. 应用层:用户编写的业务逻辑代码
  2. HAL库层:提供统一的API接口,封装硬件操作
  3. 外设驱动层:具体外设的驱动实现
  4. 硬件抽象层:寄存器级别的操作
  5. 硬件层:实际的STM32芯片

1.2 HAL库的核心文件结构

让我们看看HAL库的文件组织:

复制代码
STM32Cube_FW_F1_V1.8.0/
├── Drivers/
│   ├── STM32F1xx_HAL_Driver/
│   │   ├── Inc/
│   │   │   ├── stm32f1xx_hal.h          # HAL库主头文件
│   │   │   ├── stm32f1xx_hal_conf.h     # HAL库配置文件
│   │   │   ├── stm32f1xx_hal_gpio.h     # GPIO驱动
│   │   │   ├── stm32f1xx_hal_uart.h     # UART驱动
│   │   │   ├── stm32f1xx_hal_i2c.h      # I2C驱动
│   │   │   ├── stm32f1xx_hal_spi.h      # SPI驱动
│   │   │   └── ...                      # 其他外设驱动
│   │   └── Src/
│   │       ├── stm32f1xx_hal.c           # HAL库初始化
│   │       ├── stm32f1xx_hal_gpio.c     # GPIO实现
│   │       ├── stm32f1xx_hal_uart.c     # UART实现
│   │       └── ...                      # 其他外设实现
│   └── CMSIS/                           # CMSIS标准接口
└── Projects/                            # 示例项目

1.3 HAL库的初始化流程

每个HAL库项目都需要遵循标准的初始化流程:

c 复制代码
int main(void)
{
    // 1. HAL库初始化
    HAL_Init();
    
    // 2. 系统时钟配置
    SystemClock_Config();
    
    // 3. 外设初始化
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    // ... 其他外设初始化
    
    // 4. 应用主循环
    while (1)
    {
        // 用户代码
    }
}

详细解析:

1. HAL_Init()函数

这个函数做了哪些事情?

c 复制代码
HAL_StatusTypeDef HAL_Init(void)
{
    // 配置Flash预取指和等待周期
    __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
    __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_2);
    
    // 配置SysTick定时器(用于HAL_Delay)
    HAL_InitTick(TICK_INT_PRIORITY);
    
    // 配置低功耗模式
    HAL_MspInit();
    
    return HAL_OK;
}

关键点:

  • Flash预取指:提高代码执行效率
  • 等待周期:根据系统时钟配置
  • SysTick配置 :用于HAL_Delay()函数
  • MSP初始化:用户可在此添加自定义初始化代码

2. SystemClock_Config()函数

系统时钟配置是STM32开发的第一步,也是最关键的一步:

c 复制代码
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    // 配置HSE(外部高速时钟)
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;  // 8MHz * 9 = 72MHz
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
    
    // 配置系统时钟
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK 
                                 | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;      // AHB = 72MHz
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;       // APB1 = 36MHz
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;      // APB2 = 72MHz
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}

时钟树解析:

复制代码
外部晶振(8MHz)
    ↓
HSE
    ↓
PLL (×9)
    ↓
系统时钟(72MHz)
    ├── AHB总线(72MHz)
    │   ├── CPU时钟(72MHz)
    │   ├── GPIO时钟(72MHz)
    │   └── DMA时钟(72MHz)
    ├── APB1总线(36MHz)
    │   ├── USART2/3时钟(36MHz)
    │   ├── I2C1/2时钟(36MHz)
    │   └── TIM2/3/4时钟(72MHz, 因为APB1有×2)
    └── APB2总线(72MHz)
        ├── USART1时钟(72MHz)
        ├── SPI1时钟(72MHz)
        └── ADC1时钟(72MHz)

为什么需要这样配置?

  • 性能优化:不同外设需要不同的时钟频率
  • 功耗管理:可以动态调整时钟,降低功耗
  • 稳定性:合理的时钟配置保证系统稳定运行

1.4 HAL库的错误处理机制

HAL库提供了完善的错误处理机制:

c 复制代码
typedef enum
{
    HAL_OK       = 0x00U,  // 操作成功
    HAL_ERROR    = 0x01U,  // 一般错误
    HAL_BUSY     = 0x02U,  // 外设忙
    HAL_TIMEOUT  = 0x03U   // 超时
} HAL_StatusTypeDef;

使用示例:

c 复制代码
HAL_StatusTypeDef status;

// 发送数据
status = HAL_UART_Transmit(&huart1, data, size, timeout);
if (status != HAL_OK)
{
    // 错误处理
    Error_Handler();
}

错误回调函数:

HAL库提供了错误回调机制,可以在错误发生时自动调用:

c 复制代码
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        // USART1错误处理
        // 可以在这里重新初始化或记录错误
    }
}

1.5 HAL库的配置选项

stm32f1xx_hal_conf.h文件中,可以配置HAL库的各种选项:

c 复制代码
// 使能/禁用外设模块
#define HAL_MODULE_ENABLED
#define HAL_GPIO_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED
#define HAL_I2C_MODULE_ENABLED
// ... 其他模块

// 配置SysTick
#define TICK_INT_PRIORITY    0U

// 配置断言
#define USE_FULL_ASSERT      1U

优化建议:

  • 只使能需要的外设模块,减少代码体积
  • 合理配置中断优先级
  • 在调试时启用断言,发布时禁用

第二章:时钟系统:嵌入式系统的脉搏

2.1 时钟系统的重要性

时钟系统是STM32的"心脏",它决定了:

  • 系统运行速度:CPU执行指令的速度
  • 外设工作频率:各个外设的时钟频率
  • 功耗水平:时钟频率直接影响功耗
  • 系统稳定性:合理的时钟配置保证稳定运行

2.2 STM32F103的时钟源

STM32F103有多个时钟源:

复制代码
时钟源选择
├── HSI (内部高速时钟) - 8MHz,精度±1%
├── HSE (外部高速时钟) - 4-16MHz,精度高
├── LSI (内部低速时钟) - 40kHz,用于看门狗
└── LSE (外部低速时钟) - 32.768kHz,用于RTC

各时钟源的特点:

时钟源 频率 精度 用途 功耗
HSI 8MHz ±1% 系统时钟(备用)
HSE 4-16MHz 系统时钟(推荐)
LSI 40kHz 看门狗 极低
LSE 32.768kHz RTC 极低

2.3 PLL锁相环:频率倍增器

PLL(Phase Locked Loop)可以将输入时钟倍频,生成更高的系统时钟:

复制代码
输入时钟 → PLL → 输出时钟
8MHz    → ×9  → 72MHz

PLL配置示例:

c 复制代码
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    
    // 配置HSE为8MHz外部晶振
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    
    // 配置PLL
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;  // 8MHz × 9 = 72MHz
    
    // 注意:PLL输出频率不能超过72MHz(STM32F103)
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
}

PLL倍频系数表(STM32F103):

PLLMUL值 倍频系数 8MHz输入输出 最大频率
RCC_PLL_MUL_2 ×2 16MHz 72MHz
RCC_PLL_MUL_3 ×3 24MHz 72MHz
... ... ... ...
RCC_PLL_MUL_9 ×9 72MHz 72MHz
RCC_PLL_MUL_10 ×10 80MHz ❌ 超频

2.4 时钟树:理解时钟分配

STM32的时钟树非常复杂,让我们简化理解:

复制代码
                    HSE(8MHz)
                       ↓
                    PLL(×9)
                       ↓
              系统时钟(72MHz)
                       ↓
        ┌──────────────┼──────────────┐
        ↓              ↓              ↓
    AHB总线        APB1总线        APB2总线
    (72MHz)        (36MHz)         (72MHz)
        │              │              │
        ├─ CPU         ├─ USART2      ├─ USART1
        ├─ GPIO        ├─ I2C1        ├─ SPI1
        ├─ DMA         ├─ TIM2        ├─ ADC1
        └─ ...         └─ ...         └─ ...

重要规则:

  1. APB1时钟限制:最大36MHz
  2. APB2时钟限制:最大72MHz
  3. 定时器时钟:如果APB分频系数>1,定时器时钟=APB时钟×2

2.5 动态时钟配置实战

在实际项目中,我们可能需要动态调整时钟:

c 复制代码
// 降低系统时钟以节省功耗
void SystemClock_Reduce(void)
{
    // 切换到HSI(8MHz)
    __HAL_RCC_PLL_DISABLE();
    __HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_HSI);
    
    // 等待切换完成
    while(__HAL_RCC_GET_SYSCLK_SOURCE() != RCC_SYSCLKSOURCE_HSI);
}

// 恢复系统时钟
void SystemClock_Restore(void)
{
    // 重新配置PLL
    SystemClock_Config();
}

2.6 时钟配置常见问题

问题1:系统无法启动

原因:

  • HSE晶振未连接或损坏
  • PLL配置错误导致超频
  • Flash等待周期配置错误

解决:

c 复制代码
// 检查HSE是否就绪
if (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY))
{
    // HSE正常
}
else
{
    // HSE异常,切换到HSI
    __HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_HSI);
}

问题2:外设时钟未使能

原因:

  • 忘记使能外设时钟

解决:

c 复制代码
// 使能USART1时钟
__HAL_RCC_USART1_CLK_ENABLE();

// 使能GPIOA时钟
__HAL_RCC_GPIOA_CLK_ENABLE();

问题3:定时器频率不对

原因:

  • APB分频导致定时器时钟×2

解决:

c 复制代码
// 计算实际定时器时钟
uint32_t GetTimerClock(TIM_TypeDef *TIMx)
{
    uint32_t pclk;
    if (TIMx == TIM1 || TIMx == TIM8 || TIMx == TIM9 || 
        TIMx == TIM10 || TIMx == TIM11)
    {
        // APB2定时器
        pclk = HAL_RCC_GetPCLK2Freq();
    }
    else
    {
        // APB1定时器
        pclk = HAL_RCC_GetPCLK1Freq();
    }
    
    // 如果APB分频>1,定时器时钟×2
    if ((RCC->CFGR & RCC_CFGR_PPRE1) >> 8 > 1)
    {
        pclk *= 2;
    }
    
    return pclk;
}

第三章:GPIO编程:点亮你的第一个LED

3.1 GPIO基础概念

GPIO(General Purpose Input/Output)是STM32最基础也是最重要的外设。每个GPIO引脚都可以配置为:

  • 输入模式:读取外部信号
  • 输出模式:驱动外部设备
  • 复用功能:作为外设的引脚(如UART、SPI等)
  • 模拟功能:作为ADC输入

3.2 GPIO工作模式详解

STM32F103的GPIO有8种工作模式:

c 复制代码
typedef enum
{
    GPIO_MODE_INPUT = 0x00000000U,           // 输入模式
    GPIO_MODE_OUTPUT_PP = 0x00000001U,       // 推挽输出
    GPIO_MODE_OUTPUT_OD = 0x00000002U,       // 开漏输出
    GPIO_MODE_AF_PP = 0x00000008U,           // 复用推挽
    GPIO_MODE_AF_OD = 0x00000010U,           // 复用开漏
    GPIO_MODE_ANALOG = 0x00000003U,          // 模拟模式
    GPIO_MODE_IT_RISING = 0x10110000U,       // 上升沿中断
    GPIO_MODE_IT_FALLING = 0x10210000U,      // 下降沿中断
    GPIO_MODE_IT_RISING_FALLING = 0x10310000U // 双边沿中断
} GPIO_ModeTypeDef;

各模式的应用场景:

模式 特点 应用场景
输入浮空 高阻态,易受干扰 不推荐使用
输入上拉 内部上拉电阻 按键输入
输入下拉 内部下拉电阻 按键输入
模拟输入 高阻态 ADC采样
推挽输出 强驱动能力 LED、蜂鸣器
开漏输出 需要外部上拉 I2C总线
复用推挽 外设控制 UART、SPI
复用开漏 外设控制 I2C

3.3 GPIO配置实战:LED闪烁

让我们通过一个完整的LED闪烁项目来学习GPIO:

硬件连接:

复制代码
LED正极 → 限流电阻(220Ω) → STM32 PA5
LED负极 → GND

代码实现:

c 复制代码
// main.h
#include "stm32f1xx_hal.h"

// 函数声明
void SystemClock_Config(void);
void Error_Handler(void);

// main.c
int main(void)
{
    // HAL库初始化
    HAL_Init();
    SystemClock_Config();
    
    // GPIO初始化
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置PA5为推挽输出
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 主循环:LED闪烁
    while (1)
    {
        // LED亮
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
        HAL_Delay(500);
        
        // LED灭
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
        HAL_Delay(500);
    }
}

代码解析:

  1. 时钟使能__HAL_RCC_GPIOA_CLK_ENABLE() - 必须使能GPIO时钟才能使用
  2. 引脚选择GPIO_PIN_5 - 选择PA5引脚
  3. 模式配置GPIO_MODE_OUTPUT_PP - 推挽输出模式
  4. 速度配置GPIO_SPEED_FREQ_LOW - 低速(LED不需要高速)

3.4 GPIO高级应用:按键输入

按键输入是GPIO的另一个重要应用:

硬件连接:

复制代码
按键一端 → STM32 PA0
按键另一端 → GND
PA0内部上拉 → 3.3V

代码实现:

c 复制代码
// 按键初始化
void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置PA0为输入上拉模式
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;  // 上拉,按键按下为低电平
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 按键扫描(带消抖)
uint8_t Key_Scan(void)
{
    static uint8_t key_state = 1;  // 按键状态
    static uint32_t key_time = 0;  // 按键时间
    
    uint8_t key_press = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
    
    if (key_press == 0 && key_state == 1)  // 检测到按下
    {
        if (HAL_GetTick() - key_time > 20)  // 消抖20ms
        {
            key_state = 0;
            key_time = HAL_GetTick();
            return 1;  // 返回按键按下
        }
    }
    else if (key_press == 1 && key_state == 0)  // 检测到释放
    {
        key_state = 1;
        key_time = HAL_GetTick();
    }
    
    return 0;
}

// 主循环中使用
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    Key_Init();
    
    while (1)
    {
        if (Key_Scan())
        {
            // 按键按下,执行操作
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  // 切换LED状态
        }
    }
}

按键消抖原理:

机械按键在按下和释放时会产生抖动,需要软件消抖:

复制代码
时间轴 →
按键状态: 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 1 1
          ↑抖动↑     ↑稳定按下↑        ↑抖动↑
          
消抖后:   1           0                 1

3.5 GPIO位操作:高效控制

HAL库提供了位操作函数,可以高效地控制单个引脚:

c 复制代码
// 设置引脚为高电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);

// 设置引脚为低电平
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);

// 读取引脚状态
GPIO_PinState state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_5);

// 翻转引脚状态
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

// 锁定引脚配置(防止意外修改)
HAL_GPIO_LockPin(GPIOA, GPIO_PIN_5);

性能对比:

方法 执行时间 代码大小
HAL库函数 ~10个时钟周期 较大
寄存器操作 ~2个时钟周期
位带操作 ~1个时钟周期

寄存器直接操作(高级用法):

c 复制代码
// 直接操作寄存器,速度最快
GPIOA->BSRR = GPIO_PIN_5;        // 置位
GPIOA->BRR = GPIO_PIN_5;         // 复位
GPIOA->ODR ^= GPIO_PIN_5;        // 翻转
uint8_t state = (GPIOA->IDR >> 5) & 0x01;  // 读取

3.6 GPIO复用功能:外设引脚配置

当GPIO用作外设功能时,需要配置为复用模式:

c 复制代码
// 配置PA9为USART1_TX(复用推挽输出)
GPIO_InitTypeDef GPIO_InitStruct = {0};

GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      // 复用推挽
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速(串口需要)
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

STM32F103引脚复用表(部分):

引脚 功能1 功能2 功能3
PA9 GPIO USART1_TX TIM1_CH2
PA10 GPIO USART1_RX TIM1_CH3
PB6 GPIO I2C1_SCL TIM4_CH1
PB7 GPIO I2C1_SDA TIM4_CH2
PB10 GPIO USART3_TX I2C2_SCL
PB11 GPIO USART3_RX I2C2_SDA

3.7 GPIO实战项目:流水灯

让我们实现一个经典的流水灯项目:

c 复制代码
// 定义LED引脚
#define LED1_PIN GPIO_PIN_5
#define LED2_PIN GPIO_PIN_6
#define LED3_PIN GPIO_PIN_7
#define LED_PORT GPIOA

// LED初始化
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitStruct.Pin = LED1_PIN | LED2_PIN | LED3_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct);
    
    // 初始状态:全部熄灭
    HAL_GPIO_WritePin(LED_PORT, LED1_PIN | LED2_PIN | LED3_PIN, GPIO_PIN_RESET);
}

// 流水灯效果
void LED_Flow(void)
{
    // LED1亮
    HAL_GPIO_WritePin(LED_PORT, LED1_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_PORT, LED2_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_PORT, LED3_PIN, GPIO_PIN_RESET);
    HAL_Delay(200);
    
    // LED2亮
    HAL_GPIO_WritePin(LED_PORT, LED1_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_PORT, LED2_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_PORT, LED3_PIN, GPIO_PIN_RESET);
    HAL_Delay(200);
    
    // LED3亮
    HAL_GPIO_WritePin(LED_PORT, LED1_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_PORT, LED2_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_PORT, LED3_PIN, GPIO_PIN_SET);
    HAL_Delay(200);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    LED_Init();
    
    while (1)
    {
        LED_Flow();
    }
}

第四章:中断系统:让系统更智能

4.1 中断系统概述

中断是嵌入式系统的核心机制之一,它允许CPU在执行主程序时,响应外部事件或内部异常。

中断的优势:

  • 实时响应:立即响应外部事件
  • 提高效率:避免轮询,节省CPU资源
  • 多任务处理:看似同时处理多个任务

4.2 STM32中断优先级

STM32使用NVIC(Nested Vectored Interrupt Controller)管理中断:

优先级分组:

c 复制代码
// 优先级分组配置(在HAL_Init中)
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

// 4位优先级分组:
// - 抢占优先级:0-15(高优先级可以打断低优先级)
// - 子优先级:0-15(相同抢占优先级时,子优先级高的先执行)

优先级配置示例:

c 复制代码
// 配置USART1中断优先级
HAL_NVIC_SetPriority(USART1_IRQn, 2, 0);  // 抢占优先级2,子优先级0
HAL_NVIC_EnableIRQ(USART1_IRQn);          // 使能中断

4.3 GPIO外部中断实战

GPIO外部中断是最常用的中断类型:

硬件连接:

复制代码
按键 → PA0(外部中断)

代码实现:

c 复制代码
// GPIO外部中断初始化
void EXTI_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIOA时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置PA0为输入上拉,下降沿触发中断
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿中断
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 配置中断优先级
    HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
}

// 外部中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0)
    {
        // 按键按下处理
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  // 切换LED
    }
}

// 中断服务函数(在stm32f1xx_it.c中)
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

中断触发模式:

c 复制代码
// 上升沿触发
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;

// 下降沿触发
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;

// 双边沿触发
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;

4.4 串口中断:高效的数据接收

串口中断可以高效地接收数据,避免丢失:

c 复制代码
// 串口中断接收初始化
UART_HandleTypeDef huart1;
uint8_t uart_rx_buffer[256];
uint8_t uart_rx_index = 0;

void UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    HAL_UART_Init(&huart1);
    
    // 启动接收中断
    HAL_UART_Receive_IT(&huart1, &uart_rx_buffer[uart_rx_index], 1);
    
    // 使能中断
    HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
}

// 串口接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        // 处理接收到的数据
        if (uart_rx_buffer[uart_rx_index] == '\n')
        {
            // 收到完整的一行数据
            uart_rx_buffer[uart_rx_index] = '\0';
            ProcessCommand((char*)uart_rx_buffer);
            uart_rx_index = 0;
        }
        else
        {
            uart_rx_index++;
            if (uart_rx_index >= 255) uart_rx_index = 0;
        }
        
        // 继续接收下一个字节
        HAL_UART_Receive_IT(&huart1, &uart_rx_buffer[uart_rx_index], 1);
    }
}

4.5 中断嵌套与优先级管理

中断嵌套规则:

复制代码
主程序(优先级最低)
    ↓
中断A(抢占优先级2)
    ↓
中断B(抢占优先级1)← 可以打断中断A
    ↓
中断C(抢占优先级0)← 可以打断中断B

最佳实践:

  1. 关键中断设置高优先级:如看门狗、系统错误
  2. 通信中断设置中优先级:如UART、SPI
  3. 用户中断设置低优先级:如按键、LED
c 复制代码
// 系统关键中断(最高优先级)
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);

// 通信中断(中等优先级)
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_SetPriority(SPI1_IRQn, 1, 1);

// 用户中断(低优先级)
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);

第五章:定时器应用:精准的时间控制

5.1 定时器概述

STM32F103有多种定时器:

  • 高级定时器:TIM1、TIM8(带死区控制,用于电机控制)
  • 通用定时器:TIM2-TIM5(最常用)
  • 基本定时器:TIM6、TIM7(用于DAC触发)
  • SysTick:系统滴答定时器(用于HAL_Delay)

5.2 定时器工作原理

定时器的核心是计数器:

复制代码
时钟源 → 预分频器 → 计数器 → 比较/捕获
72MHz  →  /7200   → 0-9999 → 触发中断/输出PWM

定时器计算公式:

复制代码
定时时间 = (ARR + 1) × (PSC + 1) / 定时器时钟频率

例如:
定时器时钟 = 72MHz
PSC = 7199  (分频7200)
ARR = 9999  (计数值10000)
定时时间 = 10000 × 7200 / 72000000 = 1秒

5.3 定时器中断:精准延时

使用定时器中断实现精准的定时功能:

c 复制代码
TIM_HandleTypeDef htim2;

// 定时器初始化(1ms中断)
void TIM2_Init(void)
{
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 7200 - 1;      // 72MHz / 7200 = 10kHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 10 - 1;            // 10kHz / 10 = 1kHz (1ms)
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);
    
    // 使能定时器中断
    HAL_TIM_Base_Start_IT(&htim2);
    
    // 配置中断优先级
    HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
}

// 定时器中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        // 1ms定时任务
        static uint32_t counter = 0;
        counter++;
        
        if (counter >= 1000)  // 1秒
        {
            counter = 0;
            // 执行1秒定时任务
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        }
    }
}

5.4 PWM输出:控制电机和LED亮度

PWM(Pulse Width Modulation)是定时器的重要应用:

c 复制代码
TIM_HandleTypeDef htim3;
TIM_OC_InitTypeDef sConfigOC = {0};

// PWM初始化(通道1,频率1kHz)
void PWM_Init(void)
{
    // 定时器基础配置
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 72 - 1;        // 72MHz / 72 = 1MHz
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 1000 - 1;          // 1MHz / 1000 = 1kHz
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim3);
    
    // PWM通道配置
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 500;                 // 占空比50%
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
    
    // 启动PWM输出
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}

// 改变PWM占空比
void PWM_SetDuty(uint16_t duty)  // duty: 0-1000
{
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty);
}

// LED呼吸灯效果
void LED_Breathing(void)
{
    static uint16_t duty = 0;
    static int8_t direction = 1;
    
    duty += direction * 10;
    
    if (duty >= 1000)
    {
        duty = 1000;
        direction = -1;
    }
    else if (duty <= 0)
    {
        duty = 0;
        direction = 1;
    }
    
    PWM_SetDuty(duty);
    HAL_Delay(10);
}

PWM波形:

复制代码
占空比50%:
    ┌─────┐     ┌─────┐
    │     │     │     │
────┘     └─────┘     └─────

占空比25%:
    ┌──┐         ┌──┐
    │  │         │  │
────┘  └─────────┘  └─────

5.5 输入捕获:测量频率和脉宽

输入捕获可以测量外部信号的频率和脉宽:

c 复制代码
TIM_HandleTypeDef htim4;
uint32_t capture_value1 = 0;
uint32_t capture_value2 = 0;
uint32_t frequency = 0;

// 输入捕获初始化
void InputCapture_Init(void)
{
    htim4.Instance = TIM4;
    htim4.Init.Prescaler = 72 - 1;         // 72MHz / 72 = 1MHz
    htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim4.Init.Period = 0xFFFF;            // 16位最大值
    htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_IC_Init(&htim4);
    
    // 配置输入捕获通道
    TIM_IC_InitTypeDef sConfigIC;
    sConfigIC.ICFilter = 0;
    sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    HAL_TIM_IC_ConfigChannel(&htim4, &sConfigIC, TIM_CHANNEL_1);
    
    // 启动输入捕获
    HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_1);
    
    HAL_NVIC_SetPriority(TIM4_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM4_IRQn);
}

// 输入捕获回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM4)
    {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
        {
            capture_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            
            if (capture_value2 > capture_value1)
            {
                // 计算频率(单位:Hz)
                frequency = 1000000 / (capture_value2 - capture_value1);
            }
            
            capture_value1 = capture_value2;
        }
    }
}

第六章:串口通信:设备间的桥梁

6.1 串口通信基础

UART(Universal Asynchronous Receiver/Transmitter)是最常用的串行通信接口。

串口通信参数:

  • 波特率:数据传输速度(如115200bps)
  • 数据位:5-9位(通常8位)
  • 停止位:1或2位
  • 校验位:无、奇、偶

6.2 串口初始化与配置

c 复制代码
UART_HandleTypeDef huart1;

// 串口初始化
void UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    HAL_UART_Init(&huart1);
}

// 串口MSP初始化(配置GPIO)
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    if(huart->Instance == USART1)
    {
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        // PA9: USART1_TX
        GPIO_InitStruct.Pin = GPIO_PIN_9;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // PA10: USART1_RX
        GPIO_InitStruct.Pin = GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
    }
}

6.3 串口发送数据

阻塞方式发送:

c 复制代码
// 发送字符串
void UART_SendString(char* str)
{
    HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), HAL_MAX_DELAY);
}

// 发送格式化字符串
void UART_Printf(const char* format, ...)
{
    char buffer[256];
    va_list args;
    va_start(args, format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
    HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY);
}

非阻塞方式发送:

c 复制代码
// 启动发送
HAL_UART_Transmit_IT(&huart1, data, size);

// 发送完成回调
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        // 发送完成,可以继续发送下一批数据
    }
}

6.4 串口接收数据

阻塞方式接收:

c 复制代码
uint8_t rx_buffer[256];
HAL_UART_Receive(&huart1, rx_buffer, 256, 5000);  // 5秒超时

中断方式接收(推荐):

c 复制代码
uint8_t uart_rx_buffer[256];
uint16_t uart_rx_index = 0;

// 启动接收
HAL_UART_Receive_IT(&huart1, &uart_rx_buffer[uart_rx_index], 1);

// 接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        if (uart_rx_buffer[uart_rx_index] == '\n')
        {
            // 收到完整的一行
            uart_rx_buffer[uart_rx_index] = '\0';
            ProcessCommand((char*)uart_rx_buffer);
            uart_rx_index = 0;
        }
        else
        {
            uart_rx_index++;
        }
        
        // 继续接收
        HAL_UART_Receive_IT(&huart1, &uart_rx_buffer[uart_rx_index], 1);
    }
}

6.5 串口通信实战:命令解析器

实现一个简单的命令解析器:

c 复制代码
typedef struct {
    char* cmd;
    void (*handler)(char* args);
} Command_t;

// 命令表
Command_t commands[] = {
    {"LED_ON", CMD_LED_On},
    {"LED_OFF", CMD_LED_Off},
    {"GET_TEMP", CMD_GetTemp},
    {NULL, NULL}
};

// 命令处理
void ProcessCommand(char* cmd_line)
{
    char cmd[32];
    char args[64];
    
    // 解析命令和参数
    sscanf(cmd_line, "%s %s", cmd, args);
    
    // 查找命令
    for (int i = 0; commands[i].cmd != NULL; i++)
    {
        if (strcmp(cmd, commands[i].cmd) == 0)
        {
            commands[i].handler(args);
            return;
        }
    }
    
    // 未找到命令
    UART_SendString("Unknown command\r\n");
}

// 命令处理函数
void CMD_LED_On(char* args)
{
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
    UART_SendString("LED ON\r\n");
}

void CMD_LED_Off(char* args)
{
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
    UART_SendString("LED OFF\r\n");
}

第七章:I2C总线:多设备协同的纽带

7.1 I2C总线基础

I2C(Inter-Integrated Circuit)是一种两线制串行总线:

  • SDA:数据线
  • SCL:时钟线

I2C特点:

  • 多主多从架构
  • 7位或10位地址
  • 标准模式100kHz,快速模式400kHz
  • 需要上拉电阻(通常4.7kΩ)

7.2 I2C初始化配置

c 复制代码
I2C_HandleTypeDef hi2c1;

// I2C初始化
void I2C_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;           // 100kHz
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    HAL_I2C_Init(&hi2c1);
}

// I2C MSP初始化
void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    if(hi2c->Instance == I2C1)
    {
        __HAL_RCC_GPIOB_CLK_ENABLE();
        __HAL_RCC_I2C1_CLK_ENABLE();
        
        // PB6: I2C1_SCL
        // PB7: I2C1_SDA
        GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    }
}

7.3 I2C读写操作

写入数据:

c 复制代码
// 向从设备写入数据
uint8_t data[2] = {0x00, 0x55};
HAL_I2C_Master_Transmit(&hi2c1, 0xA0, data, 2, 1000);

读取数据:

c 复制代码
// 从从设备读取数据
uint8_t data[4];
HAL_I2C_Master_Receive(&hi2c1, 0xA0, data, 4, 1000);

写入后读取(常用):

c 复制代码
// 先写入寄存器地址,再读取数据
uint8_t reg_addr = 0x00;
uint8_t data;
HAL_I2C_Mem_Read(&hi2c1, 0xA0, reg_addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);

7.4 I2C实战:OLED显示屏驱动

以SSD1306 OLED显示屏为例:

c 复制代码
#define OLED_ADDRESS 0x3C

// OLED初始化
void OLED_Init(void)
{
    // 初始化序列
    uint8_t init_cmd[] = {
        0xAE,  // 关闭显示
        0xD5, 0x80,  // 设置时钟分频
        0xA8, 0x3F,  // 设置多路复用
        0xD3, 0x00,  // 设置显示偏移
        0x40,  // 设置起始行
        0x8D, 0x14,  // 电荷泵使能
        0x20, 0x00,  // 内存地址模式
        0xA1,  // 段重映射
        0xC8,  // COM扫描方向
        0xDA, 0x12,  // COM引脚配置
        0x81, 0xCF,  // 对比度设置
        0xD9, 0xF1,  // 预充电周期
        0xDB, 0x40,  // VCOMH设置
        0xA4,  // 全部显示开启
        0xA6,  // 正常显示
        0xAF   // 开启显示
    };
    
    // 发送初始化命令
    for (int i = 0; i < sizeof(init_cmd); i++)
    {
        OLED_WriteCommand(init_cmd[i]);
    }
}

// 写入命令
void OLED_WriteCommand(uint8_t cmd)
{
    uint8_t data[2] = {0x00, cmd};  // 0x00表示命令
    HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDRESS, data, 2, 1000);
}

// 写入数据
void OLED_WriteData(uint8_t data)
{
    uint8_t buf[2] = {0x40, data};  // 0x40表示数据
    HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDRESS, buf, 2, 1000);
}

第八章:SPI通信:高速数据传输

8.1 SPI总线基础

SPI(Serial Peripheral Interface)是一种高速全双工串行总线:

  • MOSI:主出从入
  • MISO:主入从出
  • SCK:时钟
  • CS/NSS:片选

SPI特点:

  • 全双工通信
  • 高速(可达几十MHz)
  • 主从架构
  • 需要片选信号

8.2 SPI初始化配置

c 复制代码
SPI_HandleTypeDef hspi1;

// SPI初始化
void SPI_Init(void)
{
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;  // 9MHz
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    HAL_SPI_Init(&hspi1);
}

8.3 SPI读写操作

c 复制代码
// SPI发送数据
uint8_t tx_data = 0x55;
uint8_t rx_data;
HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, 1000);

// SPI只发送
HAL_SPI_Transmit(&hspi1, tx_buffer, size, 1000);

// SPI只接收
HAL_SPI_Receive(&hspi1, rx_buffer, size, 1000);

第九章:ADC采集:感知世界的窗口

9.1 ADC基础

ADC(Analog-to-Digital Converter)将模拟信号转换为数字信号。

STM32F103 ADC特性:

  • 12位分辨率(0-4095)
  • 多个通道(最多18个)
  • 单次/连续转换模式
  • 扫描模式

9.2 ADC初始化配置

c 复制代码
ADC_HandleTypeDef hadc1;

// ADC初始化
void ADC_Init(void)
{
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    HAL_ADC_Init(&hadc1);
    
    // 配置通道
    ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

// ADC读取
uint16_t ADC_Read(void)
{
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 1000);
    uint16_t value = HAL_ADC_GetValue(&hadc1);
    HAL_ADC_Stop(&hadc1);
    return value;
}

// 转换为电压(参考电压3.3V)
float ADC_ToVoltage(uint16_t adc_value)
{
    return (float)adc_value * 3.3f / 4095.0f;
}

第十章:DMA传输:解放CPU的利器

10.1 DMA基础

DMA(Direct Memory Access)可以在不占用CPU的情况下传输数据。

DMA优势:

  • 提高系统效率
  • 减少CPU占用
  • 支持大数据传输

10.2 DMA配置示例

c 复制代码
DMA_HandleTypeDef hdma_usart1_tx;

// DMA初始化(用于UART发送)
void DMA_Init(void)
{
    __HAL_RCC_DMA1_CLK_ENABLE();
    
    hdma_usart1_tx.Instance = DMA1_Channel4;
    hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_tx.Init.Mode = DMA_NORMAL;
    hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
    HAL_DMA_Init(&hdma_usart1_tx);
    
    __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);
}

// DMA发送数据
void UART_DMA_Send(uint8_t* data, uint16_t size)
{
    HAL_UART_Transmit_DMA(&huart1, data, size);
}

第十一章:低功耗设计:让设备更持久

11.1 STM32低功耗模式

STM32有多种低功耗模式:

  • 睡眠模式:CPU停止,外设运行
  • 停止模式:大部分模块停止
  • 待机模式:最低功耗

11.2 进入低功耗模式

c 复制代码
// 进入停止模式
void Enter_StopMode(void)
{
    // 配置唤醒源
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
    
    // 进入停止模式
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    
    // 唤醒后重新配置时钟
    SystemClock_Config();
}

第十二章:综合实战项目:智能环境监测系统

12.1 项目需求

设计一个智能环境监测系统,功能包括:

  • 温湿度采集(DHT22传感器)
  • 光照强度采集(光敏电阻+ADC)
  • OLED显示数据
  • 串口输出数据
  • 按键控制

12.2 系统架构

复制代码
STM32F103C8T6
├── DHT22传感器(单总线)
├── 光敏电阻(ADC采集)
├── OLED显示屏(I2C)
├── 按键(GPIO中断)
└── 串口(调试输出)

12.3 代码实现框架

c 复制代码
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    
    // 外设初始化
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_I2C1_Init();
    MX_ADC1_Init();
    MX_TIM2_Init();
    
    // 传感器初始化
    DHT22_Init();
    OLED_Init();
    
    // 主循环
    while (1)
    {
        // 读取传感器数据
        float temp = DHT22_ReadTemperature();
        float humi = DHT22_ReadHumidity();
        uint16_t light = ADC_ReadLight();
        
        // 显示数据
        OLED_DisplayData(temp, humi, light);
        
        // 串口输出
        UART_Printf("Temp: %.1fC, Humi: %.1f%%, Light: %d\r\n", 
                    temp, humi, light);
        
        // 延时
        HAL_Delay(1000);
    }
}

总结与展望

通过本文档的学习,你应该已经掌握了:

  1. ✅ HAL库的基础架构和使用方法
  2. ✅ 时钟系统的配置和优化
  3. ✅ GPIO的配置和应用
  4. ✅ 中断系统的使用
  5. ✅ 定时器的各种应用
  6. ✅ 串口通信的实现
  7. ✅ I2C和SPI总线通信
  8. ✅ ADC数据采集
  9. ✅ DMA高效传输
  10. ✅ 低功耗设计
  11. ✅ 综合项目开发

下一步学习建议:

  1. 深入学习:研究HAL库源码,理解底层实现
  2. 项目实践:完成更多实际项目,积累经验
  3. 性能优化:学习代码优化技巧,提高效率
  4. RTOS应用:学习FreeRTOS等实时操作系统
  5. 高级外设:学习USB、以太网等高级外设

祝您学习顺利,开发愉快!

相关推荐
就是蠢啊1 小时前
51单片机——DS1302 时钟芯片(一)
单片机·嵌入式硬件·51单片机
淘晶驰AK1 小时前
电机控制是不是就绑死在单片机上了
单片机·嵌入式硬件
CS Beginner1 小时前
【单片机】orange prime pi开发板与单片机的区别
笔记·嵌入式硬件·学习
bubiyoushang88811 小时前
基于STM32F103与A3988驱动芯片的两相四线步进电机控制方案
stm32·单片机·嵌入式硬件
bai54593614 小时前
STM32 备份寄存器
stm32·单片机·嵌入式硬件
cold_Mirac14 小时前
stm32-freertos和逻辑编程下堆栈功能的区分
stm32·单片机·嵌入式硬件
youcans_15 小时前
【动手学STM32G4】(3)上位机实时显示多路波形
stm32·单片机·嵌入式硬件·上位机
铁手飞鹰16 小时前
[HAL库分析—GPIO]
c语言·stm32·单片机·嵌入式硬件
徐某人..16 小时前
网络编程学习--第一天
arm开发·单片机·学习·arm