极海芯得 EP.81 | G32R430 ADC16 全用例实战:从单通道采样到双ADC同步解算

来源:Geehy极海半导体 MCU/MPU 151 次阅读
摘要:1. 背景 前面我们已经看过 G32R430 在编码器场景里的“专业对口”,也围观过 ATAN2 的速度表演。这一篇轮到 ADC16 登场:毕竟角度算法再快,也架不住前端采样“手抖”。 可以把它想成一条流水线:ATAN2 是后厨大厨,ADC16 是前厅点单。后厨刀工再秀,前厅把菜名听错了,最后端上来的也只能是玄学料理。 这篇博客我们一起来做三件事: 先把 ADC 这件事讲清楚,知道它在编码器链

1. 背景

前面我们已经看过 G32R430 在编码器场景里的“专业对口”,也围观过 ATAN2 的速度表演。这一篇轮到 ADC16 登场:毕竟角度算法再快,也架不住前端采样“手抖”。

可以把它想成一条流水线:ATAN2 是后厨大厨,ADC16 是前厅点单。后厨刀工再秀,前厅把菜名听错了,最后端上来的也只能是玄学料理。

这篇博客我们一起来做三件事:

  • 先把 ADC 这件事讲清楚,知道它在编码器链路里为什么是第一环;

  • 再把 G32R430 的 ADC16 能力讲清楚,知道每个能力能解决什么工程问题;

  • 最后把 SDK 里的ADC16 示例逐个拆开,看看“共性”和“差异”都在哪儿。

2. ADC 是什么?

ADC(Analog to Digital Converter)就是把模拟电压变成数字量的外设。在 MCU 工程里,它不只是“测电压”,而是“把传感器信号送进算法”的入口。放到编码器场景里(磁编/旋变 sin/cos)我们会马上遇到三个问题:

  • 两路采样不同步,atan2 输入就会错位;

  • 触发时刻不稳定,角度会抖,控制环会被噪声放大;

  • 没有硬件阈值监测,异常输入会把系统保护压力全扔给软件。

所以我们看 ADC,不能只盯分辨率,还要同时看时序确定性、触发机制、数据路径和保护能力。

3. G32R430 的 ADC16

模块情况:G32R430 系列集成 2 个 16 位精度、逐次逼近(SAR)型 ADC。每个 ADC 最多 6 个外部通道,支持单次、连续、扫描或间断等转换模式;双 ADC 同步时,转换结果可存储在主 ADC(ADC0)的 32 位数据寄存器中,便于一次读出两路同步采样值。

主要特征(与本文例程对应关系如下):

  • 精度与速率:16 位分辨率,ENOB 约 13.5 bit;内置高精度参考源 1.65V±2mV(25℃);16 bit 分辨率下最大采样率 1 Msps。

  • 输入与通道:支持单端和差分输入;通道数:最多 6 对差分 或 12 个单端。

  • 转换模式:支持规则序列、注入序列,以及单次、连续、扫描、间断等模式;转换结果可存 16/32 位结果寄存器或经 DMA 传到 RAM。

  • 双 ADC:两路 ADC 支持主从模式,配置为主从时可保持采样同步;双 ADC 同步采样可由软件或硬件触发。

  • 过采样:支持最高 16 倍过采样。

  • 触发方式:每个 ADC 可独立选择触发源——软件触发、片上定时器信号触发、外部引脚触发;双 ADC 同步采样时由同一触发源驱动。

  • 其它:支持外部参考电压引脚(输入范围可独立于供电);可编程通道采样时间;每通道偏移补偿;模拟看门狗(自动电压监测、产生中断或触发定时器)。

这些能力对应到工程收益,我们可以这么记:

  • 同步采样:给 sin/cos、双电流这种相位敏感输入兜底;

  • 差分采样:在强干扰现场提升抗共模能力;

  • 定时触发:把采样时刻锁到控制节拍;

  • 过采样:用吞吐换稳定度和有效精度;

  • 模拟看门狗:越界检测前移到硬件,提高响应速度。

ADC16 时钟来源:ADC1/2 的模拟时钟的源时钟可为 PLL 或 SYSCLK,由 RCM 模块中的 ADCACLKSEL(RCM_ADCCR 寄存器 bit 8)选择:0 选 PLL,1 选 SYSCLK。源时钟再经 ADC16ADIV(RCM_ADCCR[1:0])分频得到 ADCCLK,分频比可选 6、12、24(对应 SDK 中的 DIV_6 / DIV_12 / DIV_24)。ADCCLK 直接决定单次转换的节拍,进而影响采样率上限与转换时间,配置时需保证不超过芯片手册规定的 ADCCLK 上限;使能 ADC 后需等待 ADC16ACLKRDY(RCM_ADCCR bit 2)为 1 再启动转换。时钟来源与分频在芯片时钟树中的位置见下图。

