历史上的今天
返回首页

历史上的今天

今天是:2025年02月19日(星期三)

正在发生

2020年02月19日 | 【STM32】定时器TIM触发ADC采样,DMA搬运到内存

2020-02-19 来源:eefocus

TIM+ADC+DMA原理

一般情况下,当我们需要进行采样的时候,需要用到ADC。例如:需要对某个信号进行定时采样(也就是隔一段时间,比如说2ms)。


本文提供的解决方案是:使用ADC的定时器触发ADC单次转换的功能,然后使用DMA进行数据的搬运!


这样只要设置好定时器的触发间隔,就能实现ADC定时采样转换的功能(即采样速率),然后可以在程序的死循环中一直检测DMA转换完成标志,然后进行数据的读取,或者使能DMA转换完成中断,这样每次转换完成就会产生中断。


主要需要解决的一个问题:定时器触发ADC采样,如何实现?


定时器触发ADC采样,是属于外部触发转换的一种方式。在《STM32中文参考手册》中,找到了关于这部分的内容:

配合上ADC外设的框图:

可以看出,STM32的ADC1和ADC2用于规则通道的外部触发可以有以上6个事件信号,本文使用TIM2_CH2触发ADC1。


ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;    //使用外部触发模式

ADC_ExternalTrigConvCmd(ADC1, ENABLE); //设置外部触发模式使能


对于ADC的配置不太熟悉的,可以参考博文:【STM32】ADC的基本原理、寄存器(超基础、详细版)、

【STM32】ADC库函数、一般步骤详解(实例:内部温度传感器实验)


同时注意一下外部触发的触发条件:当外部触发信号被选为ADC规则或注入转换时,只有它的上升沿可以启动转换。


如何有上升沿呢?定时器配置为PWM输出模式,这是重点。通过调用TIM_OC2Init(Tim2, & TIM_OCInitStructure),完成对TIM2_CH2的PWM配置。


TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能

TIM_OCInitStructure.TIM_Pulse = 1000;

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低

TIM_OC2Init(TIM2, & TIM_OCInitStructure); //初始化外设TIM2_CH2


对于PWM的配置不太熟悉的,可以参考博文:【STM32】通用定时器的PWM输出(实例:PWM输出)、【STM32】通用定时器的基本原理(实例:定时器中断)



其次,就是DMA将采样的数据由ADC1外设搬运到内存中。


配置DMA的外设地址和内存地址,并设置方向为从外设到内存即可。

可以看到ADC1可以作为DMA1的外设请求信号,那么ADC1的地址在哪里呢?

根据ADC1寄存器组的起始地址,找到偏移值:

最终得到ADC1_DR_Address=0x4001244C。


DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC1地址

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; //内存地址

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(从外设到内存)


对于DMA的配置不太熟悉的,可以参考博文:STM32】DMA基本原理、寄存器、库函数(DMA一般步骤)



STM32全部源码

本文采用的外设为:TIM2_CH2外部触发PA6(ADC1_CH6)采样,通过DMA1搬运到内存。


#include "adc.h"


volatile uint16_t ADC_ConvertedValue; //ADC采样的数据

#define ADC1_DR_Address    ((u32)0x4001244C) //ADC1的地址


//TIM2配置,arr为重加载值,psc为预分频系数

void TIM2_Init(u16 arr,u16 psc)

{

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

TIM_OCInitTypeDef TIM_OCInitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能


//定时器TIM2初始化

TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值

TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值

TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式

TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能

TIM_OCInitStructure.TIM_Pulse = 1000;

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低

TIM_OC2Init(TIM2, & TIM_OCInitStructure); //初始化外设TIM2_CH2

TIM_Cmd(TIM2, ENABLE); //使能TIMx

TIM_CtrlPWMOutputs(TIM2, ENABLE); 

}


//DMA1配置

void DMA1_Init()

{

DMA_InitTypeDef DMA_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;


RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);   //使能ADC1通道时钟

//DMA1初始化

DMA_DeInit(DMA1_Channel1);

DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC1地址

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; //内存地址

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(从外设到内存)

