发新帖我要提问
12
返回列表
打印
[其他ST产品]

stm32之ADC应用实例(单通道、多通道、基于DMA)

[复制链接]
楼主: 逢dududu必shu
手机看帖
扫描二维码
随时随地手机跟帖
21
逢dududu必shu|  楼主 | 2022-1-27 23:58 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
第一个问题,所有的外设都要使能时钟,时钟源分为外部时钟和内部时钟,外部时钟比如接8MHz晶振,内部时钟就在芯片内部集成,时钟源为所有的时序电路提供基本的脉冲信号。时钟源好比是一颗跳动的心脏,它按照一定的频率在跳动,所有的器官(外设)要跟心脏(时钟源)桥接起来才能工作,但不同的外设需要的频率不同,所以在时钟源跟外设之中常常还会有一些分频器或者倍频器,以实现对频率的衰减或增强。还想了解更多专业的解释可以去研究stm32的时钟树图。

使用特权

评论回复
22
逢dududu必shu|  楼主 | 2022-1-27 23:58 | 只看该作者
**第二个问题,**回答这个问题那么就等于开始介绍多通道转换怎么实现了,看下图

使用特权

评论回复
23
逢dududu必shu|  楼主 | 2022-1-27 23:59 | 只看该作者
由图理解,一个ADC转换器只能选择转换一个通道,那么对比单通道我们只需做一下改变(以双通道为例):
1.在void Adc_Config(void)函数里面添加:、
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);       

使用特权

评论回复
24
逢dududu必shu|  楼主 | 2022-1-29 15:35 | 只看该作者
配置多一个IO(PA1)口, 也就是通道1。

2.在void Adc_Config(void)函数里面添加:
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );        

<span style="color: rgb(77, 77, 77); font-family: -apple-system, &quot;SF UI Text&quot;, Arial, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, &quot;WenQuanYi Micro Hei&quot;, sans-serif; font-variant-ligatures: no-common-ligatures; background-color: rgb(255, 255, 255);">先不指定ADC转换通道。</span>

使用特权

评论回复
25
逢dududu必shu|  楼主 | 2022-1-29 15:36 | 只看该作者
3.在主函数循环里改为:
    while(1)
        {
          /*先采集通道1数据*/
          ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );
          ADC_SoftwareStartConvCmd(ADC1, ENABLE);               
          while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));  
          ADC_ConvertedValue=ADC_GetConversionValue(ADC1);
          ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096);  
          
          /*再采集通道2数据*/
          ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5 );
      ADC_SoftwareStartConvCmd(ADC1, ENABLE);               
          while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));  
          ADC_ConvertedValue=ADC_GetConversionValue(ADC1);
          ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096);  

          //加入适当延时
        }



完成以上三步就能把单通道扩展到双通道(或者更多个通道)。不过还有一种基于DMA的多通道转换更加合适。

使用特权

评论回复
26
逢dududu必shu|  楼主 | 2022-1-29 15:38 | 只看该作者
首先简单介绍DMA,DMA(Direct Memory Access,直接内存存取) ,用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无需CPU干预,节省CPU资源;ADC转换出来的值直接赋值给定义好的变量中。配置好的DMA可以不停地将ADC转换值写到该变量中,在主函数直接判断该变量就知道此时的AD值,也就是说在主函数中不需要调用ADC_GetConversionValue()函数来获取转换值。

DMA跟其他外设一样需要进行配置通道,使能时钟等参数。
下面直接看代码分析:

使用特权

评论回复
27
逢dududu必shu|  楼主 | 2022-1-29 15:40 | 只看该作者
/*基于DMA的ADC多通道采集*/

volatile uint16 ADCConvertedValue[10][3];//用来存放ADC转换结果,也是DMA的目标地址,3通道,每通道采集10次后面取平均数
         
void DMA_Init(void)
{

    DMA_InitTypeDef DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能时钟

    DMA_DeInit(DMA1_Channel1);    //将通道一寄存器设为默认值
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);//该参数用以定义DMA外设基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;//该参数用以定义DMA内存基地址(转换结果保存的地址)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//该参数规定了外设是作为数据传输的目的地还是来源,此处是作为来源
    DMA_InitStructure.DMA_BufferSize = 3*10;//定义指定DMA通道的DMA缓存的大小,单位为数据单位。这里也就是ADCConvertedValue的大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设定外设地址寄存器递增与否,此处设为不变 Disable
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//用来设定内存地址寄存器递增与否,此处设为递增,Enable
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度为16位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//数据宽度为16位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA通道拥有高优先级 分别4个等级 低、中、高、非常高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//使能DMA通道的内存到内存传输
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);//根据DMA_InitStruct中指定的参数初始化DMA的通道

    DMA_Cmd(DMA1_Channel1, ENABLE);//启动DMA通道一
}

使用特权

评论回复
28
逢dududu必shu|  楼主 | 2022-1-29 15:42 | 只看该作者
下面是ADC的初始化,可以将它与上面的对比一下有啥不同,重复的就不解析了

void Adc_Init(void)
{
    ADC_InitTypeDef ADC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    /*3个IO口的配置(PA0、PA1、PA2)*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /*IO和ADC使能时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA,ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);

    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 3;
    ADC_Init(ADC1, &ADC_InitStructure);
   
    ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);//通道一转换结果保存到ADCConvertedValue[0~10][0]
        ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_239Cycles5););//通道二转换结果保存到ADCConvertedValue[0~10][1]
        ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_239Cycles5); );//通道三转换结果保存到ADCConvertedValue[0~10][2]

          
    ADC_DMACmd(ADC1, ENABLE);//开启ADC的DMA支持
    ADC_Cmd(ADC1, ENABLE);

    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));

}

使用特权

评论回复
29
逢dududu必shu|  楼主 | 2022-1-29 15:43 | 只看该作者
做完这两步,ADCConvertedValue数组的值就会随输入的模拟电压改变而改变,在主函数中最好取多几次的平均值,再通过公式换算成电压单位。下面是主函数:

int main(void)
{
        int sum;
        u8 i,j;
        float ADC_Value[3];//用来保存经过转换得到的电压值
        ADC_Init();
        DMA_Init();
       
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开始采集

        while(1)
        {
                for(i=0;i<3;i<++)
                {
                        sum=0;
                        for(j=0;j<10;j++)
                        {
                                sum+=ADCConvertedValue[j][i];
                        }
                        ADC_Value[i]=(float)sum/(10*4096)*3.3;//求平均值并转换成电压值
                        //打印(略)
                }
                //延时(略)
        }
}

使用特权

评论回复
30
逢dududu必shu|  楼主 | 2022-1-29 15:44 | 只看该作者
ADCConvertedValue的定义用了volatile修饰词,因为这样可以保证每次的读取都是从绝对地址读出来的值,不会因为被会编译器进行优化导致读取到的值不是实时的AD值。

最后提醒一下,接线测试的时候记得接上基准电压,就是VREF+和VREF-这两个引脚。如果不想外接线测试就将内部通道的电压读出来,这样就不用配置IO口了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则