4. 上手前提:同一块板、同一套口径

下面我们先把“用哪块板、跑哪些例程、怎么看结果”说清楚,后面所有示例都按这一套来,方便复现和对照。

4.1. 使用的平台与板卡

本文示例基于 Geehy G32R430 TinyBoard V1.2。板子分为上、下两段:上方为板载调试器(APM32F103 作 Link 调试/烧录),通过 USB-C(J13)供电并做串口调试;下方为 G32R430 目标板,带按键、LED、以及丰富的排针与专用接口。和 ADC16 直接相关的是左侧 J11 模拟排针,提供单端/差分 ADC 引脚:AD1P/AD1M、AD2P/AD2M,对应 ADC1/ADC2 的差分或单端接入;右侧 J12 为通用 GPIO/模拟(如 A1~A5)等。板子还预留 BiSS-C(J6)、RS-485(J4)等接口,便于做编码器或通信类实验。板卡实物见下图。

  • 板卡:G32R430 TinyBoard V1.2

  • SDK:G32R430_DDL_SDK_V1.0.2

  • 示例目录:Examples/Board_G32R430_Tiny/ADC16

  • IDE:μVision V5.43.1.0

  • 串口:USART2,115200(通过板载调试器,无需外接仿真器)

4.2. 我们怎么观察结果

以每个示例的 main.c 为主,重点看初始化和数据处理路径,结果以串口输出作为第一验证口径。每个例程目录下都有 readme.txt 说明文档,一般包含功能说明、硬件连接与预期现象,复现时可先对照阅读以便核对串口输出或接线。

5. 先跑通基础示例:ADC16_ContinuousConversion

本章我们来看一个完成基本转换的示例。

5.1 main() 主流程

int main(void)

{

    DDL_USART_InitTypeDef USART_InitStruct = {0U};

    DDL_SysClkConfig(); 

    DDL_NVIC_ConfigPriorityGroup(DDL_NVIC_PRIORITY_GROUP_4);

    USART_InitStruct.BaudRate = 115200U;

    USART_InitStruct.DataWidth = DDL_USART_DATAWIDTH_8B;

    USART_InitStruct.StopBits = DDL_USART_STOPBITS_1;

    USART_InitStruct.Parity = DDL_USART_PARITY_NONE;

    USART_InitStruct.TransferDirection = DDL_USART_DIRECTION_TX_RX;

    USART_InitStruct.HardwareFlowControl = DDL_USART_HWCONTROL_NONE;

    USART_InitStruct.OverSampling = DDL_USART_OVERSAMPLING_16;

    BOARD_COMInit(COM2, &USART_InitStruct);

    ADC_Init();

    while (1) {}

}

我们先抓主线:

  1. DDL_SysClkConfig():先把系统时钟准备好,ADC 才有稳定节拍;

  2. BOARD_COMInit():先打通串口,后面调试结果能直接看到;

  3. ADC_Init():把 ADC 的通道、触发、转换模式一次配齐;

5.2 ADC_Init() 关键配置

DDL_RCM_ADC_SetAdcAnalogClkSource(DDL_RCM_ADCACLK_SYSCLK);

DDL_RCM_ADC_SetAdc16AnalogClkDivision(DDL_RCM_ADC16ACLK_DIV_6);

DDL_RCM_EnableAHBPeripheral(DDL_RCM_AHB_PERIPHERAL_GPIO);

DDL_RCM_EnableAHBPeripheral(DDL_RCM_AHB_PERIPHERAL_ADC1);

GPIO_InitStruct.Pin = DDL_GPIO_PIN_1;

GPIO_InitStruct.Mode = DDL_GPIO_MODE_ANALOG;

DDL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  • ADC 时钟源与分频:SetAdcAnalogClkSource(SYSCLK) 表示 ADC 模拟时钟来自系统时钟;SetAdc16AnalogClkDivision(DIV_6) 表示再对 SYSCLK 做 6 分频,得到 ADC 内核实际用的时钟 ADCCLK = SYSCLK / 6。例如 SYSCLK = 120 MHz 时,ADCCLK = 20 MHz。一次转换所花的时间 =(采样时间 + 转换时间),单位都是 ADCCLK 的周期。采样时间由我们配置(如 10 cycles),转换时间由芯片固定(16 位 ADC 一般为十几 cycles,以数据手册为准)。所以 ADCCLK 越高,单次转换时间越短,理论上可达到的采样率上限就越高;分频选得越大(如 DIV_12、DIV_24),ADCCLK 越低,采样节拍上限就越低。ADCCLK 不宜超过芯片规定上限,否则可能影响线性度。

  • 打开 GPIO/ADC1 外设时钟,不开时钟后面配置都白写;

  • PA1 置模拟模式,接入 ADC1_CHANNEL_1。

