STM32 ADC转换+DMA传输(详解)

这篇具有很好参考价值的文章主要介绍了STM32 ADC转换+DMA传输(详解)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、选题背景

        最近刚入坑,看了半个多月的入门视频并动手了一些简单的实验,但看工程项目的代码总是很费劲,便想以一个有难度的课题来进一步入门嵌入式开发。这个选题充分使用了STM32的各种片上外设,包括定时器、ADC模/数变换、GPIO口和DMA的使用,配合外部资源如LCD屏幕来展示结果。


2、实验介绍

实验目标:

        从一个简单的实验出发,逐步加大难度,增加单片机资源的使用,做一个有意思且有意义的学习课题。注:本实验的全部例程使用库函数进行编写,文中提到的模式关键字均为库函数所定义。本实验对硬件部分不过多关注,仅在必要时稍作解释

        首先理解为什么ADC需要和DMA进行配合,ADC本身可以独立工作,并通过中断服务程序获取每次采样的值。但这仅是基本功能,通常只能采样单通道的信号,每次转换完毕后存储到特定寄存器内(每次转换结果会覆盖上一次的值)并触发中断,频繁地中断又会消耗资源、浪费时间。而配合DMA传输,可以采集多路信号,可以等待采集了大量数据传入内存,一次性进行处理

       下面通过模块的不同工作模式来探讨ADC和DMA的配合工作。

        对于ADC模块,由于注入通道不会产生DMA请求,因此以下主要介绍规则通道的一些工作模式(所谓规则通道,即ADC的输入口中稳定工作的部分,区别于注入通道类似中断的方式进行采样):

(1)扫描模式(ADC_ScanConvMode)。控制是否使用多通道,启用后:ADC每次转换完一个通道,会继续去到下一个通道进行采样转换;否则仅转换当前通道。启用后通常需要配合DMA进行数据存储,否则在进入下一个通道转换后的值会覆盖上一次转换的结果(ADC规则通道的数据寄存器只有一个)。

(2)双ADC模式(ADC_Mode)。增强型的STM32芯片通常具有三个ADC外设,其中ADC1和ADC3都可以使用DMA进行传输,ADC2虽然没有直接与DMA相连,但可以与ADC1配合,在双ADC模式下共同工作并使用DMA传输,提高工作效率。因此双模式主要针对ADC1和ADC2,二者既可以相互独立工作,也可以主从配合工作,以下模式适用于规则通道

  • 独立模式(ADC_Mode_Independent),两个ADC相互独立工作,通常在仅使用一个ADC时使用;
  • 同步规则模式(ADC_Mode_RegSimult),ADC1和ADC2同时转换规则通道,但不能是同一个规则通道,即同一时间内两个ADC不能转换同一个规则通道,转换结果存储为32位(各占高低16位)。
  • 快速交叉模式(ADC_Mode_FastInterl),外部触发启动,ADC2先工作进行采样,延迟7个ADC时钟周期后ADC1启动进行采样,此模式通常仅采样一个规则通道,能加快采样率。要注意采样时间不要小于7个ADC时钟周期,避免重复采样(如下图)。

stm32 adc dma,stm32,单片机,嵌入式硬件

  • 慢速交叉模式(ADC_Mode_SlowInterl),与快速交叉模式类似,ADC1、ADC2轮流工作,但均需要等待上一次采样结束14个ADC时钟周期后才启动。该模式会比快速交叉模式的采样速度慢一些,但比独立模式还是快不少,同样要注意采样时间不要小于14个ADC时钟周期。

stm32 adc dma,stm32,单片机,嵌入式硬件

(3)连续转换模式(ADC_ContinuousConvMode)。控制是否连续转换,使能后通道会进行连续的转换,在一轮转换后(即所有通道转换完一遍),又从第一个通道重新开始下一轮;否则通道在结束一轮转换后停止工作。

        该配置与下面要介绍的外部触发的配置息息相关

  • 在配置了外部触发(尤其是定时器触发,如ADC_ExternalTrigConv_T2_CC2)后,每次相关事件发生时会触发ADC的转换,此时若仍然连续转换,便失去了外部触发的意义。但也有例外,即IO口的触发,通常仅触发一次,相当于一次使能操作,便可以设置连续转换。外部触发方式的优点是容易控制,更容易符合特定场景需求;
  • 如果没有设置外部触发(ADC_ExternalTrigConv_None),则由ADC内部时钟和寄存器配合转换工作。在连续转换模式下,其采样率远远大于普通定时器触发,但缺点也很明显,过快的采样速度要求后续的工作也有超高的处理速度,会给CPU带来巨大的运算负担,在一些计算复杂度较高的场景并不适用。

