本文帖是通过 SCCP4 定时器触发 + DMA 搬运 的实现ADC 高速采集,实测稳定采样率 24.5 Msps。
1. 采样链路
SCCP4(定时器周期匹配) ──触发──> ADC1 CH0(AN6, 板载电位器) 转换 │ │ │ ADC Done(AN6) 触发 │ ▼ └──────────────────────────────> DMA0 搬运 16/32 位结果到 SRAM │ 1024 点采满 → DMA 完成中断 │ CPU 统计 + 串口输出
2. 时钟配置(关键)
2.1 时钟树
| 系统时钟 CLKGEN1 | 200 MHz | PLL1 FOUT |
| ADC 时钟 CLKGEN6 | 200 MHz | PLL1 FOUT(源 5) |
| 标准外设时钟 | 100 MHz | FOSC/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)
| 1 | FRC(8 MHz 内部 RC) |
| 5 | PLL1 FOUT |
| 6 | PLL2 FOUT |
| 7 | PLL1 VCO DIV(实测固定 160 MHz,见 7.1 坑) |
| 8 | PLL2 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;
}关键点:
DMA 地址边界:DMALOW/DMAHIGH 必须设为 data 内存范围(0x4000~0x7FFF),否则 DMA 写 SRAM 会触发总线错误死循环。
PIC32A 统一编址:CPU 与 DMA 使用同一地址,无需 KVA→PA 转换。
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
我要赚赏金
