STM32之3路ADC同步转换
国庆也算是出去浪了几天,当然 回来也不能忘记学习。经过前些天的摸索与学习对STM32的ADC有了一个了解,下面是学习过程(个人学习观点,仅供参考,如有错误或改进之处还望指出,臣不甚感激)。
更为详细的讲解可以参考一下这位前辈的博客:https://www.geek-share.com/detail/2626022181.html
初识ADC有一种茫然的状态同时又会觉得它的功能强大,在stm32中有3个ADC,每个ADC都有16个转换通道,由此可知它的强大。首先通过它的引脚功能图可以看出每个引脚所对应的ADC转换通道。这样在编写程序的时候就不会把通道搞错。
有了上述的了解之后呢,还要记住ADC所能检测的电压范围是0~3.3V,如果检测的电压值大于3.3V就会把ADC烧坏,还有就是ADC转换的时钟频率,在72M的主控CPU中它的转换频率不能大于14M也就是6分频否则检测的值会不准确。采样周期为1.5个时钟周期。stm32的ADC转换有两种通道,规则通道和注入通道,注入通道可以抢占式地打断规则通道的采样,执行注入通道采样后,再执行之前的规则通道采样,和中断类似。当然本次学习是基于规则转换的,库函数操作。有了以上的了解之后来看看ADC的库函数:
对于一些基本的库函数就不列出来了,具体参考stm32固件库使用手册。这里来讲一下几个重要的库函数,第一个就是ADC_Init()在这个函数里有以下参数
其中ADC_Mode又包含了10种模式
ADC_ExternalTrigConv 函数里有多种触发方式,一般常用软件触发。
这里要注意ADC转换的数据对齐方式,因为stm32的ADC是12位的,而它的数据寄存器ADC_DR是16位的。至于是左对齐还是右对齐?在读取ADC的数据时是从高位开始的,所以一般右对齐就好。
ADC_SampleTime设定了选中通道的ADC 采样时间。
采样的周期越长准确度越高,一般设为239.5个周期。ADC_ExternalTrigInjectedConv 指定了所使用的注入转换启动触发。
一般使用软件触发。
转换的数据被存在了ADC_DR寄存器中,当多通道转换时后一次的数据会把前一次的覆盖所以要用到DMA进行数据处理。以下是DMA的库函数
重点理解DMA_Init,以下是它的参数
关于DMA_PeripheralBaseAddr该参数用以定义DMA 外设基地址。DMA_MemoryBaseAddr该参数用以定义DMA 内存基地址。这两个函数等会程序里会有说明。下面是库函数配置的步骤:
1 定义ADC、GPIO结构体
2 使能相关时钟,设置分频,IO引脚设为模拟输入
3 复位ADC,选择ADC外设,设定ADC工作模式
4 设置数据对齐方式,通道转换数目
5 使能指定ADC,ADC校准
6 设置转换通道模式,设置ADC通道,采样周期
7 DMA初始化
上程序:
#include "sys.h" #include "ADC.h" #define ADC1_2_DR_ADDRESS ((u32)0x40012400+0x4c) #define ADC3_DR_ADDRESS ((u32)0x40013c00+0x4c) #define NUM 100 __IO u32 ADC1_ConvertedValue[NUM];//防止数据传输溢出造成的错误 __IO u16 ADC3_ConvertedValue[NUM]; void adc_Init(void) { //定义结构体 ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_ADC2|RCC_APB2Periph_ADC3, ENABLE);//使能所用功能时钟 RCC_ADCCLKConfig(RCC_PCLK2_Div6);//12MHZ RCC->APB2ENR|=1<<2; GPIOA->CRH&=0XFFFFF000;//PA0~2上拉输入 RCC->APB2ENR|=1<<9; //使能ADC时钟 //ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult ;//ADC双重模式,同步规则转换 ADC1->CR1|=6<<16 双重模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE;//单通道转换 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发ADC转换 ADC1->CR2|=7<<16 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1;//顺序进行规则转换的ADC 通道的数目 ADC_Init(ADC1, &ADC_InitStructure);//初始化ADC结构体 ADC_Cmd(ADC1, ENABLE); //ADC_DeInit(ADC2); ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult ;//ADC双重模式,同步规则 4000 转换 ADC_InitStructure.ADC_ScanConvMode = DISABLE;//单通道转换 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换h ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发ADC转换 ADC2->CR2|=7<<16 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1;//顺序进行规则转换的ADC 通道的数目 ADC_Init(ADC2, &ADC_InitStructure);//初始化ADC结构体 ADC_Cmd(ADC2, ENABLE); //ADC_DeInit(ADC3); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//ADC3独立模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE;//单通道转换 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//连续转换 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发ADC转换 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1;//顺序进行规则转换的ADC 通道的数目 ADC_Init(ADC3, &ADC_InitStructure);//初始化ADC结构体 ADC_Cmd(ADC3, ENABLE); //ADC校准 ADC_ResetCalibration(ADC1);//重置指定的ADC 的校准寄存器 ADC_ResetCalibration(ADC2); ADC_ResetCalibration(ADC3); while(ADC_GetResetCalibrationStatus(ADC1)&&ADC_GetResetCalibrationStatus(ADC2)&&ADC_GetResetCalibrationStatus(ADC3));//获取ADC 重置校准寄存器的状态 ADC_StartCalibration(ADC1);//开始指定ADC 的校准状态 ADC_StartCalibration(ADC2); ADC_StartCalibration(ADC3); while(ADC_GetCalibrationStatus(ADC1)&&ADC_GetCalibrationStatus(ADC2)&&ADC_GetCalibrationStatus(ADC3));//获取指定ADC 的校准程序 //设置指定ADC 的规则组通道,设置它们的转化顺序和采样时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1,ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC2, ADC_Channel_1, 1,ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC3, ADC_Channel_2, 1,ADC_SampleTime_239Cycles5); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)&&ADC_GetFlagStatus(ADC2, ADC_FLAG_EOC)&&ADC_GetFlagStatus(ADC3, ADC_FLAG_EOC));//等待转换结果 //使能指定的ADC 的软件转换启动功 ADC_SoftwareStartConvCmd(ADC1, ENABLE); ADC_SoftwareStartConvCmd(ADC2, ENABLE); ADC_SoftwareStartConvCmd(ADC3, ENABLE); }
DMA初始化:
void DMA_InIt(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1|RCC_AHBPeriph_DMA2, ENABLE); DMA_DeInit(DMA1_Channel1);//DMA1 ADC1通道 DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_2_DR_ADDRESS; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC1_ConvertedValue;//存放地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据传输的来源 DST则为目的地 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设寄存器地址不递增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存寄存器地址递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//数据宽度为16 位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;//数据宽度为16 位 DMA_InitStructure.DMA_BufferSize = NUM;// 传输数据量的大小 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//工作在循环缓存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); DMA_DeInit(DMA2_Channel5);//DMA2 ADC3通道 DMA_InitStructure.DMA_PeripheralBaseAddr = ADC3_DR_ADDRESS; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC3_ConvertedValue;//存放地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据传输的来源 DST则为目的地 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设寄存器地址不递增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存寄存器地址递增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度为16 位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//数据宽度为16 位 DMA_InitStructure.DMA_BufferSize = NUM;// 传输数据量的大小 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//工作在循环缓存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA2_Channel5, &DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE);//使能或者失能指定的通道 DMA_Cmd(DMA2_Channel5, ENABLE); //使能ADC的DMA请求 ADC_DMACmd(ADC1, ENABLE); ADC_DMACmd(ADC2, ENABLE); ADC_DMACmd(ADC3, ENABLE); } //转换值传出: float v_avr = 0; void Get_Average(void) { u16 i = 0; u32 adc1_sum = 0; for(i=0; i<NUM;i++) { adc1_sum += ADC1_ConvertedValue[i]%65536; } v_avr = adc1_sum/NUM/4096.0*3300; }
由于在学习中只用到了一个IO用于检测电压,故只写了一个传出值。
*问题:DMA的地址怎么选?*如何快速的找到这个寄存器的地址,首先打开参考手册,移动到2.3节—寄存器映像,也就是第28页,这里列出了STM32内部地址的划分,找到你要的外设基地址,例如ADC1的基地址为0x4001 2400 。然后再你需要的外设关于寄存器的地方找到寄存器的偏移地址,如ADC1的DR寄存器的偏移地址为0x4c,基地址加偏移地址就得到这个寄存器的地址了。
C语言知识:定义extern变量后不能赋以初值,否则会报错。外部变量在编译时赋值,只赋值一次。
总结:之前写的程序逻辑混乱,事件发生的顺序被颠倒导致转换出现问题,一定要仔细检查!理清步骤!
- 对STM32 ADC单次转换模式 连续转换模式 扫描模式的理解
- stm32ADC校准和连续单次转换的理解
- STM32 ADC多通道转换
- STM32 ADC 模数转换的简单实现
- STM32 ADC多通道转换(转)
- STM32 双ADC同步规则采样
- 对STM32 ADC单次转换模式 连续转换模式 扫描模式的理解
- cortex_m3_stm32嵌入式学习笔记(十六):ADC实验(模数转换)
- 经典STM32 ADC多通道转换
- STM32之ADC转换
- STM32 ADC 转换时间
- STM32学习笔记之ADC转换
- STM32 ADC转换时间
- STM32 ADC 单次转换
- STM32 ADC 规则多通道转换
- STM32基础设计(6)---ADC转换(DMA方式)
- 对STM32 ADC单次转换模式 连续转换模式 扫描模式的理解
- STM32 ADC 规则多通道转换
- STM32多通道ADC规则转换实现了(转)!
- STM32 ADC笔记单次转换已测试通过