目录
前言:
一、ADC介绍
二、DMA介绍
三、代码编写 ——不使用DMA进行数据转运
1.开启对应的时钟以及ADCCLK的配置
2.初始化输入引脚
3.ADC的基本配置
4.ADC校准
5.读取ADC转换数据
6.ADC部分全部代码
7、主函数代码及运行现象
现象:
四、代码编写——使用DMA配合ADC进行数据采集
1.开启对应的时钟:
2.初始化ADC采集引脚,并配置采样通道
3.配置ADC结构体
4.ADC上电,并开启DMA至ADC的转运通道
5.设置全局变量接收DMA转运的数据
6.配置DMA,DMA上电
7.ADC校准,并通过软件触发ADC
8.ADC+DMA所有代码
9.主函数编写
最终的效果
填坑:在完成纯软件实现贪吃蛇后,CLION 基于Easyx的贪吃蛇小游戏(链表),CLION 基于EasyX的贪吃蛇小游戏,开始将代码移植到STM32上使用硬件实现贪吃蛇,项目分成两部分,一部分先介绍ADC读取摇杆值,第二部分再将贪吃蛇具体的落实到硬件上,现在先进行第一部分记录。
STM32上的ADC为12位ADC,且输入电压只能是0~3.3V,所以后期在给摇杆上电时要格外注意,摇杆上电压的5V一定要接3.3V,否则可能会损坏单片机。
通过查看手册可知,STM32F103ZET6上有三个ADC,每个ADC有21个输入通道。
这里我选用ADC3上的通道4和通道5即PF6,PF7进行摇杆X、Y两轴的模拟量读取。
DMA是数据转运的助手,通过DMA进行数据转运可以极大的节省软件资源,让系统通过硬件自动执行相应的操作。
通过查看数据手册可知,STM32F103ZET6共有两个ADC可以使用,两者均挂载在AHB总线上。
//使用ADC3_IN4和ADC3_IN5,ADC时钟使用6分频,即12MHz
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
注意:ADC的GPIO配置一定是模拟输入
//初始化ADC输入引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN ;
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;
GPIO_Init (GPIOF ,&GPIO_InitStructure);
对结构体进行配置,并对ADC进行上电(ADC_CMD)
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//单ADC模式
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//无外部触发源(使用软件触发)
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//单次转换模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//非扫描模式
ADC_InitStructure.ADC_NbrOfChannel=1;//一个通道
ADC_Init(ADC3,&ADC_InitStructure);
ADC_Cmd(ADC3,ENABLE);
先是复位校准,等待ADC复位校准完成后,开始进行ADC校准,通过函数描述以及手册即可得出ADC复位校准和ADC校准完成的标准为均为RESET。
ADC_ResetCalibration(ADC3);
while (ADC_GetResetCalibrationStatus(ADC3)==SET);
ADC_StartCalibration(ADC3);
while (ADC_GetCalibrationStatus(ADC3)==SET);
读取时先设定规则组转换的通道,接着通过软件触发ADC转换,等待ADC转换完成之后,将转换值返回。
uint16_t MyADC_GetValue(uint8_t ADC_Channel )
{
ADC_RegularChannelConfig(ADC3,ADC_Channel,1,ADC_SampleTime_28Cycles5);
ADC_SoftwareStartConvCmd(ADC3,ENABLE);
while (ADC_GetFlagStatus(ADC3,ADC_FLAG_EOC)==RESET);
return ADC_GetConversionValue(ADC3);
}
#include "MyADC.h"
void MyADC_Init(void )
{
//使用ADC3_IN4和ADC3_IN5,ADC时钟使用6分频,即12MHz
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//初始化ADC输入引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN ;
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;
GPIO_Init (GPIOF ,&GPIO_InitStructure);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//单ADC模式
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//无外部触发源(使用软件触发)
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//单次转换模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//非扫描模式
ADC_InitStructure.ADC_NbrOfChannel=1;//一个通道
ADC_Init(ADC3,&ADC_InitStructure);
ADC_Cmd(ADC3,ENABLE);
ADC_ResetCalibration(ADC3);
while (ADC_GetResetCalibrationStatus(ADC3)==SET);
ADC_StartCalibration(ADC3);
while (ADC_GetCalibrationStatus(ADC3)==SET);
}
uint16_t MyADC_GetValue(uint8_t ADC_Channel )
{
ADC_RegularChannelConfig(ADC3,ADC_Channel,1,ADC_SampleTime_28Cycles5);
ADC_SoftwareStartConvCmd(ADC3,ENABLE);
while (ADC_GetFlagStatus(ADC3,ADC_FLAG_EOC)==RESET);
return ADC_GetConversionValue(ADC3);
}
#include "main.h"
uint16_t value1=0;
uint16_t value2=0;
int main(void)
{
uart_init(115200);
delay_init();
MyADC_Init();
OLED_Init();
OLED_Clear();
while(1)
{
value1=MyADC_GetValue(ADC_Channel_4);
value2=MyADC_GetValue(ADC_Channel_5);
OLED_ShowString(0,0,"ADCValue1:",16);
OLED_ShowString(0,2,"ADCValue2:",16);
OLED_ShowNum(80,0,value1,4,16);
OLED_ShowNum(80,2,value2,4,16);
}
return 0;
}
可见,已经成功读出采样值,后面经过线性变换即可获得实际电压值。
将ADC配置成连续转换、扫描模式,DMA配置成ADC触发,循环转运模式。
//使用ADC3_IN4和ADC3_IN5,ADC时钟使用6分频,即12MHz,配合DMA进行数据转运
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//初始化ADC输入引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN ;
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;
GPIO_Init (GPIOF ,&GPIO_InitStructure);
ADC_RegularChannelConfig(ADC3,ADC_Channel_4,1,ADC_SampleTime_28Cycles5);
ADC_RegularChannelConfig(ADC3,ADC_Channel_5,2,ADC_SampleTime_28Cycles5);
ADC依然通过软件触发,并使用连续转换(EOC置位后并不停下来,继续进行转换),扫描模式。
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//单ADC模式
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//无外部触发源(使用软件触发)
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//连续转换模式
ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描模式
ADC_InitStructure.ADC_NbrOfChannel=2;//两个通道
ADC_Init(ADC3,&ADC_InitStructure);
ADC_DMACmd(ADC3,ENABLE);
ADC_Cmd(ADC3,ENABLE);
uint16_t ADC_Value[2]={0};
外设的地址(DMA_PeripheralBaseAddr)为ADC3的DR寄存器的值,需要强制转换为32位的地址,存储器的地址(DMA_MemoryBaseAddr)为第5点定义的全局变量ADC_Value,将外设寄存器作为源头,且外设地址不自增(一直为ADC3的DR寄存器地址),存储器地址自增(两个通道),由于ADC采集出的数据为16位,故外设和存储器均为半字(halfword),DMA使用循环转运,最后将DMA上电。
下图位DMA的硬件触发源,由手册可知,可以通过DMA2的通道5进行ADC3的触发,故DMA_Init(DMA2_Channel5,&DMA_InitStructure);选择通道5进行触发。
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC3->DR;
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)ADC_Value;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//外设作为数据源头
DMA_InitStructure.DMA_BufferSize=2;//ADC采集两个通道的信息
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设不自增,地址一直是ADC的DR寄存器地址
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//ADC采集数据为16位
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//不使用软件触发,即通过ADC触发
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;
DMA_Init(DMA2_Channel5,&DMA_InitStructure);
DMA_Cmd(DMA2_Channel5,ENABLE);
注意,在配置完成之后一定要使用软件触发一次ADC进行转运才能使得ADC进行连续转换,扫描模式,DMA也进行循环转运。
ADC_ResetCalibration(ADC3);
while (ADC_GetResetCalibrationStatus(ADC3)==SET);
ADC_StartCalibration(ADC3);
while (ADC_GetCalibrationStatus(ADC3)==SET);
ADC_SoftwareStartConvCmd(ADC3,ENABLE);
记得在ADC的头文件申明全局变量出去。
#include "MyADC.h"
uint16_t ADC_Value[2]={0};
void MyADC_Init(void )
{
//使用ADC3_IN4和ADC3_IN5,ADC时钟使用6分频,即12MHz,配合DMA进行数据转运
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//初始化ADC输入引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AIN ;
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz ;
GPIO_Init (GPIOF ,&GPIO_InitStructure);
ADC_RegularChannelConfig(ADC3,ADC_Channel_4,1,ADC_SampleTime_28Cycles5);
ADC_RegularChannelConfig(ADC3,ADC_Channel_5,2,ADC_SampleTime_28Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//单ADC模式
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//无外部触发源(使用软件触发)
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//连续转换模式
ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描模式
ADC_InitStructure.ADC_NbrOfChannel=2;//两个通道
ADC_Init(ADC3,&ADC_InitStructure);
ADC_DMACmd(ADC3,ENABLE);
ADC_Cmd(ADC3,ENABLE);
/*DMA进行转运的三个条件
* 1.DMA使能上电
* 2.DMA的传输计数器BUFFER不为零
* 3.有触发信号
* */
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC3->DR;
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)ADC_Value;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//外设作为数据源头
DMA_InitStructure.DMA_BufferSize=2;//ADC采集两个通道的信息
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设不自增,地址一直是ADC的DR寄存器地址
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//ADC采集数据为16位
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//不使用软件触发,即通过ADC触发
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;
DMA_Init(DMA2_Channel5,&DMA_InitStructure);
DMA_Cmd(DMA2_Channel5,ENABLE);
ADC_ResetCalibration(ADC3);
while (ADC_GetResetCalibrationStatus(ADC3)==SET);
ADC_StartCalibration(ADC3);
while (ADC_GetCalibrationStatus(ADC3)==SET);
ADC_SoftwareStartConvCmd(ADC3,ENABLE);
}
#include "main.h"
float Voltage1=0;
float Voltage2=0;
int main(void)
{
uart_init(115200);
delay_init();
MyADC_Init();
OLED_Init();
OLED_Clear();
OLED_ShowString(0,0,"LocationX:",16);
OLED_ShowString(0,2,"LocationY:",16);
OLED_ShowString(0,4,"VoltageX:0.00V",16);
OLED_ShowString(0,6,"VoltageY:0.00V",16);
while(1) {
Voltage1=(float )ADC_Value[0]/4095*3.3;
Voltage2=(float )ADC_Value[1]/4095*3.3;
OLED_ShowNum(80,0,ADC_Value[0],4,16);
OLED_ShowNum(80,2,ADC_Value[1],4,16);
OLED_ShowNum(72,4,Voltage1,1,16);
OLED_ShowNum(88,4,(uint16_t)(Voltage1*100)%100,2,16);
OLED_ShowNum(72,6,Voltage1,1,16);
OLED_ShowNum(88,6,(uint16_t)(Voltage2*100)%100,2,16);
}
return 0;
}
项目基于CLION进行开发