MC68336/376队列式ADC:多通道数据采集的硬件级解决方案
1. 项目概述:深入理解MC68336/376的队列式ADC
在嵌入式系统开发,尤其是汽车电子、工业控制这些对实时性和可靠性要求极高的领域,数据采集是连接物理世界与数字世界的桥梁。我们常常需要同时监控多个传感器的状态——比如发动机的温度、压力、转速,或者生产线上的多个位置、速度、张力信号。传统的单通道ADC或者简单的轮询式多通道ADC在面对这种复杂、时序要求严格的场景时,往往会显得力不从心,要么是编程逻辑复杂,要么是难以保证关键通道的采样时机。
Motorola(后来的Freescale,现为NXP)的MC68336/376系列微控制器,作为经典的32位嵌入式处理器,其内置的队列式模数转换器模块,为解决这类问题提供了一个非常优雅的硬件级方案。它不像普通ADC那样,需要软件频繁介入去启动每一次转换、切换通道,而是允许开发者预先编排好一个完整的“剧本”——也就是转换命令队列。这个队列里写明了要按什么顺序、对哪些通道、以何种采样时间进行转换。编排好后,只需一个触发信号(可以是软件命令、定时器溢出或者外部硬件信号),QADC模块就能像一个忠实的执行者,自动地、按部就班地完成整个序列的转换,并将结果整齐地存放在指定的内存表格中。整个过程极大地解放了CPU,让软件从繁琐的ADC控制中解脱出来,专注于更上层的逻辑处理。
我过去在汽车电控单元项目中,就用它来管理发动机的多个模拟量输入,效果非常显著。系统响应更及时,代码结构也更清晰。接下来,我就结合手册内容和实际调试经验,把这个模块从工作原理到寄存器配置,再到实际编程中的坑和技巧,给大家掰开揉碎了讲清楚。
2. QADC模块核心架构与工作模式解析
要玩转QADC,首先得在脑子里建立起它的核心架构图。它不是单个ADC的简单复用,而是一套包含调度器、内存表和执行单元的小型系统。
2.1 双队列与共享资源模型
QADC最核心的设计思想是双优先级队列和共享的转换命令/结果内存。你可以把它想象成一个有两个流水线的厨房(队列1和队列2),它们共享一套灶具和厨具(ADC转换核心),但共用一本写满了菜谱的笔记本(CCW表)和一块出菜台(结果字表)。
- 队列1(Queue 1):拥有最高优先级。它的起点固定在整个CCW表的第一个位置(地址偏移0)。一旦队列1被触发,它会立即抢占正在进行的队列2的转换(如果有的话),优先执行自己的任务。这非常适合处理紧急、高优先级的采样任务,比如安全相关的传感器信号。
- 队列2(Queue 2):优先级较低。它的起始位置(BQ2)是可编程的,由寄存器QACR2中的BQ2字段指定。这意味着你可以灵活地将CCW表的内存空间分配给两个队列。例如,你可以让队列1使用前20个条目,队列2使用后20个条目。
- CCW表(Conversion Command Word Table):这是一块40个条目、每个条目10位宽的RAM区。每个条目就是一个“转换命令字”,它明确告诉QADC:“去转换AN5通道,采样时间用8个QCLK周期,转换完成后暂停等待下一个触发”。软件的工作就是预先把这个表填好。关键点:这个表是静态的,QADC只会读取它,不会修改它。这保证了转换序列的可预测性。
- 结果字表(Result Word Table):与CCW表一一对应,也是40个条目、10位宽的RAM区。当QADC完成一个CCW指定的转换后,得到的10位数字结果就会自动存入结果字表中对应的位置。软件只需要定期或通过中断来读取这个表,就能获取所有通道的转换值。
这种设计的美妙之处在于灵活性和确定性。你可以为队列1配置一个由外部中断触发的、高优先级的快速采样序列(比如几个关键通道);同时为队列2配置一个由内部定时器触发的、低优先级的慢速巡检序列(比如所有其他辅助通道)。两个队列独立工作,互不干扰(除非队列1抢占队列2),整个采样计划在初始化阶段就完全确定,运行时几乎没有软件开销。
2.2 转换执行的四个阶段
QADC完成一次转换并不是“一键完成”,它内部细分为四个精确定时的阶段,理解这个对配置采样时间至关重要:
- 初始采样(Initial Sample):在这个阶段,模拟多路复用器将选定的输入通道连接到内部的采样电容上。电容开始充电,电压逐渐逼近外部模拟信号的电压。这个阶段主要是为了建立初步的电荷。
- 转移(Transfer):采样电容与输入通道断开,其上的电荷被转移到后续的采样保持放大器进行缓冲。这个阶段完成了模拟信号的“捕捉”和隔离。
- 最终采样(Final Sample):这是精度建立的关键阶段。放大器被旁路,输入通道直接连接到核心的逐次逼近寄存器型ADC的RC DAC阵列上,对其进行充电。每个CCW中的IST[1:0]位(输入采样时间)就是用来配置这个阶段的持续时间的,可选2、4、8或16个QADC时钟周期。时间越长,信号建立得越充分,转换精度越高,但吞吐率越低。你需要根据信号源阻抗和精度要求来权衡选择。对于高阻抗源(如某些传感器),必须选择更长的采样时间。
- 转换(Resolution):SAR ADC开始进行逐次逼近比较,将模拟电压转换为10位数字量。这个阶段的时间是固定的,由ADC内核决定。
实操心得:很多新手会忽略采样时间的配置,直接使用默认值。结果发现采集动态变化较快的信号时,读数总是不准或波动大。这很可能就是最终采样时间不足,信号还没稳定就开始转换了。我的经验法则是:先用最长的16个周期测试,如果读数稳定,再逐步减小采样时间,直到找到在满足精度要求下的最快设置。对于连接运算放大器缓冲后的低阻抗信号,2或4个周期通常就够了。
2.3 队列的执行、暂停与终止
队列的执行逻辑是QADC自动化的体现:
顺序执行:QADC从队列的起始指针开始,依次读取并执行每一个CCW,直到遇到“终止条件”。
暂停(Pause):这是QADC一个非常实用的功能。你可以在任何一个CCW中设置“暂停位(P)”。当执行到这个CCW并完成转换后,队列会停止,等待下一个触发事件。同时,状态寄存器中的暂停标志位(PF1或PF2)会置位,并可产生中断。这个功能有什么用?实现同步采样。例如,你需要同时采集三个相位互差120度的交流信号。你可以编排一个包含三个CCW的队列,每个CCW都设置暂停位。然后使用同一个外部同步触发信号(如过零检测信号)来触发队列。每次触发,QADC只执行一个CCW(转换一个通道)后就暂停,等待下一个过零点。这样,三个通道就能在严格同步的时间点上被采样。
队列结束(End-of-Queue):有三种方式标识队列结束:
- 在CCW的通道号字段写入特殊值63(
$3F)。 - 对于队列1,其结束位置隐含地由队列2的起始指针BQ2定义(BQ2指向的位置就是队列1的结束)。
- 到达CCW表的物理末尾(第39个条目)。 当队列结束时,完成标志位(CF1或CF2)置位,并可产生中断。
- 在CCW的通道号字段写入特殊值63(
队列的异常终止与恢复:这是调试时最容易出问题的地方。手册列出了几种会中止当前转换的情况:
- 高优先级队列抢占:队列1触发会中止正在进行的队列2转换。这是设计使然。
- 队列被禁用:软件写寄存器禁用某个队列。
- 队列模式改变:软件改变了队列的触发模式。
- 进入低功耗停止模式。
- 调试冻结:CPU进入调试模式,冻结外设。
这里重点说下队列2被队列1抢占后的恢复行为,因为它有两种模式,由QACR2中的RES位控制:
- RES=0(默认):队列2从中断处继续。即从被中止的那个CCW条目开始重新执行。这保证了数据序列的连续性,但可能导致被中止的那个通道的样本是在不同的扫描周期内采集的(如果队列1执行时间很长)。
- RES=1:队列2重新开始。即从队列2的第一个CCW(或当前子队列的开头)重新执行。这能保证在一次完整的队列2扫描中,所有样本都是在同一个“扫描周期”内采集的,对于需要相关性的计算(如三相电流计算)非常重要。但代价是,被中止的那次转换数据丢失了。
避坑指南:在实时性要求高的系统中,如果队列1的触发非常频繁(例如用于高速保护),一定要小心评估它对队列2的影响。如果队列2总是无法完成一次完整的扫描,它的数据将永远无法被有效使用。此时可能需要重新设计,比如减少队列2的长度,或者提高其优先级(但队列1的优先级不可更改),或者考虑使用单队列模式。
3. 关键寄存器配置详解与编程实战
理解了原理,我们就要动手配置了。QADC的寄存器不算多,但每个位都有其用意。配置不当,轻则数据不准,重则模块不工作。
3.1 模式控制寄存器(QACR1, QACR2)
这是每个队列的“大脑”,决定了队列如何被触发、如何运行。
QACR1 (Queue 1 Control Register) / QACR2 (Queue 2 Control Register)这两个寄存器结构类似,我们以QACR1为例讲解关键位域:
| 位域 | 名称 | 功能描述 | 配置要点 |
|---|---|---|---|
| 15-14 | MODE[1:0] | 队列操作模式。这是最重要的设置之一。 | 00: 禁止队列。01: 软件触发扫描。10: 外部触发扫描。11: 内部定时器触发扫描。 |
| 13 | SSC | 单次扫描使能。 | 0: 连续扫描模式。触发一次,队列循环执行。1: 单次扫描模式。触发一次,队列执行完一遍后停止,需软件再次使能。 |
| 12 | GATE | 门控使能(仅对外部触发模式有效)。 | 0: 边沿触发。检测到ETRIG1引脚的有效边沿即触发。1: 电平门控触发。ETRIG1引脚为高电平时,定时器溢出或软件命令才能触发。 |
| 11 | CIE | 完成中断使能。 | 1: 当队列完成(CF1=1)时,产生中断请求。 |
| 10 | PIE | 暂停中断使能。 | 1: 当队列暂停(PF1=1)时,产生中断请求。 |
| 9-8 | BQ2[1:0] | 队列2起始指针(仅在QACR2中有效)。 | 指定队列2在40字CCW表中的起始位置(0-39)。它同时也隐式定义了队列1的结束位置。 |
| 7 | RES | 队列2恢复控制(仅在QACR2中有效)。 | 0: 从中断点继续。1: 从队列/子队列开头重新开始。 |
| 6 | SSE | 单次扫描使能位(软件写入,读取总为0)。 | 在单次扫描模式(SSC=1)下,向此位写1来启动一次扫描。 |
配置示例:设置队列1为外部下降沿触发、单次扫描、使能完成中断假设我们需要用外部信号(连接至ETRIG1引脚)的下降沿来触发一次对8个通道的采样。
// 假设 QADC 模块基地址为 0xFFF14000 volatile struct QADC_tag { ... // 其他寄存器 vuint16_t QACR1; ... } * const QADC = (struct QADC_tag *)0xFFF14000; void QADC_Queue1_Init(void) { // MODE=10 (外部触发), SSC=1 (单次扫描), GATE=0 (边沿触发) // CIE=1 (使能完成中断), PIE=0 (禁用暂停中断) // 其他位保持0(例如,对于QACR1,BQ2和RES位无关) QADC->QACR1 = 0x5200; // 二进制 0101 0010 0000 0000 }这个配置意味着:每次ETRIG1引脚出现有效边沿(具体是上升沿还是下降沿可能由其他寄存器控制,需查手册),队列1就会自动执行一遍预设的转换序列,然后停止,等待下一个边沿。完成时会触发中断,通知CPU去读取结果。
3.2 状态寄存器(QASR)与标志位清除机制
QASR是了解QADC当前状态的窗口,也是中断产生的源头。
QASR (QADC Status Register) 关键位:
CF1,PF1,CF2,PF2: 分别是队列1完成、队列1暂停、队列2完成、队列2暂停标志位。当相应事件发生时,硬件置1。TOR1,TOR2: 触发溢出标志。如果一个新的触发事件到来时,队列还未处理完上一个触发(例如还在暂停状态),此位置1。这提示你可能触发频率过高,导致数据丢失。CQ[5:0]: 当前队列指针。指示正在执行或即将执行的CCW在表中的位置。调试时非常有用。
重中之重:标志位的清除机制这是QADC编程中最容易出错的地方之一。你不能直接写0来清除CF1、PF1等标志位。手册明确说明,清除这些标志需要一个特定的“读-改-写”序列:
- 读取QASR寄存器。CPU必须读到该标志位为1。
- 写入QASR寄存器,仅将要清除的标志位对应的位写0,其他位写0或1(通常写0以保持清晰)。
为什么这么设计?这是一种硬件互锁机制,防止在两次操作之间发生新事件时错误地清除标志。如果在新事件发生(标志位置1)之后、CPU执行写清除之前,CPU已经读过了旧状态的寄存器,那么这次写操作是无效的,标志位不会被清除。这确保了软件不会错过任何事件。
错误的清除方式:
QADC->QASR = 0x0000; // 错误!这可能会清除所有标志,但不符合硬件序列要求,可能无效或导致未定义行为。正确的清除方式(以清除CF1和PF1为例):
vuint16_t status; status = QADC->QASR; // 1. 读取状态寄存器 if (status & (QASR_CF1_MASK | QASR_PF1_MASK)) { // 2. 写入,仅将CF1和PF1位写0。注意:写入的值中,要清除的位写0,其他位通常也写0。 // 假设 CF1 是 bit 7, PF1 是 bit 6 QADC->QASR = (status & ~(QASR_CF1_MASK | QASR_PF1_MASK)); }调试血泪教训:我曾经在一个项目中,中断服务程序里直接
QADC->QASR = 0;来清除标志。大部分时间工作正常,但在高负载、中断频繁时,偶尔会出现中断标志“粘住”不清除,导致中断持续触发,系统卡死。排查了很久才发现是这个清除序列的问题。严格按照手册的读-改-写顺序操作后,问题彻底解决。
3.3 转换命令字(CCW)的编排艺术
CCW是QADC的灵魂,它的编排直接决定了采样行为。它是一个10位的字,格式如下:
| 位 | 名称 | 功能 |
|---|---|---|
| 9 | BYP | 旁路位。1表示跳过最终采样阶段(使用最短采样时间),用于高速但低精度转换。通常设为0。 |
| 8 | P | 暂停位。1表示转换完成后暂停队列,等待下一个触发。 |
| 7-6 | IST[1:0] | 输入采样时间。00=2周期,01=4周期,10=8周期,11=16周期。 |
| 5-0 | CHAN[5:0] | 通道选择。值0-63对应不同的模拟输入通道(具体映射见芯片数据手册)。特殊值63 (0x3F) 表示队列结束。 |
编排示例:创建一个队列1的转换序列假设我们需要按顺序采样:通道0(温度,需要高精度)、通道5(电压,快速)、通道7(电流,需要高精度),并在采样电流后暂停,等待同步信号。
// CCW 表在内存中的地址(假设从基地址偏移 0x800 开始) vuint16_t* const CCW_TABLE = (vuint16_t*)(0xFFF14800); void Setup_CCW_Table(void) { // 条目0: 通道0,采样时间16周期,不暂停 // CHAN=0, IST=11(16), P=0, BYP=0 -> 二进制 0 0 11 000000 = 0x0C00 CCW_TABLE[0] = 0x0C00; // 条目1: 通道5,采样时间2周期(快速),不暂停 // CHAN=5, IST=00(2), P=0, BYP=0 -> 0 0 00 000101 = 0x0005 CCW_TABLE[1] = 0x0005; // 条目2: 通道7,采样时间16周期,设置暂停位 // CHAN=7, IST=11(16), P=1, BYP=0 -> 0 1 11 000111 = 0x1C07 CCW_TABLE[2] = 0x1C07; // 条目3: 设置队列结束标志 (CHAN=63) // CHAN=63(0x3F), IST/P/BYP 任意(通常设0)-> 0 0 00 111111 = 0x003F CCW_TABLE[3] = 0x003F; // 注意:队列1的结束也可以通过设置QACR2的BQ2=4来实现,这样更灵活。 }这个序列配置好后,当队列1被触发,它会依次转换通道0、5、7,然后在通道7转换完成后暂停,并置位PF1标志(如果使能了则产生中断)。CPU在中断中处理完数据后,可以(例如通过软件命令)再次触发队列1,它就会从下一个CCW(即结束标志或队列开头,取决于模式)继续执行。
3.4 结果字表(RWT)与数据读取
转换完成后,10位结果存放在结果字表中。同一个物理结果,可以通过三种不同的内存地址对齐方式来读取,得到三种不同格式的16位数,这简化了后续数据处理。
假设结果字表基地址为0xFFF15000。
- 右对齐无符号格式(地址偏移
0x0000 - 0x004F):读取到的16位数据,低10位是转换结果,高6位为0。这是最直观的格式。result = *(vuint16_t*)(0xFFF15000 + index*2) & 0x03FF; - 左对齐有符号格式(地址偏移
0x0800 - 0x084F):结果左移6位到高10位,最高位(第15位)取反作为符号位。这相当于将0-1023的原始值,映射到-512到+511的范围内,方便进行有符号运算(如交流信号处理)。result_signed = *(vsint16_t*)(0xFFF15000 + 0x0800 + index*2); - 左对齐无符号格式(地址偏移
0x0C00 - 0x0C4F):结果左移6位到高10位,低6位为0。result_left = *(vuint16_t*)(0xFFF15000 + 0x0C00 + index*2);
编程技巧:在C语言中,可以通过定义联合体(union)和结构体(struct)来优雅地访问这些表,让编译器处理地址计算和格式转换。
typedef union { vuint16_t u16; struct { vuint16_t result:10; vuint16_t :6; // 未使用位 } right_justified; // 可以类似定义左对齐格式的结构 } QADC_Result_t; #define QADC_RWT_BASE 0xFFF15000 volatile QADC_Result_t* const RWT = (QADC_Result_t*)QADC_RWT_BASE; // 读取第index个结果的右对齐值 uint16_t adc_value = RWT[index].right_justified.result;
4. 中断系统配置与实战避坑
中断是高效使用QADC的关键,避免了软件轮询的延迟和CPU占用。
4.1 中断源与向量生成
QADC有四个独立的中断源,对应两个队列的完成和暂停事件。中断使能在QACR1/QACR2中(CIE, PIE),中断优先级和向量号在QADCINT寄存器中配置。
QADCINT (QADC Interrupt Register) 关键字段:
IRLQ1[2:0],IRLQ2[2:0]: 分别设置队列1和队列2中断请求的优先级级别(1-7)。设为0则禁用该队列的所有中断。IVB[7:2]:中断向量基值。这是软件设置的6位高半部分。- 低2位
IV[1:0]由硬件根据中断源自动提供:00: 队列1完成中断01: 队列1暂停中断10: 队列2完成中断11: 队列2暂停中断
因此,完整的中断向量号 =(IVB << 2) | IV。CPU用这个8位向量号乘以4,作为偏移量去异常向量表中查找中断服务程序的入口地址。
4.2 中断初始化的标准流程
分配仲裁优先级(IARB):在模块配置寄存器QADCMCR中,给QADC模块分配一个唯一的、非零的仲裁ID(1-15)。当多个模块同时请求同一中断级别时,CPU通过这个ID来决定谁先被服务。必须设置,否则中断可能无法被正确响应。
// 设置 QADC 模块中断仲裁优先级为 5 (二进制0101) QADC->QADCMCR |= (5 << 12); // 假设 IARB 字段在 bit15-12配置中断级别和向量基值:
// 设置队列1中断级别为3,队列2中断级别为2 // 设置中断向量基值为 0x2A (假设我们想将向量号定位在 0xA8-0xAB 范围) // IVB = 0x2A = 0b101010 // IRLQ1=3 (0b011), IRLQ2=2 (0b010) // 寄存器值: IVB(0x2A)左移2位 = 0xA8, 加上 IRLQ1 和 IRLQ2 // 格式: [IVB7:2][0][IRLQ2][0][IRLQ1] (具体位域需查手册) // 假设手册定义: Bits15-10: IVB, Bits7-5: IRLQ2, Bits2-0: IRLQ1 uint16_t qadc_int_val = (0x2A << 10) | (2 << 5) | (3 << 0); QADC->QADCINT = qadc_int_val;这样配置后,队列1完成中断的向量号将是
(0x2A << 2) | 0x00 = 0xA8。需要在CPU的异常向量表(如VBR指向的表)的0xA8 * 4地址处,填入对应的中断服务程序(ISR)入口。使能具体中断:在QACR1中使能CIE1或PIE1。
4.3 中断服务程序(ISR)编写要点
一个健壮的QADC中断服务程序应该遵循以下步骤:
void __attribute__((interrupt)) QADC1_Complete_ISR(void) { // 1. 读取状态寄存器,判断中断源(虽然向量已区分,但有时需确认) vuint16_t status = QADC->QASR; // 2. 处理数据:从结果字表读取转换结果 for(int i=0; i<MY_QUEUE1_LENGTH; i++) { adc_buffer[i] = RWT[i].right_justified.result; } // 3. 清除中断标志(严格的读-改-写序列!) // 注意:只清除当前中断源对应的标志位,避免影响其他可能同时置位的标志 QADC->QASR = (status & ~QASR_CF1_MASK); // 清除CF1标志 // 4. 如果是单次扫描模式,并且需要再次启动,可以在这里置位SSE1位 // if (need_restart) { // QADC->QACR1 |= QACR1_SSE_MASK; // } // 5. 其他必要的软件操作,如通知任务、设置标志等。 }高级避坑技巧:中断嵌套与重入:MC68336的CPU32内核支持中断嵌套。如果QADC的中断服务程序执行时间较长,且其中又可能触发新的QADC中断(例如在连续扫描模式下,处理速度跟不上转换速度),就会导致中断重入,可能造成数据错乱或栈溢出。解决方法:
- 在ISR入口处暂时禁止该中断级别:通过修改CPU状态寄存器(SR)或中断屏蔽寄存器。
- 使用“后台任务”模式:ISR只做最少的必要工作(如读取数据、清除标志),然后将数据拷贝到一个缓冲区,并设置一个软件标志。主循环或一个低优先级任务检测到这个标志后,再进行耗时的数据处理。这是更推荐的做法,能保证中断响应及时,不影响后续转换的触发。
5. 典型应用场景配置与调试心得
5.1 场景一:多通道周期性巡检(队列2 + 定时器触发)
这是最常见的应用。假设我们需要每10ms对16个模拟通道(AN0-AN15)进行一次轮询。
配置思路:
- 使用队列2:因为这是常规任务,优先级不高。
- 触发模式:设置为内部定时器触发(
MODE=11)。需要配置QADC的内部定时器周期为10ms。 - 扫描模式:连续扫描(
SSC=0)。这样定时器会周期性地触发整个队列。 - CCW表:编排16个条目,分别对应通道0到15。每个CCW设置合适的采样时间(IST)。最后一个CCW后跟一个结束标志(或通过BQ2定义队列尾)。
- 中断:使能队列2的完成中断(
CIE2=1)。在ISR中读取16个结果。
关键计算:定时器周期值QADC定时器时钟来源于系统时钟分频。假设系统时钟为20MHz,QADC预分频器设置为4,则QCLK = 5MHz,周期为0.2us。 要求定时器周期T = 10ms = 10000us。 定时器计数值 N = T / (QCLK周期) = 10000us / 0.2us = 50000。 由于定时器是16位计数器,最大值65535,50000在范围内。需要将50000写入定时器周期寄存器。
调试记录:在实际测试中,发现10ms的定时非常准,但偶尔会丢失一次中断。排查发现是因为ISR处理时间偶尔超过10ms,导致下一次中断到来时,上一次的CF2标志还未清除(CPU还在ISR中),造成了触发溢出(TOR2置位)。解决方法:一是优化ISR代码,减少处理时间;二是改为在ISR中只读取数据到缓冲区并清除标志,数据处理放到主循环;三是适当降低采样频率。
5.2 场景二:关键通道快速响应与同步采样(队列1 + 外部触发 + 暂停)
假设有三个电流传感器(AN0, AN1, AN2),需要在外部的PWM同步信号上升沿时,立即进行同步采样。
配置思路:
- 使用队列1:保证最高优先级,能被立即响应。
- 触发模式:外部触发(
MODE=10),配置为上升沿触发。 - 扫描模式:单次扫描(
SSC=1)或连续扫描均可。若每个PWM周期都需要采样,用连续扫描。 - CCW表:编排三个条目,分别对应AN0, AN1, AN2。在每个CCW中都设置暂停位(P=1)。不设置队列结束标志(或将其放在很远的位置)。
- 中断:使能队列1的暂停中断(
PIE1=1)。
工作流程:
- 外部PWM上升沿触发队列1。
- QADC转换AN0,完成后暂停,置位PF1并产生中断。
- 在ISR中,读取结果字表第一个位置(AN0的结果),然后通过软件命令(写SSE1位)再次触发队列1。
- QADC转换AN1,完成后再次暂停并中断。
- ISR读取AN1结果,再次软件触发。
- 如此反复,完成AN2采样。
- 这样,三个通道都是在同一个外部触发边沿后的极短时间内被依次采样,实现了高精度的“准同步”采样。虽然严格来说有微小的时间差(转换时间),但对于大多数电机控制应用已足够精确。
5.3 初始化代码框架总结
一个完整的QADC初始化函数可能包含以下步骤:
void QADC_Init(void) { // 1. 全局模块使能/配置 (QADCMCR) QADC->QADCMCR |= (1 << 15); // 使能模块 QADC->QADCMCR |= (5 << 12); // 设置IARB优先级 // 2. 配置中断寄存器 (QADCINT) QADC->QADCINT = (0x2A << 10) | (2 << 5) | (3 << 0); // 设置优先级和向量基值 // 3. 编排CCW表 Setup_CCW_Table(); // 4. 配置队列控制寄存器 // 队列1: 外部触发,单次扫描,使能完成中断 QADC->QACR1 = 0x5200; // MODE=10, SSC=1, CIE=1 // 队列2: 定时器触发,连续扫描,使能完成中断,定义起始位置 QADC->QACR2 = (0x3 << 14) | (1 << 11) | (16 << 8); // MODE=11, SSC=0, CIE=1, BQ2=16 // 5. 配置定时器(如果使用) QADC->TICR = 50000; // 设置定时器间隔 // 6. 使能队列(对于单次扫描,还需要后续写SSE位启动) // QADC->QACR1 |= QACR1_SSE_MASK; // 软件启动一次队列1扫描 }最后,再强调一个终极避坑指南:仔细阅读数据手册中关于模拟输入引脚复用的部分。MC68336/376的很多引脚是复用的,默认可能是数字IO功能。在使用某个通道前,必须通过系统集成模块(SIM)的引脚分配寄存器,将其配置为模拟输入功能,否则ADC读到的将是数字电平或浮空值,导致结果错误。这个坑我见过太多人踩进去了。