ADC_REG_InitStruct.TriggerSource = DDL_ADC16_REG_TRIG_SOFTWARE;

ADC_REG_InitStruct.SequencerLength = DDL_ADC16_REG_SEQ_SCAN_DISABLE;

ADC_REG_InitStruct.ContinuousMode = DDL_ADC16_REG_CONV_CONTINUOUS;

ADC_REG_InitStruct.DMATransfer = DDL_ADC16_REG_DMA_TRANSFER_NONE;

ADC_REG_InitStruct.Overrun = DDL_ADC16_REG_OVR_DATA_OVERWRITTEN;

DDL_ADC16_REG_Init(ADC1, &ADC_REG_InitStruct);

DDL_ADC16_SetChannelSamplingTime(ADC1, DDL_ADC16_CHANNEL_1, DDL_ADC16_SAMPLINGTIME_10CYCLES);

DDL_ADC16_REG_SetSequencerRanks(ADC1, DDL_ADC16_REG_RANK_1, DDL_ADC16_CHANNEL_1);

DDL_ADC16_SetVerfExternal(ADC1);

DDL_ADC16_SetChannelSingleDiff(ADC1, DDL_ADC16_CHANNEL_1, DDL_ADC16_SINGLE_ENDED);

  • 软件触发:方便我们先把流程跑通;

  • 关闭扫描:当前只采单通道;

  • 连续模式:一次启动后持续采样;

  • DMA 先不用:让数据路径更直观,先用 EOC 中断读数;

  • 通道采样时间、rank、参考电压、单端模式都在这里定型。

DDL_ADC16_EnableIT_EOC(ADC1);

DDL_Interrupt_Register(ADC1_IRQn, ADC_Isr);

DDL_NVIC_EnableIRQRequest(ADC1_IRQn, 1, 1);

DDL_ADC16_Enable(ADC1);

while (DDL_RCM_ADC_IsAdc16AnalogClkRDY() == RESET);

DDL_ADC16_REG_StartConversion(ADC1);

  • 使能 EOC 中断并绑定 ISR;

  • ADC 使能后等待模拟时钟 ready;

  • 最后发出 StartConversion,采样链路正式开跑。

5.3. ADC_Isr() 数据路径(读数 -> 换算 -> 输出)

void ADC_Isr(void)

{

    uint16_t adcData = 0;

    uint16_t voltage = 0;

    if(DDL_ADC16_IsActiveFlag_EOC(ADC1))

    {

        adcData = DDL_ADC16_REG_ReadConversionData32(ADC1);

        voltage = (adcData * 3300) / 65535;

        printf("\r\n voltage : %d mV\r\n", voltage);

        DDL_ADC16_ClearFlag_EOC(ADC1);

    }

}

这一段我们按三步记就行:

  1. 读原始码值;

  2. 按 3.3V 口径换算 mV(voltage_mV = adcData * 3300 / 65535);

  3. 打印并清中断标志,等待下一次 EOC。

这里的 65535 来自 16 位 ADC 的满量程码值:16 位无符号数范围是 0 ~ (2^16 - 1),也就是 0 ~ 65535。所以当 adcData = 65535 时对应满量程(约 3.3V),当 adcData = 0 时对应约 0V,中间按线性比例换算。

到这里,基础链路就是:

单端 + 单通道 + 软件触发 + 连续转换 + EOC中断处理

6. 快速部署:例程来助力

Geehy 官方 SDK 里提供了多组 ADC16 示例工程,从单通道连续转换到双 ADC 差分同步,覆盖我们前面提到的各种用法。下面我们对例程的关键代码和使用场景进行简单分析。

6.1 单通道连续转换(软件触发): ADC16_ContinuousConversion

单通道、软件触发、连续转换,无 DMA/无外部触发,是最小可跑配置,适合第一次验证“ADC 能亮串口”。

适用场景:单路慢速电压或温度/光照等传感器采样,以及第一次把 ADC 跑通做验证。配置最少、不碰 DMA 和外部触发,方便先确认硬件和软件链路没问题,再往上加功能。

6.2 定时器触发单次采样: ADC16_SingleRegulTmrTrigger

在单通道基础上增加定时器触发:采样时刻由 TMR1 的 update 事件决定,便于和控制周期对齐。

关键代码 1:TMR 产生周期触发

stcTmrInit.Prescaler         = 1199;   /* 120MHz/(1199+1)=100kHz */

stcTmrInit.Autoreload        = 49999; /* 50000/100kHz = 500ms 周期 */

DDL_TMR_Init(TMR1, &stcTmrInit);

