热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

TIMADCDMA原理一般情况下,当我们需要进行采样的时候,需要用到ADC。例如:需要对某个信号进行定时采样(也就是隔一段
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_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1TIM_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_CH2TIM_Cmd(TIM2, ENABLE); //使能TIMxTIM_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); //配置DMA1DMA_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分频,即12HzADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_239Cycles5); //配置ADC1通道6为239.5个采样周期 //使能ADC、DMAADC_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 \r\n",ADC_ConvertedValue);DMA_ClearITPendingBit(DMA1_IT_TC1);}
}

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



推荐阅读
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 配置多VLAN环境下的透明SQUID代理
    本文介绍如何在包含多个VLAN的网络环境中配置SQUID作为透明网关。网络拓扑包括Cisco 3750交换机、PANABIT防火墙和SQUID服务器,所有设备均部署在ESXi虚拟化平台上。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • Codeforces Round #566 (Div. 2) A~F个人题解
    Dashboard-CodeforcesRound#566(Div.2)-CodeforcesA.FillingShapes题意:给你一个的表格,你 ... [详细]
  • 在多线程编程环境中,线程之间共享全局变量可能导致数据竞争和不一致性。为了解决这一问题,Linux提供了线程局部存储(TLS),使每个线程可以拥有独立的变量副本,确保线程间的数据隔离与安全。 ... [详细]
  • 本次考试于2016年10月25日上午7:50至11:15举行,主要涉及数学专题,特别是斐波那契数列的性质及其在编程中的应用。本文将详细解析考试中的题目,并提供解题思路和代码实现。 ... [详细]
  • 本文介绍如何在Spring Boot项目中集成Redis,并通过具体案例展示其配置和使用方法。包括添加依赖、配置连接信息、自定义序列化方式以及实现仓储接口。 ... [详细]
author-avatar
傻缺缺的谭贱儿_452
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有