Files
EmbeddedSoftwareLearn1/03-驱动开发与外设编程
2025-06-16 22:24:34 +08:00
..
2025-06-16 22:24:34 +08:00

🟠 第三层:驱动开发与外设编程

嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。 以下从底层原理到实践应用进行深度扩展:

🔹 寄存器级开发

📌 地址映射与寄存器偏移

  • 总线架构:
    • AHB/APB总线:STM32通过AHB(高级高性能总线)连接高速外设,APB(高级外设总线)连接低速外设。
    • 示例:GPIOA位于AHB1总线,基地址0x40020000;USART1位于APB2总线,基地址0x40011000。
  • 寄存器偏移:
    • 每个外设包含多个寄存器,通过基地址+偏移量访问。
    • 示例:GPIOA_MODER(模式寄存器)偏移0x00,GPIOA_ODR(输出数据寄存器)偏移0x14。

📌 位操作技巧

  • 原子操作宏:
    #define SET_BIT(REG, BIT)     ((REG) |= (BIT))
    #define CLEAR_BIT(REG, BIT)   ((REG) &= ~(BIT))
    #define READ_BIT(REG, BIT)    ((REG) & (BIT))
    #define TOGGLE_BIT(REG, BIT)  ((REG) ^= (BIT))
    
  • 多位置位/清零:
    // 同时设置PA5、PA6为输出(MODER[13:12]=01, MODER[11:10]=01)
    GPIOA_MODER = (GPIOA_MODER & ~(0xF << 10)) | (0x5 << 10);
    

🔹 通用外设驱动

📌 GPIO(通用输入输出)

  • 模式配置:
    • 输入模式:浮空输入、上拉输入、下拉输入、模拟输入。
    • 输出模式:推挽输出、开漏输出(需外部上拉)。
    • 复用模式:用于SPI、I2C等外设功能。
  • 中断配置步骤:
    1. 配置GPIO为输入模式。
    2. 配置SYSCFG_EXTICR寄存器选择中断源。
    3. 配置EXTI_IMR(中断屏蔽)、EXTI_RTSR(上升沿触发)/FTSR(下降沿触发)。
    4. 在NVIC中使能并设置中断优先级。
    // 示例:配置PA0为上升沿触发中断
    SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;  // 选择PA0
    EXTI->IMR |= EXTI_IMR_IM0;                    // 使能中断线0
    EXTI->RTSR |= EXTI_RTSR_TR0;                  // 上升沿触发
    HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);       // 设置中断优先级
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);               // 使能NVIC中断
    

📌 UART/USART

  • 波特率计算:
    • 公式:波特率 = 系统时钟 / (16 * USARTDIV)
    • 示例:系统时钟72MHz,波特率115200,则USARTDIV = 72000000 / (16 * 115200) ≈ 39.0625。
  • 中断接收实现:
    // 接收完成回调函数
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
        if (huart->Instance == USART1) {
            // 处理接收到的数据
            process_data(rx_buffer, rx_length);
            // 重新开启接收中断
            HAL_UART_Receive_IT(&huart1, rx_buffer, 1);
        }
    }
    

📌 SPI(串行外设接口)

  • 模式配置:
    • 时钟极性(CPOL):0(空闲时SCLK为低)或1(空闲时SCLK为高)。
    • 时钟相位(CPHA):0(第一个边沿采样)或1(第二个边沿采样)。
    • 数据位宽:8位或16位。
  • 主从模式区别:
    • 主模式:控制SCK时钟,负责发起通信。
    • 从模式:接收SCK时钟,响应主设备请求。

📌 I2C(集成电路间总线)

  • 寻址方式:
    • 7位地址:0x00~0x7F,其中0x00为广播地址。
    • 10位地址:扩展寻址,用于特殊设备。
  • 多主竞争解决:
    • 通过SDA线的电平检测实现总线仲裁,先检测到SDA线被拉低的主设备退出竞争。

📌 ADC(模拟-to-数字转换器)

  • 采样时间配置:
    • 采样时间越长,转换结果越精确,但转换速度越慢。
    • 示例:STM32F4的ADC采样时间可配置为3、15、28、56、84、112、144、480周期。
  • 多通道扫描模式:
    // 配置ADC1扫描模式,采样通道0、1、2
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 3;  // 3个转换通道
    
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = 1;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
    
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = 2;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
    
    sConfig.Channel = ADC_CHANNEL_2;
    sConfig.Rank = 3;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);
    

🔹 复杂外设支持

