ADC-双ADC交叉模式

更多交流欢迎关注作者抖音号:81849645041

目标

        掌握ARM Cortex-M 系列芯片外设多ADC工作模式的工作原理,通过配置STM32F407的双ADC模式,使用ADC1和ADC2来同时测量引脚电压。

原理

        AD转换包括采样阶段和转换阶段,在采样阶段才对通道数据进行采集;而在转换阶段只是将采集到的数据进行转换为数字量输出,此刻通道数据变化不会改变转换结果。独立模式的 ADC 采集需要在一个通道采集并且转换完成后才会进行下一个通道的采集。双重或者三重 ADC的机制使用两个或以上 ADC 同时采样两个或以上不同通道的数据或者使用两个或以上 ADC 交叉采集同一通道的数据。双重或者三重 ADC 模式较独立模式一个最大的优势就是转换速度快。

        在有 2 个或以上 ADC 模块的产品中,可以使用双 ADC 模式和三 ADC 模式。在多 ADC 模式里,根据 ADC_CCR 寄存器中 MULTI[4:0]位所选的模式,转换的启动可以是 ADC1 主,ADC2 和 ADC3 从的交替触发或同步触发。

        在多 ADC 模式里,当转换配置成由外部事件触发时,用户必须将其设置成仅触发主 ADC,从ADC 设置成软件触发,这样可以防止意外的触发从转换。

        ADC共有4种可能的模式:

  1. 同步注入模式
  2. 同步规则模式
  3. 交叉模式
  4. 交替触发模式

还有可以用下列方式组合使用上面的模式:

  1. 同步注入模式+ 同步规则模式
  2. 同步规则模式+ 交替触发模式

在双 ADC 模式,ADC 公共的数据寄存器(ADC_CDR)包含 ADC1,ADC2 和 ADC3 的规则转换数据,32 位寄存器的所有位都将被使用。

在多DMA模式下,DMA支持3种模式:

  • DMA 模式 1:每次 DMA 请求(只有一个数据项是有效的),一个半字代表 ADC 转换数据项被传输。

        在双 ADC 模式,第一个请求下,ADC1 数据先被传输,第二个请求下,ADC2 数据被传输,以此类推。

        在三 ADC 模式,第一个请求下,ADC1 数据被传输,第二个请求下,ADC2 数据被传输,第三个请求下,ADC3 被传输,依次类推。

        DMA 模式 1 用于规则通道的三 ADC 模式。

        举一个例子:

        规则通道的三 ADC 模式:产生 3 个联系的 DMA 请求(一个请求对应一次转换数据项)。

        1st request: ADC_CDR[31:0] = ADC1_DR[15:0]

        2nd request: ADC_CDR[31:0] = ADC2_DR[15:0]

        3rd request: ADC_CDR[31:0] = ADC3_DR[15:0]

        4th request: ADC_CDR[31:0] = ADC1_DR[15:0]

  • DMA 模式 2:每次 DMA 请求(两个数据项有效)代表 2 个 ADC 转换数据项被传输,也就是一个字。

        在双 ADC 模式,第一个请求下,ADC2 和 ADC1 数据同时被传输(ADC2 占高 16 位,ADC1 占低16 位),后面以此类推。

        在三 ADC 模式,第一个请求下 ADC2 和 ADC1 数据被传输,第二个请求下 ADC1和 ADC3 被传输,第三个请求下 ADC3 和 ADC2 数据被传输。

        DMA 模式 2 用于交叉模式和规则同步模式(仅双 ADC 模式)。

        举一个例子:

        双 ADC 交叉模式:每次两个数据项有效是将产生 DMA 请求。

        1st request: ADC_CDR[31:0] = ADC2_DR[15:0] | ADC1_DR[15:0]

        2nd request: ADC_CDR[31:0] = ADC2_DR[15:0] | ADC1_DR[15:0]

        三 ADC 交叉模式:每次两个数据项有效是将产生 DMA 请求。

        1st request: ADC_CDR[31:0] = ADC2_DR[15:0] | ADC1_DR[15:0]

        2nd request: ADC_CDR[31:0] = ADC1_DR[15:0] | ADC3_DR[15:0]

        3rd request: ADC_CDR[31:0] = ADC3_DR[15:0] | ADC2_DR[15:0]

        4th request: ADC_CDR[31:0] = ADC2_DR[15:0] | ADC1_DR[15:0]

  • DMA 模式 3:这个模式和模式 2 类似,仅有的不同是每次 DMA 请求(两个数据项有效)两个字节代表两个 ADC 转换的数据项,也就是一个半字,数据传输顺序和 DMA 模式 2 类似。

        双 ADC 交叉模式:每次两个数据项有效是将产生 DMA 请求。

        1st request: ADC_CDR[31:0] = ADC2_DR[7:0] | ADC1_DR[7:0]

        2nd request: ADC_CDR[31:0] = ADC2_DR[7:0] | ADC1_DR[7:0]

        三 ADC 交叉模式:每次两个数据项有效是将产生 DMA 请求

        1st request: ADC_CDR[31:0] = ADC2_DR[7:0] | ADC1_DR[7:0]

        2nd request: ADC_CDR[31:0] = ADC1_DR[7:0] | ADC3_DR[7:0]

        3rd request: ADC_CDR[31:0] = ADC3_DR[7:0] | ADC2_DR[7:0]

        4th request: ADC_CDR[31:0] = ADC2_DR[7:0] | ADC1_DR[7:0]

