STM32 -ADC+DMA使用(巨全面)

1.前言

在STM32中,ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁¹。STM32的ADC为12位,AD最大值是4095,对应最大电压3.3V,可对0-3.3v之间的任意电压量化¹。STM32的ADC有18个输入通道,可测量16个外部和2个内部信号源¹。

在多通道数据采集中,如果使用DMA,那么ADC1和ADC3可以(ADC2不具备DMA功能)³。多通道采集会出现数据覆盖的现象,可以使用DMA进行数据传输²。例如,假若同时有三个通道进行采集数据:第一个通道采集完数据放到DR寄存器中,通过DMA迅速把数据搬移到16位数组的a [0];第二个通道采集完数据放到DR寄存器中,覆盖了第一个通道的数据,通过DMA迅速把数据搬移到16位数组的a [1];第三个通道采集完数据放到DR寄存器中,覆盖了第二个通道存放的数据,通过DMA迅速把数据搬移到16位数组的a [3]²。

这种方式可以有效地解决在多通道数据采集中的数据覆盖问题,提高数据采集的效率和精度¹²。具体的实现方式和步骤可能会因为具体的STM32型号和应用场景而有所不同,需要根据具体情况进行调整和优化¹²。希望这个解释能帮助你理解双ADC多通道+DMA的工作原理和

2.前期引入

2.1 ADC(模/数变换)

ADC是模/数变换的缩写,它允许我们将模拟信号转换为数字信号。在STM32中,ADC模块可以独立工作,但配合DMA传输可以更高效地采集多路信号。

2.2 DMA(直接内存访问)

DMA是直接内存访问的缩写,它允许数据在不经过CPU的情况下直接传输到内存。在STM32中,我们可以配置DMA来与ADC协同工作,实现高效的数据传输。

3.开发流程

  1. 初始化ADC和DMA。
  2. 配置ADC的工作模式,包括扫描模式、双ADC模式和连续转换模式。
  3. 配置DMA的工作模式,选择正常缓存模式或循环工作模式。
  4. 设置外部触发,以便ADC在上升沿触发时进行转换。
  5. 处理DMA中断,连接用户和外设。
  6. 编写代码示例,展示ADC+DMA的应用。

4.代码实例

3.1ADC多通道+DMA

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟
	
	/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6);			//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
	
	/*规则组通道配置*/
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);	//规则组序列1的位置,配置为通道0
	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);	//规则组序列2的位置,配置为通道1
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);	//规则组序列3的位置,配置为通道2
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);	//规则组序列4的位置,配置为通道3
	
	/*ADC初始化*/
	ADC_InitTypeDef ADC_InitStructure;					//定义结构体变量
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//模式,选择独立模式,即单独使用ADC1
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	//连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;	//扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
	ADC_InitStructure.ADC_NbrOfChannel = 4;			//通道数,为4,扫描规则组的前4个通道
	ADC_Init(ADC1, &ADC_InitStructure);				//将结构体变量交给ADC_Init,配置ADC1
	
	/*DMA初始化*/
	DMA_InitTypeDef DMA_InitStructure;							//定义结构体变量
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;	//外设基地址,给定形参AddrA
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设数据宽度,选择半字,对应16为的ADC数据寄存器
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址不自增,选择失能,始终以ADC数据寄存器为源
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;					//存储器基地址,给定存放AD转换结果的全局数组AD_Value
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;			//存储器数据宽度,选择半字,与源数据宽度对应
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;						//存储器地址自增,选择使能,每次转运后,数组移到下一个位置
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;							//数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
	DMA_InitStructure.DMA_BufferSize = 4;										//转运的数据大小(转运次数),与ADC通道数一致
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;								//模式,选择循环模式,与ADC的连续转换一致
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;								//存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;						//优先级,选择中等
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);								//将结构体变量交给DMA_Init,配置DMA1的通道1
	
	/*DMA和ADC使能*/
	DMA_Cmd(DMA1_Channel1, ENABLE);							//DMA1的通道1使能
	ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能
	ADC_Cmd(ADC1, ENABLE);									//ADC1使能
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准
	while (ADC_GetResetCalibrationStatus(ADC1) == SET);
	ADC_StartCalibration(ADC1);
	while (ADC_GetCalibrationStatus(ADC1) == SET);
	
	/*ADC触发*/
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作

