引言

ADC 的功能是将模拟信号采样得到数字信号,而有些时候,我们需要使用到定时采样,比如在计算一个采集的波形的频率的时候,我们需要精确的知道采样频率,也就是 1 s 内采集的点数,这个时候,就需要使用到定时采集。定时采样有如下三种方法:

  • 使用定时器中断,每隔一段时间进行 ADC 转换,但是这样每次都必须读 ADC 的数据寄存器,非常浪费时间。

  • 把 ADC 设置成连续转换模式,同时对应的 DMA 通道开启循环模式,这样 ADC 就一直在进行数据采集然后通过 DMA 把数据搬运至内存。这样进行处理的话,需要加一个定时中断,用来读取内存中的数据。

  • 使用 ADC 的定时器触发 ADC 转换的功能,然后使用 DMA 进行数据的搬运。这样就只要设置好定时器的触发间隔,就能实现 ADC 定时采样转换的功能,然后使能 DMA 转换完成中断,这样每次转换完就会产生中断。

本文,笔者将采用第三种方法进行 AD 采集,使用 TIM 定时器触发 AD 采集,然后 DMA 搬运至内存。

ADC 简介

首先来看一下 ADC 的框图:

ADC 框图

在本文中,我们使用的是规则通道进行转换,这里要指出的一点是规则通道和注入通道两者的区别,以下是关于两种通道的说明:

  • 规则通道:我们平时使用的就是这个通道,就是规规矩矩的按照我们设定的转换顺序就行转换的通道。

  • 注入通道:注入通道可以理解为是插入,也就是插队的意思,它是一种不安分的通道。它是一种在规则通道转换的时候强行插入要进行转换的一种,它的存在就像是程序中的中断一样,换个角度说,也就是注入通道只有在规则通道存在的情况下才会存在。

说了规则通道和注入通道的区别之后,我们来看我们在本文中所用到的规则通道的触发方式。我们最为常用的一种就是软件触发,即配置到 ADC 之后,就会自动地进行转换,然后去读 ADC 的数据寄存器就可以得到 ad 转换得到的数值。还有一种方法就是外部触发,而外部触发又包括定时器触发和外部 IO 触发,在本文中,我们使用的是定时器触发,通过上述的 ADC 功能框图,我们可以知道 ADC 的定时器触发又有如下几种类型:

  • TIM1_CH1 :定时器 1 的通道 1 的 PWM 触发

  • TIM1_CH2 : 定时器 2 的通道 2 的 PWM 触发

  • TIM1_CH3: 定时器 1 的通道 3 的 PWM 触发

  • TIM2_CH2 : 定时器 2 的通道 2 的 PWM 触发

  • TIM3_TRGO: 定时器 3 触发,TRGO属于内部触发,不需要配置对应的输出IO脚.相当于是TIM3的定时器内部计数一样,只是到了一定时间就触发ADC转换,而这个触发的实现,不依赖IO口的配置.

  • TIM4_CH4 : 定时器 4 的通道 4 的 PWM 触发

定时器配置

在进行了上述简单的介绍之后,我们来具体到代码的细节来看,本文采用的是 TIM4_CH4 进行外部触发 ADC 采样。首先来看 TIM 的配置,代码如下:

void ADC1_External_T4_CC4_Init(void){    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    GPIO_Init(GPIOB, &GPIO_InitStructure);

    TIM_TimeBaseInitTypeDef   TIM_TimeBaseStructure;    TIM_OCInitTypeDef         TIM_OCInitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

    /* Time Base configuration */    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);     TIM_TimeBaseStructure.TIM_Period = 72 - 1;              TIM_TimeBaseStructure.TIM_Prescaler = sample_psc;           TIM_TimeBaseStructure.TIM_ClockDivision = 0x00;        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;      TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

    /* TIM1 channel1 configuration in PWM mode */    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;                    TIM_OCInitStructure.TIM_Pulse = 60;     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;             TIM_OC4Init(TIM4, &TIM_OCInitStructure);

    TIM_CtrlPWMOutputs(TIM4, ENABLE);    TIM_Cmd(TIM4, DISABLE);}