准备

        MDK5 开发环境。

        STM32F4xx HAL库。

        STM32F407 开发板。

        STM32F4xx 参考手册。

        STM32F407 开发板电路原理图。

        串口调试助手软件。

        USB转TTL模块。

步骤

  • 本实验我们选取ADC1_IN4作为数据采集通道,首先分别定义ADC1和ADC2初始化结构体,以及接收数据存储变量。
ADC_HandleTypeDef    AdcHandle1; // ADC1初始化句柄

ADC_HandleTypeDef    AdcHandle2; // ADC2初始化句柄

uint16_t uhADCDualConvertedValue; // 获取转换值的变量
  • 创建MultiADC_Config()函数,用来初始化ADC1和ADC2以及相应配置。

        第一步:调用HAL_DMA_Init()函数,初始化ADC的DMA传输。

        第二步:调用HAL_ADC_Init()函数,初始化ADC1并初始化ADC1的通道4。

        第三步:调用HAL_ADC_Init()函数,初始化ADC2并初始化ADC2的通道4。

        第四步:调用HAL_ADCEx_MultiModeConfigChannel()函数,配置ADC为双重交叉模式,且配置DMA模式为模式三,每次DMA请求传输两个字节(ADC1和ADC2各一个字节)。最后开启ADC的传输。

// ADC1和ADC2初始化函数
void MultiADC_Config(void)
{
	// DMA初始化
 	DMA_HandleTypeDef  hdma_adc;
	
  	__HAL_RCC_DMA2_CLK_ENABLE();
	
 	hdma_adc.Instance = DMA2_Stream0; // 数据流
 
 	hdma_adc.Init.Channel  = DMA_CHANNEL_0; // 通道0
  	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_PDATAALIGN_HALFWORD; // 一次传输半字
  	hdma_adc.Init.Mode = DMA_CIRCULAR; // 循环转换模式
  	hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; // 优先级高
  	hdma_adc.Init.MemBurst = DMA_MBURST_SINGLE; // 突发
  	hdma_adc.Init.PeriphBurst = DMA_PBURST_SINGLE;
  	HAL_DMA_Init(&hdma_adc);		
	
	// 初始化ADC1
  	AdcHandle1.Instance = ADC1;
  	AdcHandle1.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV4; // 4分频
  	AdcHandle1.Init.Resolution = ADC_RESOLUTION_8B; // 8bit
  	AdcHandle1.Init.ScanConvMode = DISABLE; // 扫描模式不使能
  	AdcHandle1.Init.ContinuousConvMode = ENABLE; // 连续转换模式使能
  	AdcHandle1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发
  	AdcHandle1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; // 使用软件触发
  	AdcHandle1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐
  	AdcHandle1.Init.NbrOfConversion = 1;
  	AdcHandle1.Init.DMAContinuousRequests = ENABLE; // 使用DMA
	AdcHandle1.DMA_Handle = &hdma_adc; // 传递DMA句柄
 	AdcHandle1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; // 开启EOC转换一次完成中断
  	HAL_ADC_Init(&AdcHandle1); // 初始化ADC1
	
	// 配置ADC1通道
	ADC_ChannelConfTypeDef ADC_ChanneConf; // ADC通道配置
  	ADC_ChanneConf.Channel = ADC_CHANNEL_4; // ADC1通道
  	ADC_ChanneConf.Rank = 1; // 转换等级
  	ADC_ChanneConf.SamplingTime = ADC_SAMPLETIME_3CYCLES; // 采样周期
  	HAL_ADC_ConfigChannel(&AdcHandle1, &ADC_ChanneConf);
	
	// 初始化ADC2
  	AdcHandle2.Instance = ADC2;
  	AdcHandle2.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV4;
  	AdcHandle2.Init.Resolution = ADC_RESOLUTION_8B;
  	AdcHandle2.Init.ScanConvMode = DISABLE;
  	AdcHandle2.Init.ContinuousConvMode = ENABLE;
  	AdcHandle2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  	AdcHandle2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  	AdcHandle2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  	AdcHandle2.Init.NbrOfConversion = 1;
  	AdcHandle2.Init.DMAContinuousRequests = ENABLE;
	AdcHandle2.DMA_Handle = &hdma_adc; // 传递DMA句柄	
  	AdcHandle2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  	HAL_ADC_Init(&AdcHandle2); // 初始化ADC2

	// 配置ADC2通道
	ADC_ChanneConf.Channel = ADC_CHANNEL_4; // ADC2通道
  	HAL_ADC_ConfigChannel(&AdcHandle2, &ADC_ChanneConf);
	
	// 配置双重交叉模式
	ADC_MultiModeTypeDef   ADC_Multimode;
 	ADC_Multimode.Mode = ADC_DUALMODE_INTERL; // 双ADC交叉模式
  	ADC_Multimode.DMAAccessMode = ADC_DMAACCESSMODE_3; // 每次DMA请求传输两个字节 ADC1/ADC2数据一起传输
	ADC_CDR[15:0] = ADC2_DR[7:0] | ADC1_DR[7:0]
  	ADC_Multimode.TwoSamplingDelay = ADC_TWOSAMPLINGDELAY_6CYCLES; // 配置两个采样之间的延迟
  	HAL_ADCEx_MultiModeConfigChannel(&AdcHandle1, &ADC_Multimode);
	
	HAL_ADC_Start(&AdcHandle2); // 开始ADC2
	
	HAL_ADCEx_MultiModeStart_DMA(&AdcHandle1, (uint32_t*)&uhADCDualConvertedValue, 1);
}
  • 查找STM32F4数据手册,可知ADC1_IN4和ADC2_IN4通道都在PA4引脚。

  •  重定义HAL_ADC_MspInit()函数,初始化PA4引脚,并使能ADC1和ADC2时钟。