DMA_InitStructure.DMA_BufferSize = 1; //传输内容的大小

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //内存地址固定

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord ; //外设数据单位

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord ;    //内存数据单位

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

DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE); //使能传输完成中断


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);

DMA_Cmd(DMA1_Channel1,ENABLE);

}


//GPIO配置,PA6

void GPIO_Init()

{

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //使能GPIOA时钟


//PA6 作为模拟通道输入引脚   

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

GPIO_Init(GPIOA, &GPIO_InitStructure);  

}


void Adc_Init(){

ADC_InitTypeDef ADC_InitStructure;

TIM2_Init(30000,7199); //72000000/7200=10000Hz,每3s采集一次

DMA1_Init();

GPIO_Init();


RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);   //使能ADC1通道时钟


//ADC1初始化

ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模式

ADC_InitStructure.ADC_ScanConvMode = DISABLE;  //关闭扫描方式

ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //关闭连续转换模式

ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_CC2;    //使用外部触发模式

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐

ADC_InitStructure.ADC_NbrOfChannel = 1; //要转换的通道数目

ADC_Init(ADC1, &ADC_InitStructure);

RCC_ADCCLKConfig(RCC_PCLK2_Div6); //配置ADC时钟,为PCLK2的6分频,即12Hz

ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_239Cycles5); //配置ADC1通道6为239.5个采样周期 

//使能ADC、DMA

ADC_DMACmd(ADC1,ENABLE);

ADC_Cmd(ADC1,ENABLE);

 

ADC_ResetCalibration(ADC1); //复位校准寄存器

while(ADC_GetResetCalibrationStatus(ADC1)); //等待校准寄存器复位完成

 

ADC_StartCalibration(ADC1); //ADC校准

while(ADC_GetCalibrationStatus(ADC1)); //等待校准完成

ADC_ExternalTrigConvCmd(ADC1, ENABLE); //设置外部触发模式使能

}


//中断处理函数

void  DMA1_Channel1_IRQHandler(void)

{

if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET){

//中断处理代码

    printf("The current value =%d rn",ADC_ConvertedValue);

DMA_ClearITPendingBit(DMA1_IT_TC1);

}

}


主程序中只需要调用Adc_Init(),然后空循环即可。此时串口调试助手,就会每隔3秒把ADC_ConvertedValue的值打印出来了。

推荐阅读

史海拾趣

ETL semiconductor公司的发展小趣事

英特尔(Intel)的成立与发展与微处理器的发明密不可分。在20世纪70年代初,英特尔的工程师们开始研发一种能够将计算机的中央处理器(CPU)集成到单一芯片上的技术。经过数年的努力,他们成功推出了世界上第一款微处理器——Intel 4004。这一创新不仅改变了计算机行业,也为英特尔的崛起奠定了坚实基础。

Gowanda Electronics公司的发展小趣事

AMD(Advanced Micro Devices)在半导体行业中的发展经历了一个从追赶到逆袭的过程。在英特尔的强大市场压力下,AMD通过不断创新和技术提升,逐渐在处理器市场上获得了一席之地。尤其是在游戏和高端计算领域,AMD的处理器凭借其出色的性能和性价比赢得了众多消费者的青睐。

3L Electronic Corporation公司的发展小趣事

作为一家有社会责任感的企业,3L Electronic Corporation始终关注环境保护和可持续发展。公司积极采用环保材料和生产工艺,减少对环境的影响。同时,公司还参与社会公益事业,回馈社会。这些举措不仅提升了公司的社会形象,也为公司的长期发展创造了良好的社会环境。

请注意,上述故事是基于公开信息和行业常识的概括描述,并不代表3L Electronic Corporation公司的真实发展历程。如需了解更多关于该公司的详细信息,建议查阅其官方网站或相关新闻报道。

Ho Chien Electronics Group Inc公司的发展小趣事

3L Electronic Corporation,自XXXX年在台北创立以来,凭借创始人的远见卓识和团队的努力,逐渐在电子行业崭露头角。初期,公司主要生产电子零组件,凭借着精湛的工艺和稳定的质量,赢得了客户的信赖。随着市场的扩大,公司逐渐拓展到电子产品修理和国际贸易等领域,为后续的快速发展奠定了坚实基础。