DDL_TMR_SetTriggerOutput(TMR1, DDL_TMR_TRGO_UPDATE);  /* update 事件当 TRGO */

DDL_TMR_EnableCounter(TMR1);

TMR1 每 500ms 产生一次 update,从 TRGO 脚输出给 ADC,ADC 只在此时刻采样,实现“节拍对齐”。

关键代码 2:ADC 改为外部触发 + 单次

ADC_REG_InitStruct.TriggerSource = DDL_ADC16_REG_TRIG_EXT_TMR1_TRGO;

ADC_REG_InitStruct.ContinuousMode = DDL_ADC16_REG_CONV_SINGLE;

DDL_ADC16_REG_Init(ADC1, &ADC_REG_InitStruct);

ADC采样的触发源改为 TMR1 TRGO,连续模式关掉,每次 TRGO 来一次就采一次,下一轮再等下一个 TRGO。

适用场景:采样时刻必须和 PWM 或控制周期对齐时(例如 FOC 里每 PWM 周期采一次电流)。用定时器 TRGO 触发 ADC,采样点由硬件定时,不会因软件延迟或任务调度产生时间抖动,控制环更稳。

6.3. 模拟看门狗: ADC16_AnalogWindowWatchdog

在单通道连续转换上增加模拟看门狗:硬件比较上下限,越界即进 AWD 中断,适合做过压/欠压或异常幅值告警。

门限电压与码值换算

AWD 的上下限填的是 ADC 码值(0~65535,16 位满量程)。若希望按具体电压设门限,需先把电压换算成码值:

例如 Vref=3.3V 时:

  • 高限 2.0V → (Code_{high} = 2.0 \times 65535 / 3.3 \approx 39718),可取整为 0x9B36;

  • 低限 0.5V → (Code_{low} = 0.5 \times 65535 / 3.3 \approx 9930),可取整为 0x26CA。

反之,已知码值算电压:(V = Code \times V_{ref} / 65535)。下面示例里的 0xB00(2816)、0x300(768)对应约 0.142V、0.039V(Vref=3.3V),相当于一个很窄的“正常窗口”,仅作演示用;实际工程中按你需要的过压/欠压电压用上式算出码值再填入。

关键代码 1:阈值与监测通道

例程 ADC16_AnalogWindowWatchdog 中阈值配置如下(与上文公式一致:码值 × 3300 / 65535 即得 mV):

/* Set the upper and lower threshold of AWD1 for ADC1. */

/* Upper threshold voltage: 142 mV = 0xB00 * 3300 / 65535 */

/* Lower threshold voltage:  39 mV = 0x300 * 3300 / 65535 */

DDL_ADC16_ConfigAnalogWDThresholds(ADC1, DDL_ADC16_AWD1, 0xB00, 0x300);

DDL_ADC16_EnableIT_AWD1(ADC1);

例程中还会调用 DDL_ADC16_SetAnalogWDMonitChannels(ADC1, DDL_ADC16_AWD1, DDL_ADC16_AWD_CHANNEL_1_REG) 指定监视规则组 CH1,以及 DDL_ADC16_EnableIT_AWD1(ADC1) 使能 AWD1 中断。简单说就是:用码值配置 AWD1 上下限,并指定监视通道与中断;当转换结果高于高限或低于低限时触发 AWD1 中断,无需软件轮询比较。

关键代码 2:中断里区分 AWD 与 EOC

void ADC_Isr(void)

{

    uint16_t adcData = 0;

    uint16_t voltage = 0;

    if (DDL_ADC16_IsActiveFlag_AWD1(ADC1))

    {

        BOARD_LEDOn(LED2);

        iAWD_Flag = 1;

        DDL_ADC16_ClearFlag_AWD1(ADC1);

    }

    if (DDL_ADC16_IsActiveFlag_EOC(ADC1))

    {

        adcData = DDL_ADC16_REG_ReadConversionData32(ADC1);

        voltage = (adcData * 3300) / 65535;   /* 码值转 mV,Vref=3.3V */

        if (iAWD_Flag == 1)

        {

            printf("\r\nADC1 Analog Window Watchdog is active.\r\n");

            iAWD_Flag = 0;

        }

        else

        {

            BOARD_LEDOff(LED2);

        }

        printf("\r\n voltage : %d mV\r\n", voltage);

        Delay(0x4FFFFF);

        DDL_ADC16_ClearFlag_EOC(ADC1);

    }

}

简单说就是:AWD 先到则打灯并置标志、清 AWD 标志;EOC 到则读码值、换算成电压(mV)、若曾触发 AWD 则打印告警并清标志否则关灯、打印电压并延时,最后清 EOC。与例程 ADC16_AnalogWindowWatchdog 一致。下图为模拟看门狗例程运行时的串口与 LED 表现,便于验证越界告警与电压打印。