// 初始化ADC底层驱动引脚
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
	GPIO_InitTypeDef GPIO_Initure;
	
  	__HAL_RCC_ADC1_CLK_ENABLE(); // 使能ADC1时钟
		__HAL_RCC_ADC2_CLK_ENABLE(); // 使能ADC2时钟
  	__HAL_RCC_GPIOA_CLK_ENABLE(); // 开启GPIOA时钟
	
  	GPIO_Initure.Pin = GPIO_PIN_4; // PA4
  	GPIO_Initure.Mode = GPIO_MODE_ANALOG; // 模拟
 	GPIO_Initure.Pull = GPIO_NOPULL; // 浮空
  	HAL_GPIO_Init(GPIOA, &GPIO_Initure);
}
  • 定义结构体ADC_DATA,用来存储ADC1和ADC2测出的数字值和计算后的电压。
typedef struct
{
	uint8_t adc1_value; // 存储ADC1测出的值
	uint8_t adc2_value; // 存储ADC1测出的值
	float adc1_voltage; // 存储计算得到的ADC1电压
	float adc2_voltage; // 存储计算得到的ADC2电压
}ADC_DATA;
  • 创建MultiADC_GetData()函数,用来处理ADC测得的数据。函数中首先获取数据变量uhADCDualConvertedValue的高8位数据和低8位数据,其中低8位为ADC1测出的数据,高8位为ADC2测出的数据。获取数据后分别计算ADC1和ADC2的电压。
// 数据处理函数
void MultiADC_GetData(void)
{
	// 获取ADC1/ADC2测出数字值
	adc_data.adc1_value = (uhADCDualConvertedValue & 0x00FF);
	adc_data.adc2_value = (uhADCDualConvertedValue >> 8);
	
	// 计算ADC1/ADC2得到的电压
	adc_data.adc1_voltage = adc_data.adc1_value * 3.3 / 255;
	adc_data.adc2_voltage = adc_data.adc2_value * 3.3 / 255;	
}
  • 主函数main程序如下:

        第一步:初始化系统时钟和串口。

        第二步:调用MultiADC_Config()函数,初始化双ADC及交叉模式。

        第三步:调用MultiADC_GetData()函数,分别获取两个ADC的数据。

        第四步:在while()循环中每隔100ms将ADC1和ADC2通过串口打印出来。

extern ADC_DATA adc_data;

int main()
{
	CLOCK_Init(); // 时钟初始化
	UART_Init(); // 串口初始化

	MultiADC_Config(); // 双ADC交叉模式初始化

	while(1)
	{
		MultiADC_GetData(); // 双重ADC交叉模式获取数据
		printf("ADC1 voltage: %.2f ADC2 voltage: %.2f\r\n", adc_data.adc1_voltage, adc_data.adc2_voltage);
		HAL_Delay(100);
	}
}

现象

        将程序下载到开发板中,可以通过串口打印出ADC1和ADC2分别测出的电压值。

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奚海蛟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值