3.2双ADC多通道+DMA

双ADC多通道+DMA主要是指在STM32微控制器中,使用两个模拟数字转换器(ADC)并行工作,同时通过直接内存访问(DMA)快速读取数据。这种方式可以提高数据采集的效率和精度¹²。

	GPIO_InitTypeDef GPIO_InitStructure;
	
	// ADC1 GPIO 初始化
	ADCx_1_GPIO_APBxClock_FUN ( , ENABLE );
	GPIO_InitStructure.GPIO_Pin = ;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(, &GPIO_InitStructure);

	// ADCx_2 GPIO 初始化
	ADCx_2_GPIO_APBxClock_FUN ( , ENABLE );
	GPIO_InitStructure.GPIO_Pin = ;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(T, &GPIO_InitStructure);	
}

/**
  * @brief  配置ADC工作模式
  * @param  无
  * @retval 无
  */
static void ADCx_Mode_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	ADC_InitTypeDef ADC_InitStructure;
	
	// 打开DMA时钟
	RCC_AHBPeriphClockCmd(, ENABLE);
	// 打开ADC时钟
	ADCx_1_APBxClock_FUN (, ENABLE );
	ADCx_2_APBxClock_FUN (K, ENABLE );
	
  /* ------------------DMA模式配置---------------- */	
	// 复位DMA控制器
	DMA_DeInit(ADC_DMA_CHANNEL);	
	// 配置 DMA 初始化结构体
	// 外设基址为:ADC 数据寄存器地址
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&( ADCx_1->DR ));	
	// 存储器地址
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;	
	// 数据源来自外设
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;	
	// 缓冲区大小,应该等于数据目的地的大小
	DMA_InitStructure.DMA_BufferSize = NOFCHANEL;	
	// 外设寄存器只有一个,地址不用递增
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	// 存储器地址递增
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 	
	// 外设数据大小
	DMA_InitStructure.DMA_PeripheralDataSize = 
	                                  DMA_PeripheralDataSize_Word;	
	// 内存数据大小,跟外设数据大小相同
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;	
	// 循环传输模式
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	
	// DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;	
	// 禁止存储器到存储器模式,因为是从外设到存储器
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;	
	// 初始化DMA
	DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);	
	// 使能 DMA 通道
	DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
	
	/* ----------------ADCx_1 模式配置--------------------- */
	// 双ADC的规则同步
	ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;	
	// 扫描模式
	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 = NOFCHANEL;			
	// 初始化ADC
	ADC_Init(ADCx_1, &ADC_InitStructure);	
	// 配置ADC时钟N狿CLK2的8分频,即9MHz
	RCC_ADCCLKConfig(RCC_PCLK2_Div8); 	
	// 配置ADC 通道的转换顺序和采样时间
	ADC_RegularChannelConfig(ADCx_1, ADCx_1_CHANNEL, 1, 
	                         ADC_SampleTime_239Cycles5);	
	// 使能ADC DMA 请求
	ADC_DMACmd(ADCx_1, ENABLE);
	
	
		/* ----------------ADCx_2 模式配置--------------------- */
	// 双ADC的规则同步
	ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;	
	// 扫描模式
	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 = NOFCHANEL;			
	// 初始化ADC
	ADC_Init(ADCx_2, &ADC_InitStructure);	
	// 配置ADC时钟为PCLK2的8分频,即9MHz
	RCC_ADCCLKConfig(RCC_PCLK2_Div8); 	
	// 配置ADC 通道的转换顺序和采样时间
	ADC_RegularChannelConfig(ADCx_2, ADCx_2_CHANNEL, 1, 
	                         ADC_SampleTime_239Cycles5);
	/* 使能ADCx_2的外部触发转换 */
  ADC_ExternalTrigConvCmd(ADC2, ENABLE);
	
	/* ----------------ADCx_1 校准--------------------- */
	// 开启ADC ,并开始转换
	ADC_Cmd(ADC1, ENABLE);	
	// 初始化ADC 校准寄存器  
	ADC_ResetCalibration(ADC1);
	// 等待校准寄存器初始化完成
	while(ADC_GetResetCalibrationStatus(ADC1));	
	// ADC开始校准
	ADC_StartCalibration(ADC1);
	// 等待校准完成
	while(ADC_GetCalibrationStatus(ADC1));
	
  /* ----------------ADC2 校准--------------------- */
		// 开启ADC ,并开始转换
	ADC_Cmd(ADC2, ENABLE);	
	// 初始化ADC 校准寄存器  
	ADC_ResetCalibration(ADC2);
	// 等待校准寄存器初始化完成
	while(ADC_GetResetCalibrationStatus(ADC2));	
	// ADC开始校准
	ADC_StartCalibration(ADC2);
	// 等待校准完成
	while(ADC_GetCalibrationStatus(ADC2));

	// 由于没有采用外部触发,所以使用软件触发ADC转换 
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