(4)触发方式(ADC_ExternalTrigConv)。即如何触发ADC的转换工作,共有以下8中选择:

stm32 adc dma,stm32,单片机,嵌入式硬件

 其中前6种都是定时器触发,而第八种则由软件控制使能(仅需使能一次),本实验使用定时器触发,选择的是TIM2_CC2事件(ADC_ExternalTrigConv_T2_CC2)。要注意,使用外部触发方式时,只有上升沿才能开启转换,这涉及到后面TIM的配置

        对于DMA模块,在和ADC协同工作时,主要有以下几点需要注意:

(1)工作模式(DMA_Mode)。DMA的工作模式有两种选择,区别在于是否连续工作:

  • 正常缓存模式(DMA_Mode_Normal),在该模式下,每次使能仅进行一轮传输,一旦计数器的值减至0时,DMA传输会停止。想要再次传输,必须重新设置计数器CNDTR的数值;
  • 循环工作模式(DMA_Mode_Circular),主要处理连续的数据传输,每进行一轮传输,计数器会被自动恢复为初始值,同时DMA传输进入下一轮,非常适合同ADC多通道(扫描)模式一起工作。

(2)DMA通道。下面展示了DMA1的所有通道,为了和ADC1连上,需要设置好DMA1的通道1(DMA1_Channel1)。

stm32 adc dma,stm32,单片机,嵌入式硬件

注:ADC3可以使用DMA2进行传输,在这里没有展示。而ADC2天生不与DMA发生直接关系,只能配合ADC1进行主从传输。

(3)DMA中断。DMA的中断是这个实验的桥梁,连接着用户和外设,我们需要在中断里处理数据,包括计算和展示数据等,但这样很浪费资源。有一种更好的做法是,基于UCOS系统,在中断服务程序中通过事件标志的方式通知数据处理相关的任务及时运行。

        对于通用定时器的配置,在前面说到ADC需要外部的上升沿触发,那么普通的方波就可以满足这一需求,同时也要具有稳定的周期。先来看一下官方的输出比较模式介绍:

stm32 adc dma,stm32,单片机,嵌入式硬件

         其中翻转模式(TIM_OCMode_Toggle,011)和两个PWM模式都可以满足需求,这两种模式都具有“比较”和“翻转”的能力,其它模式如000、100和101只能强制高或低电平,001和010仅适用特殊配置和场景,都不满足需求。

        到这里核心的配置就基本完成了!

涉及到的知识脑图:

stm32 adc dma,stm32,单片机,嵌入式硬件 

3、代码详解

(1)ADC配置

    ADC_InitTypeDef ADC_InitStructure;
    /* 使能ADC1时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	/* 双ADC模式选择,可以是独立或同步规则模式 */
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	/* 扫描模式,多通道 */
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	/* 单次转换,不连续转换 */
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	/* 使用定时器2的CC2触发 */
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;
	/* 使用内部时钟和寄存器触发 */
	// ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	/* 右对齐,左对齐会向左产生四位的位移 */
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	/* 采样通道数量 */
	ADC_InitStructure.ADC_NbrOfChannel = 2;
	ADC_Init(ADC1, &ADC_InitStructure);
	/* 配置ADC时钟(12MHz) */
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	/* ADC通道配置,包括通道、采样顺序和采样周期 */
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_239Cycles5);
    /* 使能ADC1 */
	ADC_Cmd(ADC1, ENABLE);
	/* 复位ADC1校准寄存器 */
	ADC_ResetCalibration(ADC1);
	/* 等待复位完成 */
	while (ADC_GetResetCalibrationStatus(ADC1));
	/* 进行ADC1校准 */
	ADC_StartCalibration(ADC1);
	/* 等待校准完成 */
	while (ADC_GetCalibrationStatus(ADC1));
	/* 使能外部触发,使用外部触发必须要做 */
	ADC_ExternalTrigConvCmd(ADC1, ENABLE);
    /* 使能ADC发送数据到DMA总线,必须要做 */
    ADC_DMACmd(ADC1, ENABLE);

