这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【DSPIC33CURIOSITYPLATFMDEVBRD】过程贴-ADC25M

共1条 1/1 1 跳转至

【DSPIC33CURIOSITYPLATFMDEVBRD】过程贴-ADC25Msps稳定采样

工程师
2026-09-21 20:41:18     打赏
PIC32AK1216GC41064的主要特色之一是ADC采样率高达40Msps,实测能配置到25 Msps 稳定采样

本文帖是通过 SCCP4 定时器触发 + DMA 搬运 的实现ADC 高速采集,实测稳定采样率 24.5 Msps

1. 采样链路

SCCP4(定时器周期匹配) ──触发──> ADC1 CH0(AN6, 板载电位器) 转换
        │                                      │
        │                               ADC Done(AN6) 触发
        │                                      ▼
        └──────────────────────────────> DMA0 搬运 16/32 位结果到 SRAM
                                               │
                                        1024 点采满 → DMA 完成中断
                                               │
                                          CPU 统计 + 串口输出

2. 时钟配置(关键)

2.1 时钟树

时钟频率来源
系统时钟 CLKGEN1200 MHzPLL1 FOUT
ADC 时钟 CLKGEN6200 MHzPLL1 FOUT(源 5)
标准外设时钟100 MHzFOSC/2
SCCP4 时钟100 MHz标准外设时钟(CLKSEL=0)

2.2 PLL1 配置(200 MHz)

FVCO = Fin × M / N1 = 8 MHz × 125 / 1 = 1 GHz,FPLLO = FVCO / (N2 × N3) = 1 GHz / (5 × 1) = 200 MHz。

文件:src/config/default/peripheral/clk/plib_clk.c 的 CLOCK_Initialize():

/* PLL1:8 MHz 内部 FRC -> 200 MHz 系统时钟 */PLL1CONbits.ON = 1U;
PLL1CONbits.OE = 1U;
PLL1CONbits.BOSC = 2U;          /* BFRC 备份 */PLL1CONbits.FSCMEN = 1U;        /* 失效安全监视 */PLL1DIVbits.PLLPRE = 1U;        /* N1 = 1 */PLL1DIVbits.PLLFBDIV = 125U;    /* M = 125 */PLL1DIVbits.POSTDIV1 = 5U;      /* N2 = 5 */PLL1DIVbits.POSTDIV2 = 1U;      /* N3 = 1 */PLL1CONbits.PLLSWEN = 1U;       /* 更新输入/反馈分频 */while (PLL1CONbits.PLLSWEN == 1U) {}
PLL1CONbits.FOUTSWEN = 1U;      /* 更新输出分频 */while (PLL1CONbits.FOUTSWEN == 1U) {}

PLL1CONbits.NOSC = 1U;          /* 输入源 FRC */PLL1CONbits.OSWEN = 1U;while (PLL1CONbits.OSWEN == 1U) {}while (!OSCCTRLbits.PLL1RDY) {}while (!PLL1CONbits.CLKRDY) {}/* 系统时钟切到 PLL1 FOUT = 200 MHz */CLK1CONbits.NOSC = 5U;          /* 5 = PLL1 FOUT */CLK1CONbits.OSWEN = 1U;while (CLK1CONbits.OSWEN == 1U) {}while (!CLK1CONbits.CLKRDY) {}/* ADC 时钟(CLKGEN6)切到 PLL1 FOUT = 200 MHz */CLK6CONbits.NOSC = 5U;          /* 5 = PLL1 FOUT */CLK6CONbits.OSWEN = 1U;while (CLK6CONbits.OSWEN == 1U) {}while (!CLK6CONbits.CLKRDY) {}

2.3 时钟源 NOSC 编码(Table 15-2)

NOSC时钟源
1FRC(8 MHz 内部 RC)
5PLL1 FOUT
6PLL2 FOUT
7PLL1 VCO DIV(实测固定 160 MHz,见 7.1 坑)
8PLL2 VCO DIV

3. ADC 采样率换算

数据手册:ADC 转换速率 = ADC 时钟 / 8(每个转换 8 TAD,TAD = ADC 时钟周期)。

