STM32外设概览:ADC模块地图解析

一、ADC(Analog-to-digital converter)

1、单通道单次采样(Single no-scan mode):任务A 读一个通道的一次采样数据

  配置ADC为 Single no-scan mode(CONT = 0,SCAN = 0)。采样 channel X (X 取值范围 0 ~ 17)时,软件先设置 channel X 的采样时间(SMPx[2:0]),并把 regular group的第一个采样通道设置为 X(SQ1[4:0] = X),然后启动 regular group 采样(ADON = 1),并等待ADC采样结束(等待EOC bit置位),当采样结束后,软件读出采样结果sample_value(读寄存器ADC_DR,读完后EOC bit自动清零),然后计算出采样电压值: volt = sample_value * 1LSB电压值

2、多通道连续采样(Continuous scan mode):任务A 读多个通道的多次采样数据(每个通道有多个采样数据,算出平均值作为有效值)

  配置ADC为 continuous scan mode**(CONT = 1,SCAN = 1),并在 regular group 中配置要采样的通道个数(L [3:0 ] = 要采样的通道个数CHANNEL_NUM – 1,注意通道个数不能超过16)、各通道的采样顺序和采样时间(配置ADC_SQR1、ADC_SMPR1),然后 在内存中准备好DMA缓存(uint16_t dma_buff[CHANNEL_DEEPTH] [ CHANNEL_NUM ],通道个数为CHANNEL_NUM,每个通道采样次数为CHANNEL_DEEPTH), 并配置DMA channel

​ 循环模式,传输方向为 peripheral–> memory,搬运次数为 CHANNEL_NUM * CHANNEL_DEEPTH;
peripheral:起始地址为寄存器ADC_DR地址,地址不增加,单数据长度为2字节;
memory: 起始地址为 &dma_buff,地址增加,单数据长度为2字节;
使能DMA channel(ADC对应的DMA channel);
​最后使能ADC的DMA功能(置位ADC的DMA标志)。 //每次regular group中的一个通道采样结束后,ADC自动向DMA控制器发送DMA请求

配置DMA channel结束后,软件启动 regular group采样(ADON = 1),启动采样后,ADC按照设定的采样顺序依次采样 regular group中的各个通道(scan regular group,scan结束后又重新开始scan),每采样完一个通道就发送一次DMA请求,DMA channel收到请求后就把采样数据从寄存器ADC_DR 读到缓存 dma_buff中。当任务A被调度器调度时,处理 dma_buff 中各通道的多个采样数据(根据采样数据算出电压值,并算出电压平均值)。

二、程序开发流程

1、单通道单次采样(Single no-scan mode)

/* 函数功能:读ADC1指定通道的采样电压. 此接口供外部模块调用
   返回值:指定channel的采样电压值
*/
uint16_t adc1_channel_volt(uint8_t channel)
{
#define REF_VOLT	  3300		//参考电压, 3300mV
#define CONVERT_BITS  12		//ADC1转换位数为12位
	ASSERT(channel < 18);		//ADC1最多18个通道
	
	把regular group的第一个采样通道设置为 channel;	//SQ1[4:0] = channel
	启动regular group采样;		//对ADON标志写1
	while(EOC标志为0);			//等待采样结束
	
	uint16_t value = ADC_DR;	//读采样数据
	uint32_t volt = value * REF_VOLT / (1 << CONVERT_BITS);	//根据采样数据算出电压值
	
	return (uint16_t)volt;		//返回电压值
}

void adc1_init(void)
{
	初始化ADC1:
	ADC1的时钟预分频为8;			  //配置ADCPRE[1:0], 时钟源为APB2(72Mhz), ADC1频率为9M, 注意不能让ADC频率超过14M
	不使能Continuous mode;	      //清零CONT标志
	不使能Scan mode;	              //清零SCAN标志
	18个通道采样时间都为239.5cycle; //配置寄存器ADC_SMPR1、ADC_SMPR2, 一个通道采样时间为 (239.5 + 12.5) / 9M = 28us
	不使能DMA模式;		         //清零DMA标志
	数据寄存器向右对齐;	         //清零ALIGN标志
	使能ADC;				     //置位ADON标志
	  
	配置ADC1各采样通道的引脚;	 	 //各引脚配置为模拟输入, 另外需根据电路图来确定是否需要软件执行ADC1引脚重映射
	
	启动ADC内部校准并等待校准完成; //置位CAL标志启动校准, 校准完成后硬件清零CAL标志
}  

2、多通道连续采样(Continuous scan mode)

#define CHANNEL_DEEPTH	20							//每个通道的采样次数
#define CHANNEL_NUM	sizeof(adc_channel_sequence)	//通道个数

static uint8_t adc_channel_sequence[ ] =			//通道采样顺序
{
	ADC_Channel_8,	//第一个采样
	ADC_Channel_10,
	ADC_Channel_11,	   
    ADC_Channel_5,			
    ADC_Channel_4,		
    ADC_Channel_1,		
    ADC_Channel_0,
	ADC_Channel_17	//最后一个采样
};

static volatile uint16_t dma_buf[CHANNEL_DEEPTH][CHANNEL_NUM];	//DMA缓存

