RT1060 ADC demo 寄存器解析_Part2 中断
i.MX RT1060 下的 ADC 开发实战:从 HAL 库到寄存器级驱动
在嵌入式开发中,官方提供的 HAL (Hardware Abstraction Layer) 库虽然使用方便,但有时为了追求极致的性能、更小的代码体积,或者仅仅是为了“知其所以然”,我们需要深入底层,直接操作寄存器。
本文将以 NXP i.MX RT1060 的 ADC 中断例程为例,深入剖析 ADC 外设的工作原理、寄存器配置细节,并演示如何将官方 SDK 的 HAL 库代码重构为直接操作寄存器的高效代码。
1. 工程概述与逻辑架构
1.1 功能描述
RT1060 adc_interrupt Demo 演示了一个 12-bit SAR ADC 在软件触发模式下的基础功能:
- 交互方式:通过串口终端 (Debug Console) 交互。
- 工作流:*用户按下任意键 -> 触发一次 ADC 转换 -> ADC 转换完成触发中断 -> 在 ISR 中读取数据 -> 串口打印结果*。
- 核心价值:展示了从触发到中断读取的完整闭环。
1.2 逻辑时序图 (UML)
为了理清代码并未直接展现的硬件交互流程,我们绘制了如下的时序图:
sequenceDiagram
participant User as 用户 (Terminal)
participant Main as 主程序 (Main Loop)
participant Register as 寄存器接口
participant ADC_HW as ADC 硬件核心
participant ISR as 中断服务程序
Note over Main, ADC_HW: === 初始化阶段 ===
Main->>Register: 配置 CFG (时钟/模式) & GC (功能)
Main->>Register: 写入 GC[CAL] 启动自校准
ADC_HW-->>Register: 校准完成自动清零 CAL
Main->>Register: 检查 GS[CALF] 确认成功
Note over User, ISR: === 运行时循环 ===
loop Every Conversion
Main->>User: "Press any key..."
User->>Main: 按键输入
Main->>Main: 清除软件标志位 Flag = false
Main->>Register: 写入 HC0[ADCH] (通道号)
Note right of Main: 关键:写入 HC0 立即启动软件触发转换
ADC_HW->>ADC_HW: 采样 & 转换 (Sampling)
ADC_HW-->>ISR: 完成中断 (COCO Interrupt)
activate ISR
ISR->>Register: 读取 R0 寄存器
Note right of ISR: 关键:读取 R0 自动清除 COCO 标志
ISR->>Main: 置位 Flag = true
deactivate ISR
Main->>Main: 退出等待循环
Main->>User: 打印采样值
end
2. 核心寄存器深度解析
要脱离 HAL 库,必须读懂 Datasheet。RT1060 的 ADC 主要由以下几个关键寄存器控制:
2.1 ADCx_CFG (Configuration Register) - 全局大脑
这是配置 ADC 基础属性的地方。
- ADICLK (Input Clock): 设置为
11b(Asynchronous Clock)。选用异步时钟让 ADC 可以在 CPU 总线时钟停止时继续运行(低功耗场景)。 - MODE (Resolution): 设置为
10b(12-bit)。输出范围 0-4095。 - ADTRG (Trigger): 设置为
0(Software Trigger)。这是本例程的核心,如果不配置为软件触发,写入命令寄存器将无效。 - ADIV (Divider): 时钟分频,通常设为 0 (不分频) 以获得最快采样速度。
2.2 ADCx_GC (General Control) - 功能开关
- CAL (Calibration): 写 1 启动硬件自校准。
- ADACKEN (Asynchronous Clock Output): 必须置 1。因为我们在 CFG 中选择了异步时钟作为源,所以这里必须打开它。
2.3 ADCx_HCn (Heap Command/Control) - 发令枪
RT1060 有多个 HC 寄存器 (HC0-HC7),但在软件触发模式下,通常只用 HC0。
- 写入动作:这是最关键的一点。在软件触发模式下,向
HC0写入通道号的操作,等同于按下了“启动按钮”。 - ADCH (Channel): 写入目标通道号。
- AIEN (Interrupt Enable): 置 1 则转换完成后触发中断。
2.4 ADCx_R0 (Data Result) - 终点
- 读取该寄存器获得 12-bit 结果。
- 隐含操作:读取
R0会自动清除状态寄存器 (HS) 中的“转换完成标志” (COCO)。
3. 从 HAL 到寄存器的代码重构实战
我们将对比 NXP SDK 的 HAL 实现与我们重构后的寄存器实现。本例中,我们将完整的 HAL 调用替换为了直接的指针操作。
3.1 初始化配置 (Initialization)
HAL 方式:
1 | adc_config_t adcConfigStruct; |
寄存器方式 (重构后):
我们直接操作 CFG 和 GC 寄存器,代码更加直观且无函数调用开销。
1 | uint32_t tmp32; |
3.2 硬件自校准 (Auto-Calibration)
HAL 库的 ADC_DoAutoCalibration 内部逻辑较为复杂,隐藏了状态查询细节。重写后我们可以清晰看到校准的完整握手流程:
1 | /* 1. 清除旧的校准失败标志 */ |
3.3 触发转换与中断读取
这是主循环中最频繁执行的代码段。
寄存器方式启动转换:
1 | // 准备命令字:通道号 + 中断使能位 |
中断服务程序 (ISR):
1 | void EXAMPLE_ADC_IRQHandler(void) |
4. 总结
通过这次重构,我们不仅缩减了代码的 Flash 占用(移除了庞大的 fsl_adc.c 依赖),更重要的是彻底掌握了:
- 触发机制:明确了“写 HC0”就是“软件触发”的本质。
- 标志位管理:理解了读取数据寄存器与清除状态标志的硬件联动关系。
- 时钟构建:理解了异步时钟源配置与 GC 控制寄存器的依赖关系。
这种寄存器级别的掌控力,在进行高性能电机控制、电源管理等对 ADC 采样时序要求极高的应用中,是必不可少的核心能力。
附录
本工程针对 i.MX RT1060 ADC (12-bit SAR) 外设。以下是核心用到的寄存器及其位域分析。
1. ADCx_CFG (Configuration Register)
用于设置 ADC 的工作模式、时钟源和采样时间。
| Bit(s) | Field Name | Description in Project | Value Analysis |
|---|---|---|---|
| 1-0 | ADICLK | Input Clock Select | 11b (kADC_ClockSourceAD) - 选择异步时钟 (Asynchronous Clock)。ADACK,该时钟是ADC模块中的时钟源生成的,所以当单片机处于停止模式时该时钟仍然在运行。使用该时钟在停止模式下ADC可以进行转换。 |
| 3-2 | MODE | Conversion Mode Selection | 10b (kADC_Resolution12Bit) - 12位分辨率。结果范围 0-4095。 |
| 4 | ADLSMP | Long Sample Time Configuration | 0 (Short Sample) - 使用短采样时间。 |
| 6-5 | ADIV | Clock Divide Select | 00b (Divide by 1) - 输入时钟不分频。 |
| 7 | ADLPC | Low-Power Configuration | 0 - 普通功耗模式。 |
| 12-11 | REFSEL | Voltage Reference Selection | 0- 只有一个选项(kADC_ReferenceVoltageSourceAlt0) |
| 13 | ADTRG | Conversion Trigger Select | 0 - 软件触发 (Software Trigger)。这是本工程的关键,转换由写入 HC 寄存器启动。 |
| 16 | OVWREN | Data Overwrite Enable | 0 - 禁止覆写。如果旧数据未读,新数据不会覆盖它(这是通常的安全做法,虽然在中断模式下通常能及时读取)。 |
2. ADCx_GC (General Control Register)
用于控制校准、DMA请求和异步时钟输出。
| Bit(s) | Field Name | Description in Project | Value Analysis |
|---|---|---|---|
| 0 | ADACKEN | Asynchronous Clock Output Enable | 1 - 使能。因为 CFG[ADICLK] 选择了 ADACK,所以必须开启此位以产生时钟。 |
| 1 | DMAEN | DMA Enable | 0 - 关闭 DMA。本工程使用中断 (Interrupt) 方式搬运数据,而非 DMA。 |
| 6 | ADCO | Continuous Conversion Enable | 0 - 单次转换 (One-shot)。每次触发只进行一次转换。 |
| 7 | CAL | Calibration Func | 1 (Momentary) - 写入 1 启动自校准流程。校准结束后硬件自动清零。 |
3. ADCx_GS (General Status Register)
用于指示 ADC 模块的全局状态(非特定通道)。
| Bit(s) | Field Name | Description in Project | Value Analysis |
|---|---|---|---|
| 1 | CALF | Calibration Failed Flag | 用于 ADC_DoAutoCalibration 函数中。如果校准失败,此位会被置 1。程序需检查此位确保校准成功。 |
| 2 | ADACT | Conversion Active | 指示转换是否正在进行中。 |
4. ADCx_HCn (Control register for hardware triggers)
重要:在 RT1050 芯片中,有 HC0 到 HC7。对于软件触发,只能使用 HC0。写入此寄存器是启动软件转换的触发器。
| Bit(s) | Field Name | Description in Project | Value Analysis |
|---|---|---|---|
| 4-0 | ADCH | Input Channel Select | 设置为 DEMO_ADC_USER_CHANNEL。选择物理模拟通道引脚。 |
| 7 | AIEN | Interrupt Enable | 1 - 开启中断。当转换完成 (COCO) 时,产生中断请求。 |
5. ADCx_Rn (Data Result Register)
保存转换结果。在此工程中主要关注 R0(对应 HC0)。
| Bit(s) | Field Name | Description in Project | Value Analysis |
|---|---|---|---|
| 11-0 | D | Data (Result) | 12位转换结果数据。 |
注意:读取
Rn寄存器也是清除该通道HS(Status) 寄存器中COCO(Conversion Complete) 标志位的标准方法。
6. ADCx_HS (Status Register)
通道状态寄存器。
| Bit(s) | Field Name | Description |
|---|---|---|
| 0 | COCO0 | Conversion Complete Flag 0。当 R0 数据准备好时置 1。读取 R0 后自动清零。 |
附录2
完整的修改后的寄存器代码
1 | int main(void) |
ADC1_IRQHandler
1 | void EXAMPLE_ADC_IRQHandler(void) |
附录3 Polling(轮询) vs Interrupt(中断)版本概要
两者的核心寄存器配置(CFG、GC、时钟/校准)一致,主要差异集中在 转换完成后的处理方式 和 初始化流程中的中断配置。
详细差异对比表
| 特性 | Polling(轮询)版本 | Interrupt(中断)版本 adc_interrupt.c |
|---|---|---|
| 1. 中断控制器使能 | 无 | 在 main 开头调用 EnableIRQ(DEMO_ADC_IRQn);,使能 NVIC 层级的中断 |
2. HCn 寄存器配置(触发转换) |
tmp32 = ADC_HC_ADCH(...)(不设置 AIEN);DEMO_ADC_BASE->HC[...] = tmp32; |
tmpHC = ADC_HC_ADCH(...) 后, **置位 AIEN**; uint32_t tmpHC = ADC_HC_ADCH(DEMO_ADC_USER_CHANNEL);`tmpHC |
| 3. 等待转换完成 | 主动忙等:死循环查询 HS/COCO 状态位:while (0U == ((...->HS >> ...) & 0x1U)) { ; } |
被动等待软件标志位:while (g_AdcConversionDoneFlag == false) { }(主循环不直接查寄存器) |
| 4. 结果读取 | 在 while 循环结束后直接读取:result = DEMO_ADC_BASE->R[...] |
在 ISR(EXAMPLE_ADC_IRQHandler(...))里读取 R[...] 并更新全局变量 |
| 5. 状态清除(COCO) | 读取 R[...] 时硬件自动清除(在主程序中发生) |
读取 R[...] 时硬件自动清除(在ISR中发生) |
代码逻辑差异深度分析
1) 触发转换的区别(HC 寄存器)
Polling 版本:仅写入 ADCH 通道号。这会启动转换,但不会请求中断。
1 | tmp32 = ADC_HC_ADCH(adcChannelConfigStruct.channelNumber); |
Interrupt 版本:写入 ADCH 通道号 + AIEN (Interrupt Enable) 位。
1 | uint32_t tmpHC = ADC_HC_ADCH(DEMO_ADC_USER_CHANNEL); |
2) 等待机制的区别(Busy-Wait vs. Interrupt-Wait)
Polling:这种方式占用 CPU。CPU 在 while 循环中不断去访问外设总线读取 HS (Status) 寄存器,直到 COCO (Conversion Complete) 标志变 1。
1 | while (0U == ((DEMO_ADC_BASE->HS >> DEMO_ADC_CHANNEL_GROUP) & 0x1U)) { ; } |
Interrupt:这种方式释放 CPU(虽然本例中 CPU 也在死循环等待 flag,但在实际 RTOS 或复杂系统中,CPU 可以去干别的事或者休眠)。当硬件转换完成,硬件信号直接触发 NVIC 跳转到 ISR。
1 | while (g_AdcConversionDoneFlag == false) { } // 等待 ISR 修改此变量 |
3) 读取数据的时机与位置
Polling: 就在 main 函数里,确认 HS 标志变位后立即读取。
Interrupt: 在 EXAMPLE_ADC_IRQHandler 函数里读取。
总结
polling 代码是同步阻塞的寄存器写法,而 adc_interrupt.c 是异步事件驱动的寄存器写法。两者底层的 CFG (时钟/模式) 和 GC (校准) 配置是完全通用的。
RT1060 ADC demo 寄存器解析_Part2 中断
https://dustofstars.github.io/NXP/RT1060/ADC/rt1060-adc-demo-寄存器解析-part2-中断/