ADC 极限 = 200 MHz / 8 = 25 Msps

对应 adc_highspeed_demo.h:

#define ADC_HS_ADC_CLOCK_HZ             200000000UL   /* ADC 时钟 = PLL1 FOUT */#define ADC_HS_SCCP_INPUT_CLOCK_HZ      100000000UL   /* SCCP 时钟 = 标准外设时钟 */#define ADC_HS_TIMER_TICK_HZ            100000000UL   /* TMR1 计时 100 MHz */#define ADC_HS_ADC_TAD_NUMERATOR        8u            /* 转换 8 TAD */#define ADC_HS_ADC_TAD_DENOMINATOR      1u#define ADC_HS_SAMPLE_COUNT             1024u         /* 每块 1024 点 */#define ADC_HS_TARGET_SAMPLE_RATE_HZ    40000000UL    /* 目标 40 Msps(被硬件限制到 25) */#define ADC_HS_PRINT_PERIOD_MS          500u          /* 打印周期 */

4. SCCP4 定时器触发配置

文件:src/adc_highspeed_demo.c 的 ADC_HS_Sccp4Configure()。

SCCP4 配置为 Dual 16-bit Timer 模式,周期匹配输出 Special Event Trigger 触发 ADC。

static void ADC_HS_Sccp4Configure(uint32_t requestedRateHz){    uint32_t divider;    uint32_t triggerPoint;    if (requestedRateHz == 0U) requestedRateHz = 1U;    /* 触发频率 = SCCP 时钟 / (PR + 1)。
     * 数据手册 27.4.2.1.1:Special Event Trigger 由 CCPxRB 设置触发点,
     * 且必须 < 计数器周期 CCPxPRL。 */
    divider = ADC_HS_SCCP_INPUT_CLOCK_HZ / requestedRateHz;  /* 100M/40M = 2 */
    if (divider < 2U) divider = 2U;                          /* PR >= 1 */
    if (divider > 0x10000U) divider = 0x10000U;

    triggerPoint = ((divider - 1U) >= 2U) ? 1U : 0U;        /* 触发点 < PR */

    CCP4CON1bits.ON = 0U;
    CCP4CON1 = 0U;                  /* CCSEL=0, T32=0, MOD=0(Timer), TMRPS=0(1:1) */
    CCP4CON1bits.T32 = 0U;          /* Dual 16-bit Timer 模式 */
    CCP4CON1bits.CLKSEL = 0U;       /* 000 = 标准外设时钟 = 100 MHz */
    CCP4CON3bits.OETRIG = 1U;       /* 触发输出使能 */

    CCP4PR = (uint16_t)(divider - 1U);   /* 计数器周期 CCP4PRL */
    CCP4RB = (uint16_t)triggerPoint;     /* Special Event Trigger 触发点 */
    CCP4TMR = 0U;

    adcHsTriggerRateHz = ADC_HS_SCCP_INPUT_CLOCK_HZ / divider;  /* 100/2 = 50 MHz */}

要点

  • 触发频率 = 100 MHz / divider。本工程 divider=2 → 50 MHz 触发,ADC 饱和在 25 Msps。

  • CCP4RB(触发点)必须 < CCP4PR(周期),这是之前容易漏配的寄存器。

5. ADC 通道触发源

ADC 通道触发源 TRG1SRC(Table 18-3):

#define ADC_HS_TRG_SOFTWARE             0x01u   /* 软件触发 */#define ADC_HS_TRG_ADC_IMMEDIATE        0x02u   /* 背靠背立即重触发 */#define ADC_HS_TRG_ADC_REPEAT_TIMER     0x03u   /* ADC 内部重复定时器(RPTCNT) */#define ADC_HS_TRG_SCCP4                0x0Fu   /* SCCP4 Trigger out(Timer 模式)*/

启动采集时切换到 SCCP4 触发:

if (adcHsTriggerMode == ADC_HS_TRIGGER_SCCP4_TIMER)
{
    AD1CH0CONbits.TRG1SRC = ADC_HS_TRG_SCCP4;   /* 0x0F */
    CCP4TMR = 0U;
    CCP4CON1bits.ON = 1U;                        /* 启动定时器 */}