5.结语

总的来说,ADC多通道+DMA在STM32微控制器中提供了一种高效且精确的数据采集方法。通过使用DMA,我们可以避免在多通道数据采集中的数据覆盖问题,从而提高数据采集的效率和精度。这种技术在许多实时、高精度的应用中都有广泛的应用,如音频处理、图像处理等。。。

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
嗨!对于STM32F1系列微控制器,你可以使用ADC(模数转换器)和DMA(直接内存访问)来实现ADC采样。以下是一些步骤和示例代码来帮助你开始: 1. 配置ADC参数: - 选择采样通道:通过设置ADC_SQR3、ADC_SQR2和ADC_SQR1寄存器来选择要采样的通道。 - 设置采样时间:通过设置ADC_SMPR2和ADC_SMPR1寄存器来配置采样时间。 2. 配置DMA参数: - 设置外设地址:将ADC数据寄存器地址(ADC_DR)设置为DMA外设地址。 - 设置内存地址:将一个内存数组的地址设置为DMA内存地址。 - 设置数据传输方向:将DMA_CCR寄存器的DIR位设置为从外设到内存。 3. 配置中断(可选): - 如果你想在每次转换完成后获得中断通知,可以启用ADC的转换完成中断,并在中断处理函数中处理数据。 4. 启动ADCDMA: - 启用ADC:通过设置ADC_CR2寄存器的ADON位启用ADC。 - 启用DMA请求:通过设置ADC_CR2寄存器的DMA位启用DMA请求。 - 启动DMA传输:通过设置DMA_CCR寄存器的EN位启动DMA传输。 以下是一个简单的示例代码片段,演示了如何配置ADCDMA进行连续采样: ```c #include "stm32f1xx_hal.h" ADC_HandleTypeDef hadc; DMA_HandleTypeDef hdma_adc; uint16_t adc_values[100]; // 存储采样结果的数组 void ADC_DMA_Init(void) { // 初始化ADCDMA hadc.Instance = ADC1; hadc.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc.Init.ContinuousConvMode = ENABLE; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc); hdma_adc.Instance = DMA1_Channel1; hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc.Init.MemInc = DMA_MINC_ENABLE; hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc.Init.Mode = DMA_CIRCULAR; hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_adc); __HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc); // 配置ADC通道和采样时间 ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; // 假设采样通道为ADC1的通道0 sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&hadc, &sConfig); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { // 采样完成中断处理函数 // 在这里处理采样结果 } int main(void) { // 初始化硬件和库 HAL_Init(); SystemClock_Config(); // 配置ADCDMA ADC_DMA_Init(); // 启动ADCDMA HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_values, sizeof(adc_values) / sizeof(uint16_t)); while (1) { // 主循环代码 // 在这里可以进行其他任务 } } ``` 这只是一个简单的示例,你可以根据你的需求进行自定义。希望对你有所帮助!如果有任何问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值