📌 DMA 控制器

  • 通道选择:
    • 每个DMA控制器包含多个通道,不同外设对应不同通道。
    • 示例:USART1_RX对应DMA2通道5,USART1_TX对应DMA2通道4。
  • 双缓冲区模式:
    • 适合大数据量传输,一个缓冲区用于当前传输,另一个准备下一次传输。
    // 配置DMA双缓冲区模式
    hdma_adc.Instance = DMA2_Stream0;
    hdma_adc.Init.BufferSize = 2;  // 双缓冲区
    hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc.Init.MemInc = DMA_MINC_ENABLE;
    // ...其他配置
    

📌 看门狗(Watchdog)

  • 独立看门狗(IWDG):
    • 由专用低速时钟(LSI,约32kHz)驱动,即使主时钟故障仍能工作。
    • 喂狗时间范围:典型值10ms~16s。
  • 窗口看门狗(WWDG):
    • 喂狗时间必须在窗口范围内(上限值~下限值),防止程序在异常状态下喂狗。

📌 CAN(控制器局域网)

  • 位时序配置:
    • 由同步段(SYNC_SEG)、传播时间段(PROP_SEG)、相位缓冲段1(PHASE_SEG1)和相位缓冲段2(PHASE_SEG2)组成。
    • 示例:波特率500kbps,系统时钟42MHz,位时序配置为:
      sFilterConfig.FilterBank = 0;
      sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
      sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
      sFilterConfig.FilterIdHigh = 0x0000;
      sFilterConfig.FilterIdLow = 0x0000;
      sFilterConfig.FilterMaskIdHigh = 0x0000;
      sFilterConfig.FilterMaskIdLow = 0x0000;
      sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
      sFilterConfig.FilterActivation = ENABLE;
      HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
      

🔹 开发库 & 工具链

📌 STM32 HAL(硬件抽象层)

  • HAL库架构:
    • 核心层:提供外设初始化、控制和状态检查函数。
    • 回调函数:通过弱函数(weak)实现,用户可重写。
    • 示例:
      // HAL_UART_Transmit()函数原型
      HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
      
      // 重写回调函数
      void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
          if (huart->Instance == USART1) {
              // 发送完成后的处理
          }
      }
      

📌 STM32 LL(低层驱动)

  • 优势:
    • 代码体积更小,执行效率更高。
    • 更接近寄存器操作,适合性能敏感场景。
  • 与HAL对比:
    特性 HAL LL
    抽象程度 高 低
    代码体积 大 小
    执行效率 低 高
    学习难度 低 高

📌 STM32CubeMX

  • 时钟树配置:
    • 基于PLL(锁相环)生成系统时钟,需合理配置倍频系数和分频系数。
    • 示例:配置系统时钟为180MHz:
      HSE (8MHz) → PLLM=8 → VCO输入=1MHz → PLLN=360 → VCO输出=360MHz → PLLP=2 → 系统时钟=180MHz
      
  • 中间件集成:
    • 支持FreeRTOS、LWIP、USB、File System等中间件一键配置。

🔹 实战技巧与常见问题

1. 外设初始化流程

  1. 使能外设时钟。
  2. 配置GPIO复用功能(如需要)。
  3. 配置外设参数(如波特率、采样时间)。
  4. 使能外设。

2. 中断处理优化

  • 中断服务函数(ISR)应尽量简短,避免耗时操作。
  • 关键数据传递使用原子操作或关中断保护。
// 示例:使用原子操作传递数据
volatile uint32_t g_flag __attribute__((aligned(4)));

void EXTI0_IRQHandler(void) {
    __disable_irq();
    g_flag = 1;  // 原子写操作
    __enable_irq();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

3. 调试技巧

  • 寄存器查看:
    // 查看GPIOA_MODER寄存器值
    uint32_t moder_value = GPIOA->MODER;
    printf("GPIOA_MODER = 0x%08X\n", moder_value);
    
  • 示波器检测:
    • 检测SPI/I2C总线波形,验证通信时序。
    • 检测PWM波形,验证占空比和频率。

六、面试高频问题

  1. HAL与LL库的选择标准:

    • 快速开发选HAL,性能敏感场景选LL;需平衡开发效率与代码体积。
  2. I2C通信中ACK/NACK的作用:

    • ACK(应答):接收方正确接收到数据,发送低电平。
    • NACK(非应答):接收方无法继续接收,发送高电平。
  3. ADC采样时间对精度的影响:

    • 采样时间越长,对信号的积分效果越好,抗干扰能力越强,精度越高。
  4. DMA与CPU直接传输的优缺点:

    • 优点:释放CPU资源,实现高速数据传输。
    • 缺点:配置复杂,占用总线带宽。