STM32F03学习笔记之ADC配置(含DMA配置)
前言
学习了之后才知道原来STM32F03自带的ADC功能还是挺强大的,10位ADC,一次可以扫描16个通道。还可以使用DMA来读取转换结果,最重要的是不用加ADC外设了。
ADC知识点
多的就不说了,手册上写的也是很详细的(不会真有人学习STM32不看手册把!不会吧!不会吧!不会吧!)
点这里下载手册 提取码:x49i
1.STM32F103有三个ADC外设分别是ADC.ADC2和ADC3
2.ADC一共有18个通道 其中:ADC1的模拟输入通道16和通道17在芯片内部分别连到了温度传感器和VREFINT。ADC2的模拟输入通道16和通道17在芯片内部连到了VSS。ADC3模拟输入通道9、14、15、16、17与Vss相连。其余的通道都是共用GPIO的。
3.ADC转换时有两种转换方式分组,分别是,规则转换组和注入转换组。转换时都是按照这两个分组进行的。我们正常使用的是规则转换,注入转换就相当于一个中断,在规则转换时,如果注入组被触发,就会先转换注入组里的通道,转换完成后接着转换规则组。也就是说注入组的转换优先级更高。
4.ADC各个通道的转换模式有:单次、连续、扫描或间断模式。规则组和注入组都可以设置成这四种模式。
单次模式:只执行单次转换.按规则组(或是注入组)里的转换顺序逐个通道转换一次直到最后一个通道转换完成后结束。软件触发只适用规则组,
连续模式:连续执行转换,和单次模式同理i,只是最后一个通道转换完成后不结束开始新的一轮转换。注入组是不会连续转换的,因为注入只是规则组的中途插入转换(中断),如果一直连续转换就没意义了。
扫描模式:打开扫描模式就是多通道转换,根据规则组(或是注入组)里规定的转换顺序进行转换,关闭扫描模式就是单通道转换,只转换规则组(或是注入组)里转换顺序为1的通道。
间断模式:把规则组(或是注入组)分成若干个子分组进行转换,子分组的通道数可以通过寄存器设置最多8个。注意注入组是不可以设置的,通道数只能为1,按注入组顺序逐个转换。
接下来看代码配置这里只用标准库代码为例
代码解析
打开ADC时钟:我这里只用到ADC1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //打开ADC1时钟
再打开用到的通道对应GPIO的时钟,然后配置GPIO为复用输入模式,比如配置通道5
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_Init(GPIOA,&GPIO_InitStructure);
接下来配置ADC
ADC1_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC模式(是否启用双ADC模式) 使用独立模式ADC1_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式(单通道与多通道) 使能扫描模式ADC1_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续模式(连续模式或是单次模式) 使能连续模式ADC1_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //规则组触发启动转换方式 配置软件触发ADC1_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //转换结果数据对齐方式 右对齐ADC1_InitStructure.ADC_NbrOfChannel = 2; //规则组通道数(范围1到16) 2个通道ADC_Init(ADC1, &ADC1_InitStructure); //设置参数
上面只是完成了一部分配置还不足以进行ADC转换,可以理解为通用配置,下面进行细节配置
要进行校准的话先配置
ADC_StartCalibration(ADC1); //开始ADC1的校准程序while(ADC_GetCalibrationStatus(ADC1)!=SET); //判断校准是否结完成
配置规则组或是注入组转换顺序表 以内部温度传感器为例
//规则组ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_55Cycles5); //规则组序号2配置和设置采样时间 设置为通道17ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_55Cycles5); //规则组序号1配置和设置采样时间 设置为通道16//注入组ADC_InjectedChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_55Cycles5);ADC_InjectedChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_55Cycles5);//还要配合ADC_InjectedSequencerLengthConfig(ADC1,2); //设置注入组序列长度 范围1到4
DMA请求有需要就打开 随便使能内部CPU温度传感器
ADC_DMACmd(ADC1, ENABLE); //使能ADC使用DMA请求ADC_TempSensorVrefintCmd(ENABLE); //使能内部温度传感器和内部参考电压
然后是启动规则或是注入转换,注入组是不可以单独启动的,我们一开始启动的是规则组,然后才可以通过外部触发或是自动在规则转换完成之后启动。
//这里以软件触发启动规则组
ADC_Cmd(ADC1, ENABLE); //ADC使能 ADC1使能一定要在启动之前
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能软件触发启动规则组转换
//软件启动注入组
ADC_Cmd(ADC1, ENABLE);
ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE);//外部触发启动大同小异 只是注入组在使能之前要调用外部触发启动配置函数 注意是配置函数不是使能函数
ADC_ExternalTrigConvCmd(ADC1,ENABLE); //规则胡外部触发启动使能 配置在一开始的通用配置里设置的ADC_ExternalTrigInjectedConvConfig(ADC1,ADC_ExternalTrigInjecConv_T1_CC4); //配置注入组外部触发启动 这里只是举个例子
ADC_ExternalTrigInjectedConvCmd(ADC1, ENABLE); //使能注入组外部触发启动
下面看下ADC的中断,和配置串口中断的方法是一样的。这里要注意下:ADC1和ADC2的中断映射在同一个中断向量上,而ADC3的中断有自己的中断向量。意思是ADC1和ADC2共用同一个中断。规则和注入组转换结束时能产生中断,当模拟看门狗状态位被设置时也能产生中断。它们都有独立的中断使能位。中断源:
1.ADC_IT_EOC 规则组中断
2.ADC_IT_JEOC 注入组中断
3.ADC_IT_AWD 看门狗中断
NVIC_InitTypeDef NVIC_InitStructure; //定义中断初始化结构体变量ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); //使能ADC1中断并设置中断源ADC_ClearITPendingBit(ADC2, ADC_IT_EOC); //清除ADC ADC_IT_EOC中断标志NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn; //指定串口1的中断NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能串口1中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //中断优先级NVIC_Init(&NVIC_InitStructure); //初始化中断NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //中断优先级分组
接下来配置DMA: 外设地址就是ADC转换后的数据寄存器 DR,在标准库里为 ADC1->DR然后要把这个地址转换为32位的无符号类型。内存地址也是同理,可以定义一个16位的无符号数组,数组的元素个数可以是通道数。
DMA1_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); //设置外设地址DMA1_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Internal1; //内存地址DMA1_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //设置传输方向 这里设置设置外设为传输来源DMA1_InitStructure.DMA_BufferSize = DMA_MemoryDataSize_HalfWord; //设置指定DMA通道的缓存大小 这里设置16位半字DMA1_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址是否递增 这里设置不递增DMA1_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址是否递增 这里设置递增DMA1_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度 16位DMA1_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存数据宽度 16位DMA1_InitStructure.DMA_Mode = DMA_Mode_Circular; //DMA工作模式 设置为循环模式DMA1_InitStructure.DMA_Priority = DMA_Priority_Low; //设置通道的软件优先级 这里设置低优先级DMA1_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA的内存到内存传输设置 这里选择不设置DMA_Init(DMA1_Channel1,&DMA1_InitStructure); //结构体初始化DMA_SetCurrDataCounter(DMA1_Channel1,2); //设置DMA1的通道1 传输数量DMA_Cmd(DMA1_Channel1,ENABLE); //使能DMA1的通道1 因为ADC1和2的DMA请求在DMA1的通道1
这里看下具体配置,以规则转换后自动启动注入
用不到的地方我就注释了
uint16_t Internal1[2]; //存放ADC的转换结果void Init_ADC1_IN17_IN18() {RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //打开DMA1时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //打开ADC1时钟ADC_InitTypeDef ADC1_InitStructure; //ADC1初始化结构体DMA_InitTypeDef DMA1_InitStructure; //DMA1初始化结构体NVIC_InitTypeDef NVIC_InitStructure; //定义中断初始化结构体变量DMA1_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); //设置外设地址DMA1_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Internal1; //内存地址DMA1_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //设置传输方向 这里设置设置外设为传输来源DMA1_InitStructure.DMA_BufferSize = DMA_MemoryDataSize_HalfWord; //设置指定DMA通道的缓存大小 这里设置16位半字DMA1_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址是否递增 这里设置不递增DMA1_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址是否递增 这里设置递增DMA1_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度 16位DMA1_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存数据宽度 16位DMA1_InitStructure.DMA_Mode = DMA_Mode_Circular; //DMA工作模式 设置为循环模式DMA1_InitStructure.DMA_Priority = DMA_Priority_Low; //设置通道的软件优先级 这里设置低优先级DMA1_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA的内存到内存传输设置 这里选择不设置DMA_Init(DMA1_Channel1,&DMA1_InitStructure); //结构体初始化DMA_SetCurrDataCounter(DMA1_Channel1,2); //设置DMA1的通道1 传输数量DMA_Cmd(DMA1_Channel1,ENABLE); //使能DMA1的通道1 因为ADC1和2的DMA请求在DMA1的通道1ADC1_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC模式 使用独立模式ADC1_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式 使能扫描模式ADC1_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续模式 使能连续模式ADC1_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //触发启动转换方式 配置软件触发ADC1_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //转换结果数据对齐方式 右对齐ADC1_InitStructure.ADC_NbrOfChannel = 2; //规则组通道数(范围1到16) 2个通道ADC_Init(ADC1, &ADC1_InitStructure); //设置参数ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); //使能ADC1中断并设置中断源ADC_ClearITPendingBit(ADC2, ADC_IT_EOC); //清除ADC ADC_IT_EOC中断标志NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn; //指定串口1的中断NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能串口1中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //中断优先级NVIC_Init(&NVIC_InitStructure); //初始化中断NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //中断优先级分组ADC_StartCalibration(ADC1); //开始ADC1的校准程序while(ADC_GetCalibrationStatus(ADC1)!=SET); //判断校准是否结完成ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_55Cycles5); //规则组序号2配置和设置采样时间 设置为通道17ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_55Cycles5); //规则组序号1配置和设置采样时间 设置为通道16//ADC_DMACmd(ADC1, ENABLE); //使能ADC使用DMA请求 ADC_TempSensorVrefintCmd(ENABLE); //使能内部温度传感器和内部参考电压//ADC_ExternalTrigConvCmd(ADC1,ENABLE); //规则胡使能外部触发启动//ADC_AutoInjectedConvCmd(ADC1, ENABLE); //规则转换后自动进行注入转换//ADC_ExternalTrigInjectedConvConfig(ADC1,ADC_ExternalTrigConv_None); //注入组外部触发启动配置 //ADC_InjectedSequencerLengthConfig(ADC1,1); //设置注入组序列长度//ADC_InjectedChannelConfig(ADC1, ADC_Channel_17, 1, ADC_SampleTime_55Cycles5); //注入组转换顺序配置//ADC_InjectedChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_55Cycles5); //注入组转换顺序配置ADC_Cmd(ADC1, ENABLE); //ADC使能ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能软件触发启动规则组转换//ADC_SoftwareStartInjectedConvCmd(ADC1,ENABLE); //软件触发启动注入组转换//ADC_ExternalTrigInjectedConvCmd(ADC1, ENABLE); //使能注入组外部触发启动配置
}
最后就是获取ADC的转换结果了:如果采用DMA的话,用的时候直接读取数组里的值就可以了,可以理解为DMA自动帮我们赋值好了我们直接用就可以了。ADC1和ADC2的中断换数固定写法void ADC1_2_IRQHandler(void),使用中断要注意只有当规则组或者是注入组里的通道全部转换完成后才产生中断。使用一般使用ADC都是开启DMA传输。
void ADC1_2_IRQHandler(void){if(ADC_GetITStatus(ADC1, ADC_IT_EOC)==SET){ //检查中断标志是否置位Internal1[0]=ADC_GetConversionValue(ADC1); //把转换结果存储到数组中}ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); //清除中断标志
}
之后就是数据的换算了。