适用场景:需要对输入电压做“越界即告警”的保护(过压、欠压、或幅值异常)。阈值和监测通道在硬件里配置,一旦越界立刻进 AWD 中断,不依赖软件轮询,响应快、也不占主循环时间。

6.4. 过采样: ADC16_OversampleMode

在单通道连续转换上开启过采样:多次采样再平均/移位,用吞吐换稳定度。G32R430 的过采样支持 2/4/8/16 倍过采样率,适合对精度或稳定性要求高、而采样率不必很高的场景。

过采样公式与寄存器

ADC 过采样的计算公式如下(手册中的定义可参考下图):

过采样组合与标志位

ADC16 为 16 位满量程(单次最大 0xFFFF),不同 N 与移位组合下,累加后的最大值及右移后写入 DR 的范围见下表。单次转换时间在过采样模式下不变,每 N 次转换才给出一个有效结果,总延迟为 (N \times T_{CONV});EOC 每 N 次转换置 1 一次,EOS 在整段 N 次序列结束时置 1,编程时可按 EOC/EOS 取数。

说明:最大转换数据 = N × 0xFFFF(16 位单次满量程)。各移位列 = 累加和右移 M 位;若结果 > 0xFFFF 则标为 >16b(超出 DR 位宽,可能截断或需避免该组合)。加粗的 0xFFFF 表示“取平均”组合(M 使 N÷2^M = 1),此时 DR 仍为 0~65535,电压换算分母用 65535。

各组合下的最大量程与适用电压区间(Vref=3.3V)

下表合并所有 N/M 组合:结果 ≤16 位时 DR 满码对应 3.3V;结果 >16 位时 0xFFFF 对应“未溢出时的电压上限”(如 2x No-shift 为 3.3/2=1.65V),仅在此电压以下 DR 与电压线性对应。换算一律 V = adcData × 满量程电压 / DR最大值。

说明:满量程电压 = 该组合下 DR 满码对应的输入电压(≤16b 时为 3.3V;>16b 时为 3.3V×(0xFFFF/累加移位后满值),超出后溢出)。按实际被测电压区间选行,用该行「电压换算」列公式。本表仅适用于参考电压 Vref=3.3V、单端通道;差分通道或其它 Vref/量程需按相同思路另行计算。

关键代码

DDL_ADC16_SetOverSamplingScope(ADC1, DDL_ADC16_OVS_GRP_REGULAR_CONTINUED);

DDL_ADC16_SetOverSamplingDiscont(ADC1, DDL_ADC16_OVS_REG_CONT);

DDL_ADC16_ConfigOverSamplingRatioShift(ADC1, DDL_ADC16_OVS_RATIO_2, DDL_ADC16_OVS_SHIFT_RIGHT_1);

总结一下,对规则组连续转换做过采样;RATIO_2 表示 2 次采样累加(N=2),SHIFT_RIGHT_1 表示结果右移 1 位(÷2),即取平均,起到平滑噪声的作用。

从 DR 到实际电压的代码(ADC16 为 16 位满量程)

G32R430 的 ADC16 单次转换结果为 16 位满量程:码值 0~65535 对应 0~Vref。在 RATIO_2 + SHIFT_RIGHT_1(N=2、M=2)下,硬件做的是两次采样求和再右移 1 位,即两次取平均:

  • 单次转换范围:0~65535

  • 两次之和:0~131070

  • 右移 1 位(÷2)后:0~65535

因此过采样后的 DR 仍为 0~65535,满量程还是 16 位,只是每个码值是两次采样的平均,噪声更小。换算电压时分母用 65535,与“单次转换”一致:

过采样完成后,在 EOC 中断里按“一次有效结果”读一次 DR 即可。下图为过采样例程运行时的串口输出,便于对照实际码值与电压换算结果。

示例(Vref=3.3V,结果单位 mV):

void ADC_Isr(void)

{

    if (DDL_ADC16_IsActiveFlag_EOC(ADC1))

    {

        uint16_t adcData = (uint16_t)DDL_ADC16_REG_ReadConversionData32(ADC1);  /* 从 DR 读 16 位 */

        /* ADC16 为 16 位满量程,2x+1bit 后 DR 仍为 0~65535,故分母用 65535: */

        uint32_t voltage_mV = (uint32_t)adcData * 3300U / 65535U;

        DDL_ADC16_ClearFlag_EOC(ADC1);

    }

}

适用场景:被测信号变化慢、但对精度或稳定性要求高(如直流母线电压、温度、基准监测)。过采样把多次采样平均或移位,能压低随机噪声、提高有效位数,代价是单次“有效采样”的时间变长,适合不追求极高采样率的场合。

