跳转到主要内容

STM32多通道AD采样DMA传输的实现

cathy 提交于

在嵌入式产品中有时候需要实现对外部的模拟量进行采样处理和记录,而这就需要使用到ADC功能,将外部的模拟量转换成数字量。而在复杂的嵌入式产品中,往往需要使用多路AD采样,例如在智能家居产品,电池电量检测,热敏温度传感器,烟雾传感器,气敏传感器等都是可以使用ADC来实现采样的。在本文章,将会介绍如何通过意法的STM32 MCU实现用DMA完成多通道的AD采样功能。

<strong>什么叫ADC</strong>

ADC即模拟数字转换器(英语:Analog-to-digital converter)是用于将模拟形式的连续信号转换为数字形式的离散信号的一类设备。一个模拟数字转换器可以提供信号用于测量。与之相对的设备成为数字模拟转换器。

<strong>影响AD采样的因素有哪些</strong>

<strong>分辨率</strong>

分辨率指的是ADC的位数,例如STM32F103MCU的内部ADC的分辨率是12位,那么它所采样的结果就在0-4096之间。

<strong>最小采样单位值</strong>

根据基准电压和参考电压的不同,其值也是不同的,例如在基准电压为3.3V,参考电压最低为0V,最高为3.3V,采样分辨率位12位的嵌入式系统中,则ADC的最小量程单位则为:3.3V/4096 = 0.00080566。

<strong>量程</strong>

在无负电压的嵌入式系统中,量程范围0-基准电压。

<strong>电源噪音</strong>

电源质量直接影响了AD采样的正确性和稳定性,如果条件满足,建议使用线性稳压源,若是使用开关电源的话,需要在VDDA模拟电源输入和参考电压输入接一个线性稳压管,同时要注意减小PCB板布局走线中结电容对采样电路的影响。

<strong>STM32F103 ADC主要特性</strong>

<ul>
<li>12-位分辨率</li>
<li>转换结束,注入转换结束和发生模拟看门狗事件时产生中断</li>
<li>单次和连续转换模式</li> <li>从通道0到通道n的自动扫描模式</li> <li>自校准</li>
<li>带内嵌数据一致的数据对齐</li>
<li>通道之间采样间隔可编程</li>
<li>规则转换和注入转换均有外部触发选项</li>
<li>间断模式</li>
<li>双重模式(带2个或以上ADC的器件)</li>
<li>ADC转换时间:─ STM32F103xx 增强型产品: ADC 时钟为 56MHz 时为 1μs(ADC 时钟为 72MHz 为 1.17μs)─ STM32F101xx 基本型产品: ADC 时钟为 28MHz 时为 1μs(ADC 时钟为 36MHz 为 1.55μs)─ STM32F102xxUSB 型产品: ADC 时钟为 48MHz 时为 1.2μs</li>
<li>ADC供电要求: 2.4V到3.6V</li>
<li>ADC输入范围: VREF- ≤ VIN ≤ VREF+● 规则通道转换期间有DMA请求产生。</li>
</ul>

<strong>DMA简介</strong>

直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU任何干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。

<strong>STM32F103 DMA主要特性</strong>

<ul>
<li>12个 独立的可配置的通道(请求)DMA1有7个通道, DMA2有5个通道</li>
<li>每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。</li>
<li>在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求0优先于请求1,依此类推) 。</li>
<li>独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。</li>
<li>支持循环的缓冲器管理</li>
<li>每个通道都有3个事件标志(DMA 半传输, DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。</li>
<li>存储器和存储器间的传输</li>
<li>外设和存储器,存储器和外设的传输</li>
<li>闪存、 SRAM、外设的SRAM、 APB1 APB2和AHB外设均可作为访问的源和目标。</li>
<li>可编程的数据传输数目:最大为65536</li>
</ul>

<strong>如何实现多通道AD采样的DMA传输</strong>

<center><img src="http://mouser.eetrend.com/files/2018-01/wen_zhang_/100009958-34047-stm…; alt=“” width="600"></center>

<strong>ADC功能引脚配置</strong>

<pre style="padding:15px;overflow:auto;background:#f6f6f6;font-size:13px;width:700px;margin:0 auto;border:1px solid #ccc; ">
void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

/* Enable DMA clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

/* Enable ADC1 and GPIOC clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);

//配置模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = ADC1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//配置引脚为模拟输入模式
GPIO_Init(ADC1_GPIOX, &GPIO_InitStructure); // 输入时不用设置速率

//配置模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = ADC2_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//配置引脚为模拟输入模式
GPIO_Init(ADC2_GPIOX, &GPIO_InitStructure); // 输入时不用设置速率

//配置模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin = ADC3_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//配置引脚为模拟输入模式
GPIO_Init(ADC3_GPIOX, &GPIO_InitStructure); // 输入时不用设置速率

GPIO_InitStructure.GPIO_Pin = ADC4_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(ADC4_GPIOX,&GPIO_InitStructure);

}

</pre>

<br>
<strong>配置多通道ADC功能</strong>

<pre style="padding:15px;overflow:auto;background:#f6f6f6;font-size:13px;width:700px;margin:0 auto;border:1px solid #ccc; ">
void ADC_Multichannel_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;

ADC_DeInit(ADC1); //将外设 ADC1 的全部寄存器重设为缺省值

/* ADC1 configuration ------------------------------------------------------*/

ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC工作模式:ADC1和ADC2工作在独立模式
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数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = M; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器

/* ADC1 regular channel11 configuration */
//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
//ADC1,ADC通道x,规则采样顺序值为y,采样时间为239.5周期

ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5 );
ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 3, ADC_SampleTime_239Cycles5 );
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 4, ADC_SampleTime_239Cycles5);
// 开启ADC的DMA支持(要实现DMA功能,还需独立配置DMA通道等参数)
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
/* Enable ADC1 reset calibaration register */
ADC_ResetCalibration(ADC1); //复位指定的ADC1的校准寄存器

