STM32F4的多路ADC采样DMA传输

问题背景:

想通过STM32F4VET6对2个信号进行AD采样,同时把采样值用DMA存入一个数组;

使用标准库开发,ADC采样的2个引脚为PC0和PC1,使用ADC3的ch10,ch11通道。

使用DMA2的数据流0通道2进行ADC到数组adc_buf的传输。

使用的方法是ADC完成一轮转换进入ADC中断,中断函数开启DMA传输;DMA传输完成后进入DMA中断,中断中开启新的一轮ADC采样。

遇到的问题:

1、在调试过程中,执行完adc初始化函数ADC_SoftwareStartConv(ADC3);后程序卡住。

2、在调试过程中查看ADC3的寄存器发现EOC无法置位,程序只会在初始化时进入ADC中断一次。

以下是源码:
adc.h:
#ifndef ADC_H
#define ADC_H
#include "stm32f4xx.h"
void adc_init(void);
#endif
adc.c:
#include "stm32f4xx.h"
#include "adc.h"
void adc_init()    // ADC123 - IN 10,11 by PC 0,1
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    ADC_CommonInitTypeDef  ADC_CommonInitStruct;
    
    RCC_APB2PeriphClockCmd(RCC_AHB1Periph_GPIOF|RCC_APB2Periph_ADC3,ENABLE);
    
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOC,&GPIO_InitStructure);
    
    ADC_CommonInitStruct.ADC_Mode=ADC_Mode_Independent;
    ADC_CommonInitStruct.ADC_Prescaler=ADC_Prescaler_Div6;
    ADC_CommonInitStruct.ADC_DMAAccessMode=ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStruct.ADC_TwoSamplingDelay=ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit(&ADC_CommonInitStruct);
    
    ADC_InitStructure.ADC_ContinuousConvMode   = ENABLE;//连续采样
    ADC_InitStructure.ADC_ScanConvMode         = ENABLE;//扫描模式
    ADC_InitStructure.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_T1_CC1;
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//触发
    ADC_InitStructure.ADC_DataAlign            = ADC_DataAlign_Right;//对齐
    ADC_InitStructure.ADC_NbrOfConversion      = 2;  // 通道数
    ADC_Init(ADC3, &ADC_InitStructure);
    
      //中断优先级配置
    NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
     //配置 ADC3 的扫描通道序列,并指定每个通道的采样时间
    ADC_RegularChannelConfig(ADC3, ADC_Channel_10, 1, ADC_SampleTime_84Cycles);
    ADC_RegularChannelConfig(ADC3, ADC_Channel_11, 2, ADC_SampleTime_84Cycles);
    
    //使能 ADC3 的 DMA 传输
    ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);  //当DMA传输完成后,将自动重新启动连续转换
    ADC_DMACmd(ADC3, ENABLE);
    
    ADC_Cmd(ADC3, ENABLE);
    ADC_ITConfig(ADC3, ADC_IT_EOC, ENABLE);
    ADC_SoftwareStartConv(ADC3);
}

void ADC_IRQHandler()
{
    if (ADC_GetITStatus(ADC3,ADC_IT_EOC)!=RESET) 
    {
        ADC_DMACmd(ADC3,ENABLE);    
        DMA_Cmd(DMA2_Stream0, DISABLE );  //关闭通道
        DMA_SetCurrDataCounter(DMA2_Stream0,2);        //设置DMA通道的传输数据大小
        DMA_Cmd(DMA2_Stream0, ENABLE);  //使能通道
        while(DMA_GetFlagStatus(DMA2_Stream0,DMA_FLAG_TCIF0)==RESET);        //等待传输完成
        DMA_ClearFlag(DMA2_Stream0,DMA_FLAG_TCIF0);        //清除传输标志
    }
    ADC_ClearITPendingBit(ADC3,ADC_IT_EOC);
}
dma.h
#ifndef DMA_H
#define DMA_H
#include "stm32f4xx.h"
void dma_init(DMA_Stream_TypeDef *DMA_Streamx, u32 chx, u32 par, u32 mar, u16 ndtr);
#endif
dma.c
#include "stm32f4xx.h"
#include "dma.h"
//DMAx的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量  
void dma_init(DMA_Stream_TypeDef *DMA_Streamx, u32 chx, u32 par, u32 mar, u16 ndtr)
{
    DMA_InitTypeDef  DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    if((u32)DMA_Streamx>(u32)DMA2)                                       //得到当前stream是属于DMA2还是DMA1
    {
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);                 //DMA2时钟使能 
    }
    else 
    {
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);                     //DMA1时钟使能 
    }
    DMA_DeInit(DMA_Streamx);

    while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}                             //等待DMA可配置 

    /* 配置 DMA Stream */
    DMA_InitStructure.DMA_Channel = chx;                                           //通道选择
    DMA_InitStructure.DMA_PeripheralBaseAddr = par;                                //外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr = mar;                                   //存储地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;                        //存储器到外设
    DMA_InitStructure.DMA_BufferSize = ndtr;                                       //数据传输量 
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;               //外设非增量
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                        //存储器增量
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;    //外设数据长度:8位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;            //存储器数据长度:8位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                                //普通模式 
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                          //优先级
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;            
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
    DMA_Init(DMA_Streamx, &DMA_InitStructure);                                     //初始化DMA Stream
    DMA_ClearFlag(DMA2_Stream0,DMA_IT_TC);
    DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel=DMA2_Stream0_IRQn; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;                    
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;                       
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    while (DMA_GetCmdStatus(DMA2_Stream0) != DISABLE){}
    DMA_Cmd(DMA2_Stream0, ENABLE);
}
void DMA2_Stream0_IRQHandler()
{
    if(DMA_GetFlagStatus(DMA2_Stream0,DMA_FLAG_TCIF0)!=RESET)
    {
        if((DMA2->LISR & DMA_LISR_TCIF1) == DMA_LISR_TCIF1);
        {            
            ADC_RegularChannelConfig(ADC3,ADC_Channel_10,1,ADC_SampleTime_84Cycles);
            ADC_RegularChannelConfig(ADC3,ADC_Channel_11,2,ADC_SampleTime_84Cycles);
            ADC_SoftwareStartConv(ADC3);
            while(ADC_GetFlagStatus(ADC3,ADC_FLAG_EOC)==RESET);
        }
        DMA_ClearITPendingBit(DMA2_Stream0,DMA_IT_TCIF0);
        DMA2->LIFCR = DMA_LIFCR_CTCIF1; 
    }
}
main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "adc.h"
#include "dma.h"