注意:SCCP4 定时器触发必须用 0x0F("SCCP4 Trigger out",Timer 模式 Special Event Trigger),不要用 0x17(那是 OCMP/ICAP 输出)。

6. DMA0 配置

文件:src/adc_highspeed_demo.c 的 ADC_HS_DmaConfigure()。

static void ADC_HS_DmaConfigure(void){    /* 关键:DMALOW/DMAHIGH 复位为 0,必须设为 data 内存范围,否则 DMA 访问 SRAM
     * 被判越界触发 _BusErrorTrap 死循环。 */
    DMALOW = 0x4000u;
    DMAHIGH = 0x7FFFu;

    DMACONbits.ON = 1U;

    DMA0CHbits.CHEN = 0U;
    DMA0SELbits.CHSEL = ADC_HS_DMA_TRG_ADC1_CH0;  /* 0x2B = ADC1 Done CH0 */
    DMA0SRC = ADC_HS_DMA_ADDRESS(&AD1CH0DATA);
    DMA0DST = ADC_HS_DMA_ADDRESS(adcHsBuffer);
    DMA0CNT = (uint32_t)ADC_HS_SAMPLE_COUNT;          /* CNT = 待传输次数,勿减 1(见 11.7 坑) */
    DMA0CHbits.SIZE = 2U;           /* 32 位传输(匹配 AD1CH0DATA 32 位寄存器) */
    DMA0CHbits.SAMODE = 0U;         /* 源地址固定(ADC 数据寄存器) */
    DMA0CHbits.DAMODE = 1U;         /* 目的地址递增 */
    DMA0CHbits.TRMODE = 0U;         /* One-Shot:传完一块即停 */
    DMA0CHbits.DONEEN = 1U;         /* 块完成中断 */

    IFS2bits.DMA0IF = 0U;
    IPC9bits.DMA0IP = 4U;
    IEC2bits.DMA0IE = 1U;
    INTC_SourceEnable(INT_SOURCE_DMA0);    /* DMA 触发源(ADC1 CH0 中断)连的是"中断请求"(IF & IE),
     * 因此必须使能 ADC 通道中断,DMA 才能收到触发。 */
    IFS4bits.AD1CH0IF = 0U;
    IPC18bits.AD1CH0IP = 1U;
    IEC4bits.AD1CH0IE = 1U;
}

关键点

  1. DMA 地址边界:DMALOW/DMAHIGH 必须设为 data 内存范围(0x4000~0x7FFF),否则 DMA 写 SRAM 会触发总线错误死循环。

  2. PIC32A 统一编址:CPU 与 DMA 使用同一地址,无需 KVA→PA 转换。

  3. DMA 触发源:CHSEL = 0x2B(ADC1 Done CH0),连的是"中断请求"(IF & IE),所以必须使能 ADC 通道中断。

DMA 触发源宏(注意 Table 16-2 的 "AN" 是命名误写,实际对应通道 CH):

#define ADC_HS_DMA_TRG_ADC1_CH0         0x2bu

7. 中断处理

7.1 DMA0 完成中断

void __attribute__((interrupt, no_auto_psv)) _DMA0Interrupt(void)
{
    IFS2bits.DMA0IF = 0U;    /* 先停掉触发源,防止 DMA 缓冲区被继续改写 */
    if (adcHsTriggerMode == ADC_HS_TRIGGER_SCCP4_TIMER)
    {
        CCP4CON1bits.ON = 0U;
    }    else
    {
        AD1CH0CONbits.TRG1SRC = 0U;
    }

    adcHsEndTick = ADC_HS_TimestampGet();
    adcHsState = ADC_HS_STATE_COMPLETE;
}

7.2 ADC1 CH0 完成中断

作用仅是产生 DMA 触发请求(IF & IE),并清中断标志(数据由 DMA 读走):

void __attribute__((interrupt, no_auto_psv)) _AD1CH0Interrupt(void)
{    volatile uint32_t discard;

    discard = AD1CH0DATA;      /* 读数据清结果就绪(CH0RDY) */
    (void)discard;

    IFS4bits.AD1CH0IF = 0U;    /* 写 0 清中断标志(CH0IF) */}