/* Enable ADC1 reset calibaration register */
ADC_ResetCalibration(ADC1); //复位指定的ADC1的校准寄存器
/* Start ADC1 calibaration */
ADC_StartCalibration(ADC1); //开始指定ADC1的校准状态
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
//获取指定ADC1的校准程序,设置状态则等待
}
</pre>

<br>
<strong>配置DMA通道,使能ADC转换结果从外设到内存</strong>

<pre style="padding:15px;overflow:auto;background:#f6f6f6;font-size:13px;width:700px;margin:0 auto;border:1px solid #ccc; ">
void DMA_Configuration(void)
{
/* ADC1 DMA1 Channel Config */
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
DMA_DeInit(DMA1_Channel1); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_Value; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //内存作为数据传输的目的地
DMA_InitStructure.DMA_BufferSize = N*M; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_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通道 x拥有高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数DMA通道
</pre>

<br>
<strong>开始启动ADC转换功能</strong>

<pre style="padding:15px;overflow:auto;background:#f6f6f6;font-size:13px;width:700px;margin:0 auto;border:1px solid #ccc; ">
void ADC_Start(void)
{
ADC_GPIO_Config();
ADC_Multichannel_Config();
DMA_Configuration();
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //开始ADC转换
DMA_Cmd(DMA1_Channel1, ENABLE); //启动DMA通道

}
</pre>

<br>
<strong>使用冒泡排序法,对各通道的连续五次采样结果取平均值</strong>

<pre style="padding:15px;overflow:auto;background:#f6f6f6;font-size:13px;width:700px;margin:0 auto;border:1px solid #ccc; ">
u16 ADC1_AveragValue(u16 ADC_Value[N][M])
{
u16 ADC1_Value[N];
u8 i = 0;
u8 j = 0;
u16 temp = 0;
u16 ADC1_Av = 0;
for (i = 0;i &lt; N;i++)
{
ADC1_Value[i] = ADC_Value[i][0];
}
/*冒泡排序*/
for(i=0; i&lt;N-1; i++)
{
//内循环选择要进行比较的数
for(j=0; j&lt;N-1-i; j++)
{
if(ADC1_Value[j]>ADC1_Value[j+1])
{
temp=ADC1_Value[j];
ADC1_Value[j]=ADC1_Value[j+1];
ADC1_Value[j+1]=temp;
}
}
}
/*去掉最大值和最小值*/
for (i = 0; i&lt;N-2;i++)
{
ADC1_Av += ADC1_Value[i+1];

}
/*取平均值*/
ADC1_Av = ADC1_Av/4;
return ADC1_Av;

}
</pre>
<br>
<strong>转换采样结果</strong>

<pre style="padding:15px;overflow:auto;background:#f6f6f6;font-size:13px;width:700px;margin:0 auto;border:1px solid #ccc; ">
(float)ADC1_AveragValue(ADC_Value)/4096*3.3)
</pre>
<br>
本文转载自:<a href="https://www.jianshu.com/p/7ee23bb2cb65">简书作者:桂慧要努力当个攻城师</a>
转载地址:https://www.jianshu.com/p/7ee23bb2cb65
声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有,如涉及侵权,请联系小编进行处理。