6.5. 多通道扫描 + DMA: ADC16_MultiChannelScan

多通道扫描 + DMA:ADC 按 rank 顺序扫多路,每路结果由 DMA 自动搬到内存,CPU 只需在 DMA 完成时处理一批数据。

关键代码 1:DMA 从 ADC 搬到数组

DMA_InitStruct.PeriphOrM2MSrcAddress  = (uint32_t)&ADC1->DR;

DMA_InitStruct.MemoryOrM2MDstAddress  = (uint32_t)&adcData;

DMA_InitStruct.Direction              = DDL_DMA_DIRECTION_PERIPH_TO_MEMORY;

DMA_InitStruct.Mode                   = DDL_DMA_MODE_CIRCULAR;

DMA_InitStruct.NbData                 = ADC_CH_SIZE;   /* 与 rank 数一致 */

DDL_DMA_Init(DMA1, DDL_DMA_STREAM_0, &DMA_InitStruct);

注意:G32R430 的 ADC16 与 DMA 之间的数据搬运只能按 32 位(word)进行,即源端 ADC1->DR 每次以 32 位被读出、DMA 每次搬运 32 位到内存。多通道时每个通道的 16 位结果在 DR 中占 32 位空间,配置 DMA 时数据宽度与 NbData 需与此一致,勿按 16 位宽度配置,否则会导致错位或搬运异常。

源是 ADC1->DR,每完成一路转换 DR 更新,DMA 按 32 位搬一 word;目的为 adcData[],循环模式,搬满 3 个 word 后可根据 TC 标志处理并再次启动 ADC。

关键代码 2:规则组 3 rank + 单次 + DMA

ADC_REG_InitStruct.SequencerLength  = DDL_ADC16_REG_SEQ_SCAN_ENABLE_3RANKS;

ADC_REG_InitStruct.ContinuousMode   = DDL_ADC16_REG_CONV_SINGLE;

ADC_REG_InitStruct.DMATransfer      = DDL_ADC16_REG_DMA_TRANSFER_UNLIMITED;

DDL_ADC16_REG_SetSequencerRanks(ADC1, DDL_ADC16_REG_RANK_1, DDL_ADC16_CHANNEL_0);

DDL_ADC16_REG_SetSequencerRanks(ADC1, DDL_ADC16_REG_RANK_2, DDL_ADC16_CHANNEL_1);

DDL_ADC16_REG_SetSequencerRanks(ADC1, DDL_ADC16_REG_RANK_3, DDL_ADC16_CHANNEL_3);

简单说就是,一次软件启动后依次采 rank1/2/3,结果依次进 DR,DMA 按顺序写入 adcData[0..2];单次模式下一轮扫完就停,需在 TC 里再次 StartConversion。

适用场景:要同时采多路模拟量(如多路电压、电流、温度),又希望 CPU 少参与搬运。ADC 按 rank 顺序扫多路,DMA 自动把结果写入数组,CPU 只在“一轮扫完”时处理一批数据即可,中断负担小、时序更可控。

6.6. 连续多通道扫描: ADC16_ContinuousMultiChannelScan

在 MultiChannelScan 基础上改为连续扫描:一次启动后 ADC 一直扫,DMA 循环写同一块 buffer,无需每次软件重启 ADC。

关键差异(相对 6.5)

ADC_REG_InitStruct.ContinuousMode = DDL_ADC16_REG_CONV_CONTINUOUS;

区别就这一句:连续模式下一轮 3 通道扫完自动开始下一轮,DMA 持续把结果写入 adcData[],主循环或 DMA 半满/全满中断里处理即可,不用再调 StartConversion。

适用场景:需要持续监控多路状态量(温度、母线电压、辅助电源等),数据要连续更新。连续扫描 + DMA 循环写 buffer,一次启动后无需反复软件触发,主循环或半满/全满中断里周期性处理即可,适合“常开”的监控类应用。

6.7. 差分输入: ADC16_DifferentialMode

单 ADC、差分输入(DP/DM),结果是有符号数,适合共模噪声大的长线信号。

关键代码:差分配置与读数

DDL_ADC16_SetChannelSamplingTime_Diff(ADC1, DDL_ADC16_DIFF_CHANNEL0, DDL_ADC16_SAMPLINGTIME_10CYCLES);

DDL_ADC16_SetSequencerRanks_Diff(ADC1, DDL_ADC16_REG_RANK_1, DDL_ADC16_DIFF_CHANNEL0);

DDL_ADC16_SetChannelSingleDiff(ADC1, DDL_ADC16_CHANNEL_0, DDL_ADC16_DIFFERENTIAL_ENDED);