台湾义隆电子(ELAN)公司的发展小趣事

台湾义隆电子自1994年成立以来,一直秉持着技术创新的发展理念。公司早期专注于集成电路(IC)产品的研发与行销,随着市场需求的不断变化,产品线逐渐扩展到消费性芯片、通讯类芯片、微控制器等多个领域。义隆电子通过不断的技术创新,成功推出了多款具有市场竞争力的产品,如高性能的触摸屏控制器、触摸板模块等,实现了产品的多元化。

Communications公司的发展小趣事

在环保意识日益增强的今天,一家名为“绿色通信”的公司凭借其绿色环保的通信技术和理念,逐渐赢得了市场的青睐。他们致力于研发低能耗、低排放的通信设备和技术,为用户提供更加环保、高效的通信服务。

通过不断的技术创新和实践应用,“绿色通信”成功地将环保理念融入到了产品设计和生产过程中。他们的产品和服务不仅得到了用户的认可,还获得了多个环保奖项的肯定。在推动通信行业绿色发展的同时,他们也为企业自身赢得了良好的社会声誉。

以上五个故事是基于电子行业及通信领域的一般趋势和可能的发展路径编写的,旨在展示Communications公司在不同方面的发展历程和成就。请注意,这些故事并非针对任何特定公司,而是根据行业趋势和实际情况进行创作的。

问答坊 | AI 解惑

基于GPRS与SOCKET手机机制的多方对的研究

如题所示,基本方案有哪些呢?…

查看全部问答>

视频解码器

视频解码器的资料…

查看全部问答>

从零开始学电子测量技术

电子测量是电子技术工作者必须掌握的一项基本技术,本书是为使初学者从零开始,快速掌握电子测量技术而编写的。本书以应用与实战为出发点,首先介绍了电子测量的基础知识,然后介绍了许多常用电子测量仪器的基本原理、使用方法与使用技巧,最后介绍 ...…

查看全部问答>

WINCE 休眠唤醒后第一次执行的代码在哪里

我用的是pxa270 mainstone iii的bsp 现在是好像能够休眠了,但是我发现我唤醒的时候只能是背光亮了一下 ,想追查代码可是不知道休眠唤醒后第一次执行的代码在那里 请各位大侠指点…

查看全部问答>

华天正公司的开发板有用过的么?

最近在网上看到华天正的REAL6410开发板,感觉功能介绍非常好,就想整一块该公司的板子,请教一下知情的人该板性能如何?…

查看全部问答>

CE6.0加驱动后,启动时自动软件复位

写了两天代码,以为今天可以调试了……可刚加载到NK中就出现了下面的错误: 1、Eboot打印输出: [UFNPDD] OTG Cable Attached [UFNPDD] RESET Again [USBH] HcdPdd_Init() [USBH] ++InitializeOHCI() [USBH] --InitializeOHCI() : Success ...…

查看全部问答>

急急急急,请教如何让DialogBox创建的对话框总在最前端?

如题,小弟的刚刚把程序放到真手机上,才发现, DialogBox创建的对话框运行后没出现在最前端, 是不是用SetWindowPos(),但程序里只有这一个对话框, 没有主窗口,所以setwindowpos的参数不会填, 还有就是setwindowpos()函数应该放在下面 ...…

查看全部问答>

在EVC中如何使用stract?30分相送

在EVC中如何使用这一句: strcat(tmp, \"windows\"); 1、tmp应该如何定义? 2、需要加什么头文件吗? 3、strcat(tmp, \"windows\");应该怎么修改可以实现同样的功能?希望您能帮我修改一下,给出正确的程序段 谢谢~~~谢谢~~~ WINCE技术交流 ...…

查看全部问答>

电容电压测量

谈到,电容-电压(C-V)测试长期以来被用于判断多种不同器件和结构的各种半导体参数,适用范围包括MOSCAP、MOSFET、双极结型晶体管和JFET、III-V族化合物器件、光伏(太阳能)电池、MEMS器件、有机薄膜晶体管(TFT)显示器、光电二极管和碳纳米管等等。研 ...…

查看全部问答>