打印
[其他ST产品]

基于STM32F103C8T6的高速DMA传输多通道ADC数据

[复制链接]
581|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
l63t89|  楼主 | 2023-5-29 23:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
AD, ADC, DM, DMA, ST
前言

ADC在STM32系列单片机的使用中占用着很大的比例,常见的案例是通过ADC单次转换电压值,这种方式的缺陷在于转换效率不高。一般的单片机带有ADC1和ADC2两个ADC转换,单次转换需要执行一定的程序,想得到结果需要耗费一些时间在赋值,调用中断上面。在此基础上,为了提高转换的效率,借用单片机内部自带的DMA传输单元,可以直接越过CPU指令,将数据传送到我们所定义的寄存单元内部,这样我们需要查看检测的电压数据时,只需要直接访问存储数组即可。
一、软件设计思路

整体的软件设计思路分为两个大的环节:初始化ADC和开启高速DMA数据传输。在本次实验中,选用ADC1作为转换单元,这是比较基础的只用到一个ADC的转换方式,在ADC的使用之中,可以以ADC1和ADC2相互配合的方式进行交替采样,其优点在于两次采样中间只需要间隔极短的时间,比单次采样要快很多,在后续会进行更新,此处只用到一个ADC单元。

ADC初始化需要设置好转换的时间,转换的IO口以及转换的触发方式。而高速DMA也需要设置对应的触发方式,这里是通过ADC进行触发。在DMA传输的过程中,需要设定好DMA传输的方式以及地址,下面就针对具体的代码进行设计。

使用特权

评论回复
沙发
l63t89|  楼主 | 2023-5-29 23:28 | 只看该作者
二、代码

代码如下:开头用define定义了ADC1_DR_Address,这是ADC存放检测数据的地址,DMA单元可以通过此地址访问数据,并将数据存储到我们定义的数组内部。那么后面定义了全局变量ADC_ConvertedValue,我们检测到的所有数据要存放到这个数组内部,可以看到,目前设置的ADC存放数据总共有四个,对应四个检测通道,可以根据自己的实际需求,去进行设计。具体数组的存放大小计算公式是:设置的检测通道*每个通道想要求平均的个数。比如四个通道,十个想要求平均的数据,则数组大小就设置为40,那么通过软件设置,数组[0]对应的就是第一个通道第一个数据,数组[1]对应的是第二个通道第一个数据,数组[4]对应的就是第一个通道第二个数据,依此类推。

在实际的ADC初始化中,调用ADC,DMA,GPIO三个InitTypeDef,这些都是实现定义好的,但是记得在FWlib库中调用dma和adc的官方例程库。前面初始化了DMA时钟,ADC1时钟,以及我们所定义的采样引脚,在这里定义的ADC采样引脚为PA1-PA4,可以自行修改。那么在GPIO口初始化了以后,就开始对DMA进行配置。

使用特权

评论回复
板凳
l63t89|  楼主 | 2023-5-29 23:28 | 只看该作者
DMA配置过程中需要对几个参数进行注意,在这里重点说明一下:

    DMA_PeripheralBaseAddr:可以理解为获取数据的地址,我们需要获取ADC的数据,则将ADC存放数据的地址放到此处。
    DMA_MemoryBaseAddr:存放数据的地址,这里的地址单元由我们定义的数组决定,只需要将定义数组赋值过去即可。
    DMA_BufferSize:缓冲区长度,取决于你的数组长度,有多少个数据就填多少
    DMA_PeripheralDataSize:与下方内存的DataSize类似,都设置为了half word,那么half word其实等同于16位,2个byte,根据自己需求决定

使用特权

评论回复
地板
l63t89|  楼主 | 2023-5-29 23:28 | 只看该作者
其它的就正常配置就行,DMA配置完毕以后,就要对ADC进行设置。ADC设置的话是要与DMA相互配合,故初始化ADC的工作模式为独立工作,之前提到了ADC可以互相配合也可以单独工作。在只使用一个ADC的情况下,只需要设置为独立工作模式即可。需要注意的是,如果需要连续不断地采集ADC数据,就要设置ADC工作在扫描和连续转换模式中。用于转换的通道也可以进行设置,具体看通道的个数,用到了多少通道就设置参数为多少。通道配置中根据芯片本身的资源,可以从channel0-channel15(以STM32F103ZET6)为例,1,2,3,4等数代表的是转换的次序,主要是方便查看你的数据位置,避免出错。转换精度此处我们设置为239.5,这个得根据你自己的需求来,设置的精度越高转换时间越长,精度自然越好,反之亦然,这里折中设置。最后再对ADC进行一些使能即可。