sAdcData = (int16_t)DDL_ADC16_REG_ReadConversionData32(ADC1);

fVoltage = ((float)sAdcData) * (3.3f / 32768.0f);  /* 双极性:±Vref 对应 ±32768 */

简单说就是,采样与 rank 用 _Diff 接口绑定差分通道;结果按 int16_t 解读,满量程对应 ±Vref,所以换算用 3.3/32768。

适用场景:传感器走线较长、地线共模噪声明显,或信号本身是差分输出(如部分电流采样、桥式传感器)。差分输入只放大两线之差,共模干扰被抑制,读数更稳;结果是有符号数,正负电压都能表示。

6.8. 双 ADC 同步单次: ADC16_DualRegulSimulMode

双 ADC 同步、单次:ADC1 与 ADC2 在同一时刻各采一路,结果合并成 32 位从 ADC1 读出,适合“同一时刻”抓两路(如 sin/cos 的某一拍)。

关键代码 1:双 ADC 同步模式 + 两路 rank

DDL_ADC16_SetMultimode(ADC1, DDL_ADC16_MULTI_DUAL_REG_SIMULT);

DDL_ADC16_SetChannelSamplingTime(ADC1, DDL_ADC16_CHANNEL_1, DDL_ADC16_SAMPLINGTIME_10CYCLES);

DDL_ADC16_REG_SetSequencerRanks(ADC1, DDL_ADC16_REG_RANK_1, DDL_ADC16_CHANNEL_1);

/* ADC2 同样配 CH1、rank1 */

DDL_ADC16_REG_Init(ADC2, &ADC_REG_InitStruct);

DDL_ADC16_SetChannelSamplingTime(ADC2, DDL_ADC16_CHANNEL_1, ...);

DDL_ADC16_REG_SetSequencerRanks(ADC2, DDL_ADC16_REG_RANK_1, DDL_ADC16_CHANNEL_1);

核心就这句:SetMultimode(..., DUAL_REG_SIMULT) 使 ADC1/ADC2 在同一触发下同时转换;两边 rank1 各绑一个通道,一次触发得到两路“同一时刻”的采样。

关键代码 2:读 32 位合并结果

adcData = DDL_ADC16_REG_ReadConversionData32(ADC1);

adc1_data = (adcData & 0xFFFF);

adc2_data = (adcData >> 16) & 0xFFFF;

简单说就是:硬件把 ADC1 结果放低 16 位、ADC2 放高 16 位;单次模式下每轮读完需软件再次 StartConversion(ADC1) 触发下一对。

适用场景:需要在“同一时刻”抓两路模拟量(例如某一拍的两相电流、或 sin/cos 的某一瞬态)。双 ADC 由同一触发同时启动,结果合并成 32 位一次读出,两路时间完全对齐,没有通道间相位差,适合做瞬态分析或单次同步记录。

6.9. 双 ADC 连续同步: ADC16_ContinuousDualRegulSimulMode

在 DualRegulSimulMode 上改为连续:双 ADC 持续同步采样,无需每次软件重启。

关键差异

ADC_REG_InitStruct.ContinuousMode = DDL_ADC16_REG_CONV_CONTINUOUS;  /* ADC1、ADC2 均设为 CONTINUOUS */

简单说就是:一次 StartConversion(ADC1) 后,ADC1/ADC2 持续同步转换,结果仍从 ADC1 以 32 位读出并拆分,适合编码器 sin/cos 或双电流的连续同步采样。

适用场景:编码器 sin/cos 或双路电流等需要“持续、且两路严格同步”的采样。双 ADC 连续同步转换,每次得到一对时间对齐的 (x,y),直接可喂给 atan2 或电流环;一次启动长期有效,无需每拍软件触发,适合高节拍控制环。

6.10. 双 ADC 双路差分: ADC16_DualDifferentialMode

双 ADC + 双路差分:ADC1/ADC2 各采一路差分,仍为同步、结果合并为 32 位,两路都用差分换算(int16 + 3.3/32768)。

关键代码:双 ADC 同步 + 差分配置与读数

DDL_ADC16_SetMultimode(ADC1, DDL_ADC16_MULTI_DUAL_REG_SIMULT);

DDL_ADC16_SetChannelSamplingTime_Diff(ADC1, DDL_ADC16_DIFF_CHANNEL0, ...);

DDL_ADC16_SetSequencerRanks_Diff(ADC1, DDL_ADC16_REG_RANK_1, DDL_ADC16_DIFF_CHANNEL0);

DDL_ADC16_SetChannelSingleDiff(ADC1, DDL_ADC16_CHANNEL_0, DDL_ADC16_DIFFERENTIAL_ENDED);

/* ADC2 同样配差分 rank、DIFFERENTIAL_ENDED */

