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

242 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🟠 第三层:驱动开发与外设编程
嵌入式驱动开发是连接硬件与上层应用的关键层,掌握寄存器操作、外设驱动编写及工具链使用是嵌入式工程师的核心技能。
以下从底层原理到实践应用进行深度扩展:
## 🔹 寄存器级开发
#### 📌 地址映射与寄存器偏移
- **总线架构**:
- AHB/APB总线:STM32通过AHB(高级高性能总线)连接高速外设,APB(高级外设总线)连接低速外设。
- 示例:GPIOA位于AHB1总线,基地址0x40020000;USART1位于APB2总线,基地址0x40011000。
- **寄存器偏移**:
- 每个外设包含多个寄存器,通过基地址+偏移量访问。
- 示例:GPIOA_MODER(模式寄存器)偏移0x00,GPIOA_ODR(输出数据寄存器)偏移0x14。
#### 📌 位操作技巧
- **原子操作宏**:
```c
#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))
```
- **多位置位/清零**:
```c
// 同时设置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中使能并设置中断优先级。
```c
// 示例:配置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。
- **中断接收实现**:
```c
// 接收完成回调函数
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周期。
- **多通道扫描模式**:
```c
// 配置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。
- **双缓冲区模式**:
- 适合大数据量传输,一个缓冲区用于当前传输,另一个准备下一次传输。
```c
// 配置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,位时序配置为:
```c
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)实现,用户可重写。
- 示例:
```c
// 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)应尽量简短,避免耗时操作。
- 关键数据传递使用原子操作或关中断保护。
```c
// 示例:使用原子操作传递数据
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. **调试技巧**
- **寄存器查看**:
```c
// 查看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资源,实现高速数据传输。
- 缺点:配置复杂,占用总线带宽。