8. 主循环集成

文件:src/main.c。

int main(void){
    SYS_Initialize(NULL);
    initializeAllLEDs();
    initializeAllButtons();
    TASK_Initialize();
    TMR1_CallbackRegister((TMR_CALLBACK)TASK_InterruptHandler, (uintptr_t)NULL);
    TMR1_Start();
    pot.initialize();
    ADC_VOLTAGE_Initialize();
    ADC_HS_Initialize();
    ledRGB.on();
    printMenu();
    ADC_HS_PrintConfiguration();
    TASK_Request(ADC_HS_Tick1ms, 1);
    TASK_Request(printStatisticsRequest, ADC_HS_PRINT_PERIOD_MS);
    ADC_HS_Start();    while (1)
    {        /* 定时器触发期间不能用 pot.read()(软件触发已失效),
           改用最新一块的平均码值驱动 RGB 亮度 */
        setRGBIntensity(ADC_HS_GetLastAverageRaw());
        ADC_HS_Task();        if (statisticsPrintRequired)
        {
            statisticsPrintRequired = false;
            moveCursor(13);
            ADC_HS_PrintStatistics();
        }
        checkUartApp();
        checkButtonS1();
        checkButtonS2();
        checkButtonS3();
    }    return (EXIT_FAILURE);
}

采集状态机 ADC_HS_Task()(每块采满自动统计并重启下一块):

void ADC_HS_Task(void){    if (!adcHsRunning) return;    if (adcHsState == ADC_HS_STATE_COMPLETE)
    {
        ADC_HS_ComputeStatistics();
        adcHsStatistics.blockCount++;
        adcHsStatisticsValid = true;
        ADC_HS_StartCapture();      /* 立即开始下一块 */
    }
}

9. 统计与采样率计算

static void ADC_HS_ComputeStatistics(void){    uint32_t index;    uint32_t sum = 0U;    uint64_t sumOfSquares = 0U;    uint16_t minimum = 0xFFFFU, maximum = 0U, sample;    uint32_t windowTicks;    for (index = 0U; index < ADC_HS_SAMPLE_COUNT; index++)
    {
        sample = (uint16_t)(adcHsBuffer[index] & 0x0FFFu);
        sum += sample;
        sumOfSquares += (uint64_t)sample * sample;        if (sample < minimum) minimum = sample;        if (sample > maximum) maximum = sample;
    }

    windowTicks = adcHsEndTick - adcHsStartTick;    /* 保护:TMR1 回绕竞争导致 windowTicks 异常(接近 2^32)时丢弃本块 */
    if (windowTicks > (ADC_HS_TIMER_TICK_HZ / 1000U)) return;   /* > 1ms 视为异常 */

    adcHsStatistics.averageRaw = (uint16_t)(sum / ADC_HS_SAMPLE_COUNT);
    adcHsStatistics.minimumRaw = minimum;
    adcHsStatistics.maximumRaw = maximum;
    adcHsStatistics.averageMillivolts = ADC_VOLTAGE_RawToMillivolts(averageRaw);
    adcHsStatistics.peakToPeakMillivolts = ADC_VOLTAGE_RawToMillivolts(maximum - minimum);
    adcHsStatistics.rmsMillivolts = ADC_VOLTAGE_RawToMillivolts((uint16_t)(sqrtf((float)sumOfSquares / ADC_HS_SAMPLE_COUNT) + 0.5f));    /* 实测采样率 = 点数 / 窗口时间 */
    adcHsStatistics.sampleRateHz =
        (uint32_t)(((uint64_t)ADC_HS_SAMPLE_COUNT * ADC_HS_TIMER_TICK_HZ) / windowTicks);
}

10. 串口输出示例

ADC-HS: target 40.000 Msps, trig 50.000 Msps, ADC clk 200000000 Hz
ADC-HS: ADC limit ~25000 ksps -> actual rate = min(trigger, ADC limit)
ADC-HS: N=1024  24.5 Msps  win=41 us  avg=2.550 V (raw 3164)  min=0 max=3214  p2p=2590 mV  rms=2.551 V



共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]