u32RawData = DDL_ADC16_REG_ReadConversionData32(ADC1);

sAdcData1 = (int16_t)(u32RawData & 0xFFFF);

sAdcData2 = (int16_t)((u32RawData >> 16) & 0xFFFF);

fVoltage1 = ((float)sAdcData1) * (3.3f / 32768.0f);

fVoltage2 = ((float)sAdcData2) * (3.3f / 32768.0f);

配置思路同上,只是两边都走差分通道与 DIFFERENTIAL_ENDED;读出的 32 位拆成两个 int16,再按双极性换算成电压。

适用场景:两路信号都是差分、且必须在同一时刻采样(如双路隔离电流、或两组差分传感器)。双 ADC 同步 + 差分,既保留“同一时刻”的时间对齐,又各自享受差分的抗共模能力,适合工业现场里对同步和抗干扰都有要求的采集。

7. 编码器怎么搭?例程怎么选?坑怎么躲?

编码器 sin/cos 链路:直接从 ADC16_ContinuousDualRegulSimulMode 上手——双 ADC 同步采两路,32 位结果拆成两个 16 位,(x,y) 进缓存(热点放 DTCM RAM),喂给 ATAN2 算角度。想更稳就加上定时器触发的节拍对齐(SingleRegulTmrTrigger)和模拟看门狗(AnalogWindowWatchdog)做越界监测。

按场景抄作业:快速上手用 ContinuousConversion;多路采集用 MultiChannelScan / ContinuousMultiChannelScan;抗干扰用 DifferentialMode / DualDifferentialMode;同步采样用 DualRegulSimulMode / ContinuousDualRegulSimulMode;节拍触发用 SingleRegulTmrTrigger;要稳用 OversampleMode;要保护用 AnalogWindowWatchdog。编码器角度链路的推荐组合就一句话:ContinuousDualRegulSimulMode +(可选)定时器触发 +(可选)看门狗。

实操时少踩坑:不进中断先看 NVIC 和标志有没有清;多通道错位就对着 rank 和 DMA 缓冲区捋一遍;双 ADC 不同步检查 SetMultimode 和启动顺序;差分不对看看共模范围和 int16_t 有没有搞反;过采样“变慢”是正常,精度和吞吐本来就要二选一。记住这些,前厅点单这块基本就能少交学费了。

8. 小结:ADC16 能打在哪,和编码器/电机有多配

其实,G32R430 的 ADC16 在“能打”和“好搭”这两件事上,都挺对得起编码器电机应用的。

和市面上很多 MCU 比,它讨喜的地方大概有这些:双路 16 位 SAR、真·硬件同步(同一拍采两路,直接 32 位打包给你),不用自己拼相位;1 Msps、ENOB 约 13.5 bit,再配上定时器/外部引脚/软件多种触发,做控制环的“节拍型采样”很顺手;过采样、模拟看门狗、单端/差分都齐,从“先把数采稳”到“越界赶紧报”一条龙。换句话说,你要的是“同步、节拍、抗干扰、别乱飘”,它都在硬件层给你留了位子,不用全靠软件硬扛。

落到编码器电机上,对得上的点就很直接:磁编/旋变 sin/cos 要的就是“同一时刻的两路”——双 ADC 同步规则组就是干这个的,连续同步那例程更是为“一直采、一直算角度”准备的;FOC 里电流采样要跟 PWM 对齐,定时器 TRGO 触发 ADC 的例程就是给你锁节拍的;差分和过采样在长线、干扰大的现场能少踩不少坑;模拟看门狗把过压/欠压告警前移到硬件,省心也省周期。所以要说“G32R430 的 ADC16 是不是为这类应用留了接口?”:是的,而且接口还挺全。

SDK 里的 ADC16 例程,从单通道连续、多通道扫描、到双 ADC 单次/连续同步、差分、过采样、模拟看门狗,把“单路→多路→双路同步→抗干扰→稳精度→保护”这条链都摸了一遍。做初步选型或方案评估时,用这些例程在 TinyBoard 上跑一跑、对一对串口和 readme,已经能判断 ADC 这块能不能撑住你的应用场景;真要上量再做一次采样率、抖动和 CPU 占用的定量测试会更稳,但“能不能用、怎么搭”的结论,例程覆盖的场景已经够你下第一刀了。

一句话:前厅点单稳了,后厨 ATAN2 才能秀得起来——ADC16 把“采准、采齐、采在节拍上”都照顾到了,编码器角度链路的第一环就算站稳了。今天的分享就到这里,你觉得 G32R430 的 ADC16 模块怎么样?有踩过坑或想安利的用法,欢迎在评论区唠一唠,我们下篇见。

相关推荐
评论区

登录后即可参与讨论

立即登录