/* 任务A周期性执行(任务A执行周期需大于dma缓存的更新周期), 负责处理dma_buf中的采样数据:
把采样数据换算成电压, 然后算出各通道的平均电压并打印出来 
*/
void taskA(void)
{
#define REF_VOLT	  3300	//参考电压, 3300mV
#define CONVERT_BITS  12	//ADC1转换位数为12位

	uint32_t one_lsb_volt = REF_VOLT / (1 << CONVERT_BITS);	//1LSB 电压值

	for(uint8_t i = 0; i < CHANNEL_NUM; i++)
	{
		uint32_t sum_volt = 0;
		
		for(uint16_t j = 0; j < CHANNEL_DEEPTH; j++)
		{
			uint32_t volt = dma_buf[j][i] * one_lsb_volt;	//计算采样电压值
			sum_volt += volt;	//电压值累加
		}
		
		printf("sequence %d average volt: %d\n", i, sum_volt / CHANNEL_DEEPTH);	//打印通道的平均电压
	}
}

void adc1_init(void)
{
	初始化ADC1:
	  ADC1的时钟预分频为8;	   		//配置ADCPRE[1:0], 时钟源为APB2(72Mhz), ADC1频率为9M, 注意不能让ADC频率超过14M
	  使能Continuous mode;	   		//置位CONT标志
	  使能Scan mode;	       		//置位SCAN标志
	  18个通道采样时间都为239.5cycle;	//配置寄存器ADC_SMPR1、ADC_SMPR2, 一个通道采样时间为 (239.5 + 12.5) / 9M = 28us
	  regular group通道个数为CHANNEL_NUM, 采样顺序为数组adc_channel_sequence 定义的顺序;
	  使能DMA模式;		       		//置位DMA标志
	  数据寄存器向右对齐;	       		//清零ALIGN标志
      使能ADC;				   		//置位ADON标志
      
    配置DMA:
      循环模式, 传输方向为 peripheral--> memory, 搬运次数为 CHANNEL_NUM * CHANNEL_DEEPTH;
      peripheral:起始地址为 寄存器ADC_DR地址,地址不增加,单数据长度为2字节;
      memory: 起始地址为 &dma_buf,地址增加,单数据长度为2字节;
      使能DMA channel(ADC1对应的DMA channel);
      
    配置ADC1各采样通道的引脚;	 		//各引脚配置为模拟输入, 另外需根据电路图来确定是否需要软件执行ADC1引脚重映射
		
	启动ADC内部校准并等待校准完成;	//置位CAL标志启动校准, 校准完成后硬件清零CAL标志
	启动regular group采样;		  	//对ADON标志写1
	
	创建任务taskA, 任务调用周期为100MS;
}           

三、注意事项

1、若使能Scan mode,则采样 regular group时,必须使用DMA功能,此时 regular group 中的每个通道采样完成后,ADC都会发送DMA请求。

2、regular group 多通道连续采样时,如果DMA channel传输完成时 需触发DMA channel中断,此时需考虑DMA channel中断周期(必要时可加大 dma缓存以延长DMA中断周期),避免中断过于频繁而影响CPU性能。

​① ADC Scan一次的时间:若配置 ADC时钟频率为 12M,每个通道采样时间为 239.5 cycle,则一个通道的转换时间为 (239.5 + 12.5)/ 12 = 21us。regular group 的通道个数可配置为 1 ~ 16,故 ADC Scan一次的时间为 21us ~ 336us。

​② DMA中断周期(dma缓存更新周期): 若dma缓存只能保存一次循环的数据,则ADC Scan完一次时,DMA立刻触发中断,DMA中断周期为 21us ~ 336us;若dma缓存可以保存 N 次循环的数据,则ADC Scan完 N次时,DMA才触发中断,DMA中断周期为 21us * N ~ 336us * N。

​  PS:如果要把DMA中断周期控制在 1MS左右,则当 regular group中只有1个通道时,dma缓存为 uint16_t dma_buff[47] [1];当 regular group中有16个通道时,dma缓存为 uint16_t dma_buff[3] [16]

​③ taskA处理采样数据的周期:taskA要等 dma缓存更新完后,才能处理采样数据,因此 taskA处理采样数据的周期,应该大于 dma缓存更新周期。

PS:如果 DMA channel传输完成时 需触发DMA channel中断,可考虑把ADC配置为 多通道单次采样,并把 regular group的外部触发配置为定时器触发,这样可以用定时器周期性启动 regular group 采样,进而控制DMA中断周期,就不用通过 加大dma缓存 来延长DMA中断周期。
  比如当设置定时器每10MS 启动一次 regular group采样时,此时DMA中断周期为: 10MS + dma缓存更新周期;当设置定时器每20MS 启动一次 regular group采样时,此时DMA中断周期为: 20MS + dma缓存更新周期。

3、关于ADC采样误差:
① 最大误差(Total unadjusted error):在任一时刻,采样值和实际值的最大差值
② 偏移误差(Offset Error):实际电压为1LSB时,采样值和实际值的差值
③ 增益误差(Gain Error):实际电压为最大Vref+ 时,采样值和实际值的差值
④ 微分线性误差(Differential Linearity Error):在任一时刻,采样的电压增量,和实际的电压增量,二者的最大差值
⑤ 积分线性误差(Integral Linearity Error):在任一时刻,采样的电压值,和实际电压 校准后的电压值,二者的最大差值

参考资料

[1] STM32F103xx datasheet.

[2] STM32F10xxx reference manual.

作者:节墨之大刀

物联沃分享整理
物联沃-IOTWORD物联网 » STM32外设概览:ADC模块地图解析

发表回复