(2)DMA配置

    DMA_InitTypeDef DMA_InitStructure;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	/* 复位DMA1通道1,可做可不做 */
	DMA_DeInit(DMA1_Channel1);
	/* 外设地址,即ADC1的数据寄存器,数据长度为16位 */
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(ADC1->DR));
	/* 内存地址,自定义数组并取首地址,注意要用16位,与ADC数据对称 */
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&DMA_ADC_PeripheralBaseAddr[0];
	/* 外设作为传输源,即ADC->DMA */
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	/* 一轮传输的数据数量,最好和内存数组长度对应 */
	DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
	/* 外设地址在传输过程中不自增,因为ADC规则通道的数据寄存器只有一个 */
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	/* 内存地址传输过程自增 */
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	/* 传输的数据都是16位的,即半字(字,不是字节) */
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	/* Normal模式单次传输,Circular模式可以自动重装循环传输 */
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	/* DMA任务优先级 */
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	/* 非内存->内存模式 */
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	/* 使能DMA1通道1 */
	DMA_Cmd(DMA1_Channel1, ENABLE);
	/* 使能传输完毕中断,这样才能接收到该中断 */
	DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);

(3)TIM配置

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	/* 定时器基本配置,使用TIM2去触发,故初始化TIM2 */
	/* 分频系数71,系统主频72M */
	TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_Period = 1000;
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	/* 定时器输出比较配置,PWM1输出 */
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    /* 输出比较的值,即TIM2_CCR2 */
	TIM_OCInitStructure.TIM_Pulse = 5000;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);
	/* 使能TIM2 */
	TIM_Cmd(TIM2, ENABLE);
    /* 使能TIM2内部时钟 */
	TIM_InternalClockConfig(TIM2);
    /* 使能TIM2在CCR2上的预装载寄存器 */
	TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
    /* 失能TIM2更新事件 */
	TIM_UpdateDisableConfig(TIM2, DISABLE);

(4)GPIO配置

    GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	/* 设置GPIOA的0号引脚为模拟输入 */
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

(5)中断配置

    NVIC_InitTypeDef NVIC_InitStructure;
    /* 设置中断分组为2 */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	/* 设置DMA1通道1的中断 */
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
	/* 设置抢占优先级 */
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	/* 设置次优先级 */
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	/* 使能中断 */
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

(6)中断服务程序

这一部分的代码比较自由,根据个人喜好处理数据

void DMA1_Channel1_IRQHandler(void)
{
	OS_CPU_SR cpu_sr;
    /* 进入临界区 */
	OS_ENTER_CRITICAL();      
    OSIntEnter();
    OS_EXIT_CRITICAL();
    /* 判断中断是否是DMA传输完成中断 */
	if(DMA_GetFlagStatus(DMA1_IT_TC1))
	{
        /* 失能DMA1通道1,以防在中断过程中发生DMA传输 */
		DMA_Cmd(DMA1_Channel1, DISABLE);
        /* 如果DMA设置的是normal模式,需要手动重装计数器 */
		// DMA1_Channel1->CNDTR = BUFFER_SIZE;
        /* 清除中断标志位 */
		DMA_ClearITPendingBit(DMA1_IT_TC1);
		// DMA_ClearFlag(DMA1_FLAG_TC1);
        /* 重新使能DMA1通道1,开始工作 */
		DMA_Cmd(DMA1_Channel1, ENABLE);
	}
	OSIntExit();
}

以上这些代码仅仅为配置代码,实际需要根据不同程序的需求进行调整,尤其是使能指令的顺序。

4、总结

        本文在编写的过程中,出现过很多纰漏和错误,我相信文中肯定还有很多不够严谨和细节的地方,欢迎指正和讨论,笔者单片机入门小白,多来一点批评和指导鞭策我吧^_^

        在最后,想给看到的同学说句话,学任何东西都要讲究方法,并坚持,看到了别人的博文,不论有多详细都仅仅是启发,最好自己回头去看看文档资料,并做做实验,能坚持的可以总结出来输出新的博文,带着批判的眼神去审视自己写的内容,才能扎实并精益求精。

       文章来源地址https://www.toymoban.com/news/detail-778443.html

