第七节、ADC模数转换器
7.1 ADC简介
ADC(Analog-Digital Converter)模拟-数字转换器(可以看做是电压表,将引脚的电压值测出,并放在一个变量内)
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
12位逐次逼近型ADC,1us转换时间
输入电压范围:0~3.3V,转换结果范围:0~4095
18个输入通道,可测量16个外部和2个内部信号源
规则组和注入组两个转换单元
模拟看门狗自动监测输入电压范围
逐次逼近型ADC:
在上图的比较器中,输入端一个是待测电压,另一个是DAC的电压输出端,两端的电压同时输入到电压比较器中进行比较,如果DAC输出电压较大,则调小DAC数据,反之则增大DAC数据,直到DAC输出的电压与外部通道输入的电压近似相等。
单个ADC的结构框图如下:
ADC的基本结构如下:
触发控制:可以选择软件触发或硬件触发。
模拟看门狗:能够监测转换结果的范围,当超出设定的阈值时,通过中断输出控制,向NVIC申请中断。
开关控制:ADC_Cmd函数
根据上图,AD初始化可分为以下几个步骤:
1、RCC开启时钟,包括ADC和GPIO的时钟,并配置ADCCLK的分频器。
2、配置GPIO,将需要用的GPIO配置为模拟输入模式。
3、配置多路开关,将左边的通道接入到右边的规则组列表中。
4、配置ADC转换器。
5、开关控制。
ADC的输入通道如下表:
ADC的转换共有四个模式,分别是单次转换、非扫描模式;连续转换、非扫描模式;单次转换、扫描模式;连续转换、扫描模式。
AD转换的步骤:采样、保持、量化、编码。
STM32的ADC的总转换时间为:
Tconv=采样时间+12.5个ADC周期。
例如,当ADCCLK=14MHz,采样周期为1.5个ADC周期
Tconv=1.5+12.5=14个ADC周期=1us。
具体的ADC转换模式的操作如下:
1、触发转换。
2、等待转换完成,读取EOC标志位。
3、读取ADC数据寄存器的值。
7.2 ADC相关库函数
void ADC_DeInit(ADC_TypeDef* ADCx);//恢复缺省配置
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);//ADC初始化
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);//ADC结构体初始化
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);//给ADC上电
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);//用于开启DMA输出信号
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);//ADC中断输出控制
void ADC_ResetCalibration(ADC_TypeDef* ADCx);//复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);//获取复位校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);//开启校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);//获取开启校准状态
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);//ADC软件开始转换控制
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);//ADC获取软件触发转换状态
/*配置间断模式*/
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);//ADC规则组通道配置
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);//ADC外部触发转换控制
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);//ADC获取转换值
uint32_t ADC_GetDualModeConversionValue(void);//ADC获取双模式转换值
/*ADC注入组配置*/
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
/*ADC模拟看门狗配置*/
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);//是否启动模拟看门狗
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);//配置高低阈值
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);//配置看门通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);//ADC开启内部两个通道
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);//获取标志位状态
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);//清除标志位
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);//获取中断状态
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);//清除中断挂起位
7.3 代码实现AD单通道&多通道转换
7.3.1 AD单通道
AD.c:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
/*RCC开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADC时钟分频系数
/*配置GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*配置多路开关*/
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
/*结构体初始化ADC*/
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外部触发转换选择
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//连续转换模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//扫描转换模式
ADC_InitStructure.ADC_NbrOfChannel=1;//通道数目
ADC_Init(ADC1,&ADC_InitStructure);
/*ADC使能*/
ADC_Cmd(ADC1,ENABLE);
/*对ADC进行校准*/
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//读取标志位状态
return ADC_GetConversionValue(ADC1);//获取转换值
}
main函数:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
#include "AD.h"
uint16_t AD_Value;
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1,1,"ADValue:");
while(1)
{
AD_Value = AD_GetValue();
OLED_ShowNum(1,9,AD_Value,4);
}
}
7.3.2 AD多通道
AD.c:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
/*RCC开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADC时钟分频系数
/*配置GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*结构体初始化ADC*/
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外部触发转换选择
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//连续转换模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE;//扫描转换模式
ADC_InitStructure.ADC_NbrOfChannel=1;//通道数目
ADC_Init(ADC1,&ADC_InitStructure);
/*ADC使能*/
ADC_Cmd(ADC1,ENABLE);
/*对ADC进行校准*/
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
/*配置多路开关*/
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//读取标志位状态
return ADC_GetConversionValue(ADC1);//获取转换值
}
main函数:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
#include "AD.h"
uint16_t AD0,AD1,AD2,AD3;
float Voltage;
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1,1,"AD0:");
OLED_ShowString(2,1,"AD1:");
OLED_ShowString(3,1,"AD2:");
OLED_ShowString(4,1,"AD3:");
while(1)
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);
OLED_ShowNum(1,5,AD0,4);
OLED_ShowNum(2,5,AD1,4);
OLED_ShowNum(3,5,AD2,4);
OLED_ShowNum(4,5,AD3,4);
Delay_ms(100);
}
}
第八节、DMA直接存储器存取
8.1 DMA简介
DMA(Direct Memory Access)直接存储器存取。
DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源。
12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)。
每个通道都支持软件触发和特定的硬件触发。一般来说,存储器到存储器之间的数据转运,使用软件触发;而外设到存储器的数据转运,使用硬件触发。
存储器映像:
DMA框图:
寄存器是一种特殊的存储器,一方面,CPU可以对寄存器进行读写,另一方面,寄存器的每一位背后都连接了一根导线,可以用于控制外设电路的状态。
上图中,DCode总线专门用于访问flash,而系统总线用于访问其他。
8.2 DMA的基本结构
DMA的基本结构图如下:
Flash:只读,不能对Flash进行写入操作。
传输计数器:指定总共需要转运多少次。
自动重装器:指定当传输计数器减为0时,是否要自动恢复到最初的值。
DMA进行转运,需要满足以下几个条件:
1、开关控制,DMA_Cmd必须使能。
2、传输寄存器必须大于0。
3、触发源必须有触发信号。
从上图可以看出,DMA初始化分为以下几个步骤:
1、RCC开启时钟。
2、调用DMA_Init函数,初始化DMA。
3、调用DMA_Cmd进行开关控制。
8.3 DMA请求
数据转运+DMA:
ADC扫描模式+DMA:
8.4 DMA相关库函数
DMA的相关库函数如下:
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);//恢复缺省配置
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);//DMA初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);//结构体初始化
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);//DMA使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);//DMA中断输出使能
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); //给传输寄存器写入数据
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);//返回传输寄存器的值
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);//获取标志位状态
void DMA_ClearFlag(uint32_t DMAy_FLAG);//清除标志位
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);//获取中断状态
void DMA_ClearITPendingBit(uint32_t DMAy_IT);//清除中断挂起位
8.5 代码实现DMA功能
DMA数据转运:
DMA.c:
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size;
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
MyDMA_Size=Size;
/*RCC开启时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
/*初始化DMA*/
DMA_InitTypeDef DMA_InitStructure;
/*外设站点的起始地址、数据宽度、是否自增*/
DMA_InitStructure.DMA_PeripheralBaseAddr=AddrA;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;
/*存储器站点的起始地址、数据宽度、是否自增*/
DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
DMA_InitStructure.DMA_BufferSize=Size;//缓冲区大小
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//传输模式
DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//选择是否是存储器到存储器
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
/*使能DMA*/
DMA_Cmd(DMA1_Channel1,DISABLE);
}
/*DMA传输函数*/
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1,DISABLE);//给DMA失能
DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);//传输数据
DMA_Cmd(DMA1_Channel1,ENABLE);//给DMA使能
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
main函数:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
#include "DMA.h"
uint8_t DataA[] = {0x01,0x02,0x03,0x04};
uint8_t DataB[] = {0,0,0,0};
int main(void)
{
OLED_Init();
MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);
OLED_ShowString(1,1,"DataA:");
OLED_ShowString(3,1,"DataB:");
OLED_ShowHexNum(1,8,(uint32_t)DataA,8);
OLED_ShowHexNum(3,8,(uint32_t)DataB,8);
while(1)
{
DataA[0]++;
DataA[1]++;
DataA[2]++;
DataA[3]++;
OLED_ShowHexNum(2,1,DataA[0],2);
OLED_ShowHexNum(2,4,DataA[1],2);
OLED_ShowHexNum(2,7,DataA[2],2);
OLED_ShowHexNum(2,10,DataA[3],2);
OLED_ShowHexNum(4,1,DataB[0],2);
OLED_ShowHexNum(4,4,DataB[1],2);
OLED_ShowHexNum(4,7,DataB[2],2);
OLED_ShowHexNum(4,10,DataB[3],2);
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowHexNum(2,1,DataA[0],2);
OLED_ShowHexNum(2,4,DataA[1],2);
OLED_ShowHexNum(2,7,DataA[2],2);
OLED_ShowHexNum(2,10,DataA[3],2);
OLED_ShowHexNum(4,1,DataB[0],2);
OLED_ShowHexNum(4,4,DataB[1],2);
OLED_ShowHexNum(4,7,DataB[2],2);
OLED_ShowHexNum(4,10,DataB[3],2);
Delay_ms(1000);
}
}
DMA+AD多通道:
AD.c:
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
void AD_Init(void)
{
/*RCC开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADC时钟分频系数
/*配置GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*配置扫描通道*/
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
/*结构体初始化ADC*/
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外部触发转换选择
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//连续转换模式
ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描转换模式
ADC_InitStructure.ADC_NbrOfChannel=4;//通道数目
ADC_Init(ADC1,&ADC_InitStructure);
/*初始化DMA*/
DMA_InitTypeDef DMA_InitStructure;
/*外设站点的起始地址、数据宽度、是否自增*/
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
/*存储器站点的起始地址、数据宽度、是否自增*/
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
DMA_InitStructure.DMA_BufferSize=4;//缓冲区大小
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;//传输模式
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//选择是否是存储器到存储器
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
/*使能DMA*/
DMA_Cmd(DMA1_Channel1,ENABLE);
/*开启ADC到DMA的输出*/
ADC_DMACmd(ADC1,ENABLE);
/*ADC使能*/
ADC_Cmd(ADC1,ENABLE);
/*对ADC进行校准*/
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发ADC转换
}
main函数:
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
#include "AD.h"
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1,1,"AD0:");
OLED_ShowString(2,1,"AD1:");
OLED_ShowString(3,1,"AD2:");
OLED_ShowString(4,1,"AD3:");
while(1)
{
OLED_ShowNum(1,5,AD_Value[0],4);
OLED_ShowNum(2,5,AD_Value[1],4);
OLED_ShowNum(3,5,AD_Value[2],4);
OLED_ShowNum(4,5,AD_Value[3],4);
Delay_ms(100);
}
}