#define adc_buff_size 2
u16 adc_buff[adc_buff_size];

int main(void)
{
    delay_init(168);
        dma_init(DMA2_Stream0,DMA_Channel_2,(u32)&(ADC1->DR),(u32)adc_buff,adc_buff_size);
    adc_init();
       for(i=1;i<=8;i++)
    {
        LED0=~LED0;
        delay_ms(88);
    }
    while(1)
    {
        delay_ms(100);
    }
}

初次接触F4,可能还有很多配置错误的地方希望大家能帮忙指正,感谢!

引用chatgpt部分指引作答:
通过查看您提供的源代码,我发现了几个问题可能导致程序无法正常工作:

1 ADC3和DMA2的时钟使能配置错误:
在函数adc_init()中,您应该将RCC_APB2PeriphClockCmd(RCC_AHB1Periph_GPIOF | RCC_APB2Periph_ADC3, ENABLE);更正为RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);。ADC3不属于AHB1总线,所以不需要使用RCC_AHB1PeriphClockCmd来使能时钟。

2 ADC中断优先级配置错误:
在函数adc_init()中,中断优先级配置错误。应该将以下代码:

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;

更改为:

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;

将中断优先级设置为0,以确保ADC中断能够正确触发。

3 DMA初始化函数调用错误:
在main函数中,您调用了错误的DMA初始化函数。应该将以下代码:

dma_init(DMA2_Stream0, DMA_Channel_2, (u32)&(ADC1->DR), (u32)adc_buff, adc_buff_size);

更正为:

dma_init(DMA2_Stream0, DMA_Channel_0, (u32)&(ADC3->DR), (u32)adc_buff, adc_buff_size);

DMA通道应该设置为DMA_Channel_0,且外设地址应为ADC3->DR。

4 DMA中断处理函数错误:
在函数DMA2_Stream0_IRQHandler()中,以下代码是多余的,应该将其删除:

if((DMA2->LISR & DMA_LISR_TCIF1) == DMA_LISR_TCIF1);
{            
    ADC_RegularChannelConfig(ADC3,ADC_Channel_10,1,ADC_SampleTime_84Cycles);
    ADC_RegularChannelConfig(ADC3,ADC_Channel_11,2,ADC_SampleTime_84Cycles);
    ADC_SoftwareStartConv(ADC3);
    while(ADC_GetFlagStatus(ADC3,ADC_FLAG_EOC)==RESET);
}

此部分代码导致了错误的ADC配置和采样。

请尝试按照上述修改建议更正代码中的问题,并重新测试程序。

来自NewBing和LP的编写:
我看到了一些可能存在的问题,我会尝试逐一解答。

  1. GPIO配置:你的ADC引脚是PC0和PC1,但是在代码中,你为GPIOF使能了时钟,这应该是一个小错误。你应该使用 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC|RCC_APB2Periph_ADC3,ENABLE); 来为GPIOC和ADC3使能时钟。

  2. DMA初始化:你的DMA初始化函数中,你启用了DMA,并在NVIC中设置了优先级,但我没看到你为DMA启用中断。你需要使用DMA_ITConfig函数来启用DMA的中断。

  3. ADC外设地址:在DMA初始化函数中,你的外设地址是 (u32)&(ADC1->DR),但你使用的是ADC3,所以应该是 (u32)&(ADC3->DR)

  4. DMA的数据方向:你的DMA初始化函数中,数据方向是DMA_DIR_PeripheralToMemory,这是正确的,因为你是从ADC读取数据到内存。

  5. ADC初始化和启动:在ADC初始化函数中,你设置了ADC为连续转换模式,并且在初始化结束后立即启动了转换。这可能会导致在DMA还没准备好时ADC就开始了转换,因此在DMA初始化完成之后再启动ADC可能会更好。

  6. 中断处理:在ADC的中断处理函数中,你关闭了DMA,设置了传输数量,然后再次启用了DMA。这可能会导致一些问题,因为你正在处理ADC的中断,但此时DMA可能仍在传输数据。可能的解决方法是在DMA的中断处理函数中处理这些事情。

这些是我在你的代码中发现的一些可能的问题。希望这些信息能帮到你。如果问题仍然存在,我建议你使用调试工具来查看每个阶段的具体行为,这将帮助你更好地理解问题所在。