在这里需要注意的是 和 sample_psc 是个变量,而这个变量可以通过调用库函数 TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC) 重新配置 TIM 所产生的 pwm 的频率,详细的原理不在这里进行赘述了,既然都能够改变 TIM 产生的 PWM 的原理,那么也就能够动态地改变 ADC 的采样频率,也就是决定 ADC 在 1 s 中能够采样多少个点,具体的原理在后续指出。还有一个需要注意的地方是 TIM_Cmd(TIM4,DISABLE),这里配置的是禁止 TIM 定时器使能,因为还有 ADC 和 DMA 还没有进行配置,因此,我们需要在 ADC 和 DMA 都配置好之后,再将 TIM4 进行使能。

DMA 配置

因为笔者所涉及到的 ADC 的具体应用是这样的,也就是通过定时器触发 ADC 采集,然后采集一定数量的点数之后,在这里笔者每个 ADC 的通道是采集了 256 个点,然后对这 256 个点进行处理,处理完毕之后,再以一定时间间隔再采集 256 个点,周而复始地进行采集和处理。并且,这里需要的是同时采集 2 个通道的数据,每个通道采集 256 个点,也就是说,我们一次性处理的是 256 * 2 = 512 个点的数据,采集完成之后,再通过 DMA 将数据其搬运至内存,因此,也就有了如下所示的 DMA 配置:

static void ADC1_DMA1_Init(void){    DMA_InitTypeDef DMA_InitStructure;    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_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);

    /* DMA1 Channel1 Configuration ----------------------------------------------*/    DMA_DeInit(DMA1_Channel1);    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;    DMA_InitStructure.DMA_BufferSize = ADC_BUFF_LEN*2;    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;    DMA_InitStructure.DMA_Priority = DMA_Priority_High;    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;    DMA_Init(DMA1_Channel1, &DMA_InitStructure);

    DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);

    /* Enable DMA1 channel1 */    DMA_Cmd(DMA1_Channel1, ENABLE);}

代码比较直观,都是一些相关的配置,这里所要指出的一点是在第五行配置了中断服务函数 DMA1_Channel1_IRQn,具体的思路就是当采集的点数满足设定的点数时,就进入中断服务函数进行处理,在这里需要注意的是我们是从 ADC 外设将数据搬运至内存,所以DMA外设的地址是 ADC1 数据寄存器的地址,可以使用宏定义的方式定义如下:

#define ADC1_DR_Address    ((uint32_t)0x4001244C)

也可以直接取地址的方式设置,设置方式如下所示:

DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 ) ( & ( ADC_x->DR ) );

设置好外设的地址之后,我们就需要设置内存的地址,在这里,因为我们要采集两个通道的数据,并且每个通道要采集 256 个点的数据,所以在这里定义了一个如下所示的二维数组:

uint16_t ADC_ConvertedValue[ADC_BUFF_LEN][2] = {0};

上述中的 ADC_BUFF_LEN 就是一个通道要采集的点数,也就是 256 个,2所代表的就是有两个通道。在这里需要稍微思考的一下是二维数组的定义方式,为什么定义成的是 256 行 2 列 的二维数组,而不是 2 行 256 列的二维数组,我们来看一下 256 行 2 列的数组的布局如下:

二维数组内存分布

根据二维数组的大小也解释了 DMA 的 Buffer_size 是 ADC_BUFF_LEN * 2 ,同时,由于在下面设置了 内存地址是递增的,而又有两个通道,那么他的转换顺序是这样的,也就是先转换通道 1 的值存入数组,然后再转换通道 2 的数据存入数组,然后,以一定时间间隔地转换 512 次,然后发生 DMA 中断,这样也就能够说明数组为什么是定义成 256 行 2 列了。

ADC 配置