使用特权

评论回复
5
l63t89|  楼主 | 2023-5-29 23:29 | 只看该作者
adc.c

#include "adc.h"
#define ADC1_DR_Address   ((u32)0x40012400+0x4c)
__IO uint16_t ADC_ConvertedValue[4];//ADC存放数据地址
void Init_adc(void)
{       
        ADC_InitTypeDef ADC_InitStructure;       
        DMA_InitTypeDef DMA_InitStructure;        
        GPIO_InitTypeDef GPIO_InitStructure;           
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//启动DMA时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);//启动ADC1时钟        
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//采样脚设置               
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;       
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;       
        GPIO_Init(GPIOA, &GPIO_InitStructure);        
        //DMA配置
        DMA_DeInit(DMA1_Channel1);//DMA1通道1配置               
        DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//外设地址                       
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;//内存地址       
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//dma传输方向单向                       
        DMA_InitStructure.DMA_BufferSize = 4;//设置DMA在传输时缓冲区的长度               
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设置DMA的外设递增模式,一个外设                       
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//设置DMA的内存递增模式               
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设数据字长       
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;        //内存数据字长       
        DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;        //设置DMA的传输模式:连续不断的循环模式       
        DMA_InitStructure.DMA_Priority = DMA_Priority_High;//设置DMA的优先级别                       
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//设置DMA的2个memory中的变量互相访问       
        DMA_Init(DMA1_Channel1, &DMA_InitStructure);                       
        DMA_Cmd(DMA1_Channel1, ENABLE);//使能通道1        
        //ADC配置       
        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 = 4;//用于转换的通道数       
        ADC_Init(ADC1, &ADC_InitStructure);               
        //规则模式通道配置       
        ADC_RegularChannelConfig(ADC1, ADC_Channel_1 , 1, ADC_SampleTime_239Cycles5);       
        ADC_RegularChannelConfig(ADC1, ADC_Channel_2 , 2, ADC_SampleTime_239Cycles5);               
        ADC_RegularChannelConfig(ADC1, ADC_Channel_3 , 3, ADC_SampleTime_239Cycles5);       
        ADC_RegularChannelConfig(ADC1, ADC_Channel_4 , 4, ADC_SampleTime_239Cycles5);                       
        ADC_DMACmd(ADC1, ENABLE);//使能ADC1的DMA                       
        ADC_Cmd(ADC1, ENABLE);//使能ADC1                        
        ADC_ResetCalibration(ADC1);//使能ADC1复位校准寄存器               
        while(ADC_GetResetCalibrationStatus(ADC1));//检查校准寄存器是否复位完毕               
        ADC_StartCalibration(ADC1);//开始校准                       
        while(ADC_GetCalibrationStatus(ADC1));//检测是否校准完毕                        
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开启ADC1的软件转换
}

使用特权

评论回复
6
l63t89|  楼主 | 2023-5-29 23:29 | 只看该作者
adc.h
#ifndef __ADC_H
#define        __ADC_H


#include "stm32f10x.h"

void Init_adc(void);

#endif /* __ADC_H */


使用特权

评论回复
7
l63t89|  楼主 | 2023-5-29 23:29 | 只看该作者
需要调用参数只需要在定时器里面进行计数求平均即可,当初始化ADC以后,我们所定义的数组里面就会源源不断的出现数据,如前文提到的,第一个数据对应的就是第一个转换通道的,那么根据这个关系,就可以对采集到的数据进行平均化处理。

总结

此代码还有很多可以优化的地方,比如利用到交替采样。如果用到了F4的单片机,还可以利用F4的DSP库进行一些函数的计算,比如均方根算法,进行交流电压交流电流的采样。

使用特权

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

本版积分规则

88

主题

776

帖子

1

粉丝