到了这里,关于STM32 ADC转换+DMA传输(详解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • STM32 hal库使用笔记(五)ADC—单通道/双通道DMA传输

    STM32 hal库使用笔记(五)ADC—单通道/双通道DMA传输

    实现目的:利用ADC采集光敏传感器/烟雾传感器的值,并利用串口打印 实验平台:正点原子精英版 一、简介 1.DMA的介绍 参考:STM32 hal库使用笔记(四)DMA—内存到内存/内存到外设_乱码小伙的博客-CSDN博客 2.ADC简介      ADC(Analog-Digital Converter)模拟-数字转换器 ADC可以将引脚

    2024年02月03日
    浏览(10)
  • 【第五章】STM32-ADC模数转换(2.AD多通道+DMA转运实验)

    【第五章】STM32-ADC模数转换(2.AD多通道+DMA转运实验)

    我们在上一节已经了解了ADC以及AD单通道采集的过程,那么既然有AD单通道,那么必然有AD多通道,上一节也已经铺垫了一下: 【问】如果一个规则组同时用多个通道采集数据,那么数据如何读取?                                              --DMA--    

    2024年04月26日
    浏览(12)
  • 基于STM32F103C8T6的高速DMA传输多通道ADC数据

    ADC在STM32系列单片机的使用中占用着很大的比例,常见的案例是通过ADC单次转换电压值,这种方式的缺陷在于转换效率不高。一般的单片机带有ADC1和ADC2两个ADC转换,单次转换需要执行一定的程序,想得到结果需要耗费一些时间在赋值,调用中断上面。在此基础上,为了提高转

    2024年02月11日
    浏览(8)
  • STM32 TIMER_TRGO触发+ADC采集 + DMA传输 + 中断均方根处理 实现三相电压显示

    STM32 TIMER_TRGO触发+ADC采集 + DMA传输 + 中断均方根处理 实现三相电压显示

    STM32 TIMER_TRGO触发+ADC采集 + DMA传输 实现三相电压采集 首先,是实际采集的三相电压值,用excel处理了下: 采集个电压,为什么这么复杂。 开始我也是直接用ADC采集,然后delay,再采集,然后delay,再采集……最后数据处理…… 问题是如果我们用单片机裸跑,每次delay都会卡死

    2024年02月16日
    浏览(8)
  • STM32 HAL库 ADC+DMA

    STM32 HAL库 ADC+DMA

       软件触发:STM32 HAL库 软件触发ADC 多通道连续转换_随风飘零翼的博客-CSDN博客 配置如图      注意采样周期不要过小,不然频繁中断会导致在RTOS中卡死 写了部分关键代码,在两个任务中OLED和串口打印分别显示的通道值。 后来发现使用的杜邦线接触不良,固定好之后,接

    2024年02月14日
    浏览(6)
  • STM32 ADC采集 DMA中断处理

    //============================================ //函数名称:ADC1_Mode_Config(void) //功能描述:配置ADC1的工作模式为MDA模式 //输入:无 //输出:无 //============================================ void ADC1_Mode_Config(void) {         DMA_InitTypeDef DMA_InitStructure;         ADC_InitTypeDef ADC_InitStructure;              

    2024年02月14日
    浏览(13)
  • STM32 -ADC+DMA使用(巨全面)

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

    2024年04月25日
    浏览(6)
  • STM32 ADC+定时器+DMA+FFT

    STM32 ADC+定时器+DMA+FFT

    本次实现的功能为单片机DAC输出一个正弦波,然后ADC定时采样用DMA输出,最后对DAC输出的波形进行FFT。 单片机STM32F103ZET6 内部时钟 一、配置ADC ADC端口为PA1,采用DMA输出,定时器3触发 定时器时钟64M,分频后为102.4KHz ADC采样时间为102.4KHz/100=1.024KHz 二、配置DAC DAC端口PA4 DMA传输

    2024年02月13日
    浏览(12)
  • STM32 ADC单/多通道采样+DMA搬运

    STM32 ADC单/多通道采样+DMA搬运

    通过介绍我们可以了解到,ADC是12位的转换器,所以采样值范围是0~4095。18个通道可同时进行转换,也可以单独转换某个通道。 使用ADC的流程应为: 初始化IO口。 我这里使用的是PA1进行采样,也就是ADC1的通道1 初始化ADC。 转换、获取采样值。 多通道的时候我们一般用DMA来搬

    2024年02月14日
    浏览(11)
  • 【STM32】- 定时器+DMA+ADC 双重模式

    【STM32】- 定时器+DMA+ADC 双重模式

    目录   1 前言 2 ADC介绍 2.1 多重工作模式 2.2 多重ADC框图 2.3 规则同时模式 3 程序设计 3.1 时序图 3.2 初始化流程图 3.3 初始化代码 4 结论        关于ADC,相信大家都比较了解,关于STM32的学习教程都会有所讲解,但以查询方式、单通道讲解的较多,主要告诉大家基本的原理。

    2024年02月10日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包