在配置了定时器和 DMA 之后,我们接下来来进行 ADC 的配置,上文中,我们配置的是使用 TIM4 的 4 通道产生 PWM 来触发 ADC 进行采集,然后设置了 DMA 来进行数据的搬运,因此, ADC 模块的配置如下所示:

void ADC_init(void){    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_InitTypeDef ADC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    /* ADC1 configuration ------------------------------------------------------*/    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;    ADC_InitStructure.ADC_ScanConvMode = ENABLE;    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;    ADC_InitStructure.ADC_NbrOfChannel = 2;    ADC_Init(ADC1, &ADC_InitStructure);

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);     ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);     ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_239Cycles5);  

    ADC_Cmd(ADC1, ENABLE);

    //外部触发    ADC_ExternalTrigConvCmd(ADC1, ENABLE);

    //使用DMA    ADC_DMACmd(ADC1, ENABLE);

    //校准ADC    ADC_ResetCalibration(ADC1);    while(ADC_GetResetCalibrationStatus(ADC1));

    ADC_StartCalibration(ADC1);    while(ADC_GetCalibrationStatus(ADC1));  

}

配置过程比较简单,没有什么逻辑性可言,不在这里进行赘述,这里需要指出的一点是因为我们设置的是 2 个通道的采集,所以,在这里应该使能 ADC 的扫描模式,另一方面,我们采用的是 TIM 产生 pwm 触发 adc 进行采集,所以要禁止 ADC 的连续转换模式,这就是两个需要注意的地方。

DMA 中断服务函数

在前文我们说了,我们通过 pwm 触发 ADC 采集,当采集了规定的点数之后,就会产生 DMA 中断,然后在 DMA 中断里面去处理数据,但是由于中断服务函数的要求是执行时间尽可能短,所以,我们可以在中断服务函数里置位数据采集完成标志位的方式来使得主程序进行数据处理,程序代码如下所示:

