本文使用源码来自整点原子例程【实验19-3 多通道ADC采集(DMA)实验】
本例程通过DMA自动采集6个通道的ADC数值,采集数据放在g_adc_dma_buf中,其声明如下:
#define ADC_DMA_BUF_SIZE 50 * 6 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
ADC_DMA_BUF_SIZE是6的几倍,表示一次采集几轮。
本文主要分析ADC的采集速度,以及如何控制ADC的采集速度。
adc.c文件中代码263-275行内容如下:
g_adc_nch_dma_handle.Instance = ADC_ADCX;
g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式 */
g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐 */
g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE; /* 扫描模式 */
g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; /* 连续转换模式,转换完成之后接着继续转换 */
g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */
g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM; /* 使用转换通道数,需根据实际转换通道去设置 */
g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0; /* 不连续采样通道数为0 */
g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 */
g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发, 此位忽略 */
g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE; /* 开启DMA连续转换 */
HAL_ADC_Init(&g_adc_nch_dma_handle);
其中的
g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
表示设置ADC的时钟频谱为APB2的4分频,APB2频率为84MHz,所以设置的ADC时钟频率为84/4 = 21MHz。
g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B;
表示设置ADC的分辨率为12比特。
adc.c文件中279-284行代码如下:
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_480CYCLES); /* 设置采样规则序列1~6 */
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_1, 2, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_2, 3, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_3, 4, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_4, 5, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_5, 6, ADC_SAMPLETIME_480CYCLES);
这部分用于设置DMA每一轮的采集顺序,ADC_SAMPLETIME_480CYCLES表示采样周期为480个ADC时钟周期。
计算MDA采集一次ADC数据的时间公式如下:
转换时间 = 单次采样时间 + 转换时间
单次采样时间 = 采样周期 × ADC一个时钟周期时间
转换时间 = ADC分辨率 × ADC一个时钟周期时间
本实例中设置的采样周期为480,ADC分辨率为12,一个ADC时钟周期时间为1秒 / 21M = 0.047619微秒。
所以一个ADC数据的采集时间 = 480 × 0.047619 + 12 × 0.047619= 23.4285微妙。
为了验证以上计算采集时间公式的的准确性,做如下实验:
首先让DMA一轮采集6个通道的ADC数据,一次采集10000轮,测试一次的采集时间,发现采集时间为1405毫秒。
1405毫秒 / (60000) = 23.416666,与计算得到的23.4285微秒基本相符,
接下来把ADC的分辨率设置为8比特,代码如下:
g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_8B;
测试采集时间为1394毫秒。
通过公式计算采集时间 (480 × 0.047619 + 8 × 0.047619) × 60000 = 1394毫秒,与实验结果相符。
所以从以上分析可以看出,ADC的采集速度,与ADC时钟周期、ADC采样周期、ADC分辨率有关。
接下来分析ADC时钟周期的设置方法。
前面提到过,本实例中设置的ADC时钟频谱是将APB2时钟频率进行4分频,可以分频的倍数如下:
#define ADC_CLOCK_SYNC_PCLK_DIV2 0x00000000U
#define ADC_CLOCK_SYNC_PCLK_DIV4 ((uint32_t)ADC_CCR_ADCPRE_0)
#define ADC_CLOCK_SYNC_PCLK_DIV6 ((uint32_t)ADC_CCR_ADCPRE_1)
#define ADC_CLOCK_SYNC_PCLK_DIV8 ((uint32_t)ADC_CCR_ADCPRE)
ADC时钟的源头是APB2,所以接下来讨论APB2的设置方式,比较直观的方式是看STM32CubeMX,如图:
本实例中,APB2的源头是AHB,AHB的源头SYSCLK,SYSCLK的源头是PLLCLK,PLLCLK的源头是HSE,
所以从晶振到ADC时钟频率的变换过程如下:
HSE(晶振)-> PLLCLK -> SYSCLK -> AHB -> APB2 -> ADC时钟
HSE表示高速外部振荡器(High Speed Extend Clock signal),也就是晶振,本实例用的开发板为8MHz。
LSE表示低速外部振荡器(Low Speed Extend Clock signal)。
HSI表示高速内部振荡器(High Speed Extend Internal signal),由内部RC振荡器产生,频谱为16Mhz,一般来说很少用,因为精度没有外部高速时钟那么高。
整个过程中比较重要的是通过PLL锁相环把8MHz的外表晶振频率转换成168MHz的系统时钟SYSCLK。其中重要的参数为M、N、P ,则:
SYSCLK = ((HSE / M) × N ) / P
程序中体现这3个参数为正点原子例程main函数中设置时钟部分:
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
其中336为N值、8为M值、2为P值。
所以通过通过PLL锁相环处理之后的系统时钟SYSCLK = ((8MHz / 8) × 336 ) / 2 = 168MHz
代码为sys.c文件中sys_stm32_clock_init() 函数141-149行
/* 使能HSE,并选择HSE作为PLL时钟源,配置PLL1,开启USB时钟 */
rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; /* 时钟源为HSE */
rcc_osc_init.HSEState = RCC_HSE_ON; /* 打开HSE */
rcc_osc_init.PLL.PLLState = RCC_PLL_ON; /* 打开PLL */
rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; /* PLL时钟源选择HSE */
rcc_osc_init.PLL.PLLN = plln;
rcc_osc_init.PLL.PLLM = pllm;
rcc_osc_init.PLL.PLLP = pllp;
rcc_osc_init.PLL.PLLQ = pllq;
ret = HAL_RCC_OscConfig(&rcc_osc_init); /* 初始化RCC */
从SYSCLK到AHB设置的代码为162行:
rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; /* AHB分频系数为1 */
表示AHB与SYSCLK频率相同,同样是168MHz。
从AHB到APB2设置的代码为164行:
rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV2; /* APB2分频系数为2 */
表示APB为AHB的2分频,也就是84MHz。
最后从APB2到ADC频率的设置代码为:
g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
表示ADC频率是APB2频率的4分频,也及时21MHz。
所以文档【STM32F4xx参考手册】中写的
其中12个周期貌似只是在ADC分辨率为12的时候。欢迎大家批评指正。