流程

CPU配置PA0为模拟输入
        ↓
CPU配置ADC3为12位、单通道、连续转换
        ↓
CPU配置DMA2 Stream0
        ↓
DMA源地址 = ADC3->DR
DMA目标地址 = uhADCxConvertedValue
        ↓
CPU配置并开启DMA中断
        ↓
CPU开启DMA,DMA开始等待
        ↓
CPU开启ADC3的DMA请求功能
        ↓
CPU软件启动ADC3
        ↓
ADC3采样PA0
        ↓
ADC3完成12位转换
        ↓
结果写入ADC3->DR
        ↓
ADC3向DMA2 Stream0发送请求
        ↓
DMA读取ADC3->DR
        ↓
DMA写入uhADCxConvertedValue
        ↓
DMA剩余数量从1变成0
        ↓
DMA产生TC传输完成中断
        ↓
NVIC通知CPU
        ↓
CPU进入DMA2_Stream0_IRQHandler()
        ↓
打印ADC值和电压
        ↓
清除DMA中断标志
        ↓
ADC连续转换、DMA循环搬运,重新开始

开启时钟配置

PeripheralEnable(PeripheralGPIOA, true); //GPIOA的时钟
PeripheralEnable(PeripheralADC3, true); //ADC3的时钟

IO脚配置

GPIO_InitTypeDef      GPIO_InitStructure = {0};

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);

ADC公共配置

所谓“公共配置”,是ADC1、ADC2、ADC3可能共用的配置。

ADC_CommonInitTypeDef ADC_CommonInitStructure = {0};
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;//ADC时钟四分频
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;//
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;//多ADC采样间隔
ADC_CommonInit(&ADC_CommonInitStructure);
  • ADC_CommonInitStructure.ADC_Mode =ADC_Mode_Independent;

    表示ADC3独立工作。

    一些芯片支持多ADC协同工作,例如:

    • ADC1和ADC2同时采样
    • ADC1和ADC2交替采样
    • 三个ADC同时采样
  • ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;

    禁止多ADC公共DMA模式

    这一项主要用于双ADC或三ADC模式。

    例如多个ADC联合工作时,多个ADC结果可能被打包到公共数据寄存器,再由DMA一起搬运。

    ADC_Mode_Independent 所以不需要多ADC公共DMA模式,设置为Disabled

ADC自身配置

ADC_InitTypeDef       ADC_InitStructure = {0};
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;//设置12位分辨率0~4095

//关闭扫描模式,扫描模式用于连续转换多个通道。例如需要测量三个通道:Channel 0 → Channel 3 → Channel 7
ADC_InitStructure.ADC_ScanConvMode = DISABLE;

//开启连续转换模式,连续转换意味着ADC只需要启动一次,之后会自动不断转换:
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

//禁止外部触发边沿ADC可以由很多事件启动,例如:- 软件命令 - 定时器更新事件 - 定时器比较事件- 外部引脚
//设置成None,表示不使用外部触发边沿,后面由软件启动 ADC_SoftwareStartConv(ADC3);
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;

//ADC结果右对齐,ADC数据寄存器通常是16位或32位,而有效结果只有12位。
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

//规则序列包含一次转换
ADC_InitStructure.ADC_NbrOfConversion = 1;
ADC_Init(ADCx, &ADC_InitStructure);

// 配置具体ADC通道
// 哪个ADC:ADC3
// 哪个通道:Channel 0
// 序列中的位置:第1位
// 采样时间:3个ADC周期
ADC_RegularChannelConfig(ADC3, ADC_Channel_0, 1, ADC_SampleTime_3Cycles);

//允许ADC产生DMA请求
ADC_DMARequestAfterLastTransferCmd(ADCx, ENABLE);

//开启ADC的DMA接口
ADC_DMACmd(ADC3, ENABLE);
//开启ADC3
ADC_Cmd(ADC3, ENABLE);

开始ADC转换

必须配置好对应的DMA之后才能开启这个ADC

ADC_SoftwareStartConv(ADC3);

DMA相关配置

DMA_InitTypeDef       DMA_InitStructure = {0};
PeripheralEnable(PeripheralDMA2, true); //时钟
DMA_InitStructure.DMA_Channel = DMA_Channel_2;//DMA请求源配置
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC3->DR;//配置DMA源地址
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&uhADCxConvertedValue;//配置DMA目标地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//设置DMA传输方向,外设 → 内存
DMA_InitStructure.DMA_BufferSize = 1;//设置每轮传输数量,表示DMA每轮只搬一个数据。
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//禁止外设地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;//禁止内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//设置外设数据宽度
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//设置内存数据宽度
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//设置DMA循环模式,通模式下,传输数量变成0以后DMA会停止,循环模式下,传输完成后自动重新装载数量
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//设置DMA优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;//关闭DMA FIFO

DMA_Init(DMA2_Stream0, &DMA_InitStructure);

DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);//开启DMA传输完成中断
DMA_Cmd(DMA2_Stream0, ENABLE);//启动DMA Stream

NVIC中断配置

void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;//选择DMA2 Stream0中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//子优先级,两个中断具有相同抢占优先级,并且同时等待CPU处理,子优先级决定先处理谁。
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//允许NVIC接收该中断
    NVIC_Init(&NVIC_InitStructure);
}
中断处理函数
void DMA2_Stream0_IRQHandler(void)
{
    printf(
        "ADC data = %d, voltage = %f mV\n",
        uhADCxConvertedValue, //读取DMA搬运后的数据
        (float)uhADCxConvertedValue * 3300 / 4096 //把ADC值换算成电压
    );

    DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF);//清除DMA传输完成标志
    NVIC_ClearPendingIRQ(DMA2_Stream0_IRQn);//清除NVIC挂起状态
}