void ADC1_DMA1_IT_Hander(void){        if (DMA_GetFlagStatus(DMA1_FLAG_TC1))    {        DMA_ClearITPendingBit(DMA1_FLAG_TC1);        //rt_sem_release(adc_complete_sem);        adc_complete_flag = 1;    }}

上述代码中,被注释掉的部分是释放信号量,这个是使用 RTOS 是用来同步线程的一个操作,其功能与裸机的标志位是相同的。

总结

上述便是本次分享的内容,其实现的一个功能便是使用 PWM 触发 ADC 多通道采集,并使用 DMA 进行搬运,通过这样子就可以精确地控制 ADC 的采样频率,也就是控制 1 s 钟可以采集多少个点。最后,而这个采样频率就是 pwm 的频率,但是为了更加精确的计算其真实的采样频率还应该加上 ADC 通道的转换一个数据的转换时间,这样才是最为精确的采样频率。在下一篇文章中,笔者将继续介绍基于这篇文章的应用,也就是根据采样得到的点,计算波形的频谱,计算波形的频率。

dma和通道的区别_STM32 定时器触发 ADC 多通道采集,DMA搬运至内存相关推荐

  1. STM32F030 使用CUBEMX配置ADC多通道采集 DMA传输

    STM32CubeMX的配置 以下为测试代码 //usart重定向,注意使用前需要包含头文件<stdio.h> int fputc(int ch, FILE *f) {HAL_UART_T ...

  2. 【STM32】定时器TIM触发ADC采样,DMA搬运到内存(超详细讲解)

    TIM+ADC+DMA原理 一般情况下,当我们需要进行采样的时候,需要用到ADC.例如:需要对某个信号进行定时采样(也就是隔一段时间,比如说2ms). 本文提供的解决方案是:使用ADC的定时器触发AD ...

  3. STM32F103C8T6制作舵机测试仪详细图文教程 | 定时器触发ADC | DMA传输 | PWM输出 | RTC实时时钟 | USART串口输出 | OLED IIC显示

    自主学习STM32已有一周,先实现一个小demo,算是给自己一个动力叭,有目标的学习收获会更多.虽然本科也修了嵌入式课程,但那种走马观花式的学习,最后真正得到的知识实在寥寥无几.个人理解,学习STM3 ...

  4. STM32F0 定时器触发ADC,多通道采样、DMA传输数据的配置

    本块代码实现了定时器定时触发ADC,多通道采样,并通过DMA进行数据传输到内存的操作. #include    "adc.h" [cpp] view plaincopy #defi ...

  5. STM32F4时钟触发ADC双通道采样DMA传输进行FFT+测频率+采样频率可变+显示波形(详细解读)...

    此文转载自:https://blog.csdn.net/qq_45620831/article/details/110819495 写在前面的婆婆妈妈的话 本人大三,参加过数次电赛,来CSDN好久, ...

  6. STM32F4时钟触发ADC双通道采样DMA传输进行FFT+测频率+采样频率可变+显示波形(详细解读)

    写在前面的婆婆妈妈的话(代码链接在最下方) 本人大三,参加过数次电赛,来CSDN好久, 每次都是在绝望中从这里找到了希望,每次都仿佛一个即将被怪兽打翻的小船突然被危险流浪者救起来.是众多前辈的智慧,让 ...

  7. dma和通道的区别_Java中IO和NIO的本质和区别

    简介 终于要写到java中最最让人激动的部分了IO和NIO.IO的全称是input output,是java程序跟外部世界交流的桥梁,IO指的是http://java.io包中的所有类,他们是从jav ...

  8. STM8单片机ADC采样功能通过定时器触发

      在使用STM8单片机的ADC功能时,读取ADC数据时一般有两种方式,一种是通常不断地读取采样标志位,来判断ADC采样是否结束,一种是通过中断的方式来通知系统采样是否结束.   有时候采样ADC数据 ...

  9. [STM32F4]STM32F407 ADC采集+DMA传输

    前言        有的项目中需要对多个通道的电压进行一定频率的AD采样. 第一种:是使用定时器去读取,通过检查转换完成标志位来读取,但这样就会加重整个系统的负担,占用CPU资源. 第二种:是采用定时 ...

最新文章

  1. idea persistence生成_真厉害!竟然可以这样用IDEA通过数据库生成lombok版的POJO...
  2. 【转摘】IT人日常的学习—中国消费产业的基本盘和机遇
  3. servlet文件上传及下载
  4. 树的同构模板题(法1.最小表示法+法2.树哈希)
  5. java正则表达式中的坑String.matches(regex)、Pattern.matches(regex, str)和Matcher.matches()
  6. CentOS 6.5下Python3.5以及virtualenv、virtualenvwrapper的安装配置
  7. 润乾单元格加html点击事件_报表输入页码翻页(润乾 V2018)
  8. 力扣914.卡牌分组
  9. Python自动化之高级语法单例模式
  10. kafka从入门到精通:Java设置全局变量传值
  11. 使用类名称创建实例并调用构造函数
  12. luminex细胞因子检测
  13. Impala metrics之statestore-subscriber
  14. C语言|求最小公倍数
  15. 2022年博士招生 | 华南理工大学-鹏城实验室 联培博士 专项计划
  16. c++,数组与指针的差别
  17. 数据分析工作,指标体系的构建流程
  18. 转三篇文章关于php中session机制
  19. 双十二结束了,程序员如何设计一个秒杀系统?
  20. python自动下载qq文件夹_GitHub - 1061700625/QQZone_AutoDownload_Album: Python+selenium 自动下载QQ空间相册...

热门文章

  1. 默写标准答案0917
  2. 查询集-104.课时104.【Django视图高级】QueryDict的用法讲解(Av61533158,P104)
  3. redis-数据类型一览
  4. python项目-每日日考系统-数据结构
  5. CentOS7的/tmp目录自动清理规则
  6. CharacterEncodingFilter作用
  7. CS-- WebService、 windowsService
  8. Android深入浅出系列之实例应用—弹出消息Toast对象的使用纯文本方式(一)
  9. Internet路由结构学习心得二:通告汇聚和具体路由影响AS入流量
  10. 面试:高并发下的流量控制