目录
ADC模拟数字转换器
规则组的四种转换模式
AD单通道
AD多通道
常用代码函数相关
DMA直接存储器 存取(访问)
两个应用
DMA存储器到存储器的转运
ADC+DMA
ADC模拟数字转换器
stm32数字电路,只有高低电平,无几V电压的概念
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量
12位(分辨率,0-2的12次方减1)逐次逼近型(工作模式)ADC,1us(转换频率1MHz)转换时间
输入电压范围:0~3.3V,转换结果范围:0~4095
18个输入通道,可测量16(16GPIO)个外部(最多)和2个内部(内部温度传感器,内部参考电压)信号源
规则组和注入组两个转换单元
STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道(该型号不够16)
(ADC模拟到数字)(DAC,PWM数字到模拟)
根据引脚定义表,PAO到PB1这10个引脚是ADC的10个通道,但是其他的这些引脚。不是ADC的通道,就不能接模拟电压了
规则组的四种转换模式
连续转换or单次转换
扫描模式or非扫描模式
非扫描模式,菜单列表只用第一个,序列1-16,只有第一个有效
单次转换:触发一次仅进行一次转换,若需下一次需要再次触发
连续转换:一次转换后不会停止,立刻开始下一轮,一直持续下去
AD单通道
1.初始化
RCC(GPIO , ADC1(APB2),ADC CLK(clock)的分频器)
GPIO(模拟输入)
规则组通道配置,多路开关(左边通道,接入右边的规则组)(点菜)(指定通道通道0,序列,指定通道采样时间)
配置ADC转换器
开关控制,开启ADC
ADC校准
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t ADValue; //定义AD值变量
float Voltage; //定义电压变量
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "ADValue:");
OLED_ShowString(2, 1, "Voltage:0.00V");
while (1)
{
ADValue = AD_GetValue(); //AD转换的值(0-4095)
Voltage = (float)ADValue / 4095 * 3.3; //AD值线性变换到0~3.3的范围,表电压
OLED_ShowNum(1, 9, ADValue, 4); //显示AD值
OLED_ShowNum(2, 9, Voltage, 1); //显示电压值整数部分
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2); //显示电压值的小数部分
Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间!!!!!
}
}
//AD.c
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//ADCCLK,6分频(ADCCLK=72/6=12MHz) //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
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_RegularChannelConfig(ADC1, ADC_Channel_3, 2, 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_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续转换,单次转换
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描模式,非扫描
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是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); //软件触发AD转换
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位置1,等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器
}
AD多通道
后面两种扫描模式,这个最好配合DMA(数据覆盖)
利用单次转换非扫描模式实现多通道
每次触发转换之前,手动更改列表第一个位置的通道
依次启动四次转换,并在转换前,指定转换通道,每次转换完成,把结果存在四个里面
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5); //在每次转换前,根据函数形参灵活更改规则组的通道1
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
/
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0, AD1, AD2, AD3; //定义AD值变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
AD_Init(); //AD初始化
/*显示静态字符串*/
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); //单次启动ADC,转换通道0
AD1 = AD_GetValue(ADC_Channel_1); //单次启动ADC,转换通道1
AD2 = AD_GetValue(ADC_Channel_2); //单次启动ADC,转换通道2
AD3 = AD_GetValue(ADC_Channel_3); //单次启动ADC,转换通道3
OLED_ShowNum(1, 5, AD0, 4); //显示通道0的转换结果AD0
OLED_ShowNum(2, 5, AD1, 4); //显示通道1的转换结果AD1
OLED_ShowNum(3, 5, AD2, 4); //显示通道2的转换结果AD2
OLED_ShowNum(4, 5, AD3, 4); //显示通道3的转换结果AD3
Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间
}
}
常用代码函数相关
//配置ADCCLK分频器
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
//给ADC上电
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//ADC 中断输出控制
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
//复位校准
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规则组通道配置(给序列的每个位置填写指定的通道)
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
//读取转换结果
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
DMA直接存储器 存取(访问)
数据转运
提供外设(外设寄存器:ADC、串口的数据寄存器)(硬件触发)和存储器(运行内存SRAM和程序存储器Flash)(软件触发)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
数据转运的路径:12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
每个通道都支持软件触发和特定的硬件触发
STM32F103C8T6 DMA资源:DMA1(7个通道)
stm32中存储器:
DMA
所以DMA,即是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元
CPU通过这一条线路,就可以对DMA进行配置了
用于访问各个存储器的DMA总线
内部的多个通道,可以进行独立的数据转运
仲裁器,用于调度各个通道,防止产生冲突
AHB从设备。用于配置DMA参数
DMA请求,用于硬件触发DMA的数据转运
总之就是CPU或者DMA直接访问Flash(只读存储器的一种)的话。是只可以读而不可以写的
然后SRAM是运行内存,可以任意读写。没有问题
注意一下。写传输计数器时。必须要先关闭DMA,再进行
两个应用
外设地址显然应该填DataA数组的首地址
所以数据宽度都是按8位的字节传输
均自增
打印变量的地址,确定其存在的位置
uint8_t aa=0x66
(uint8_t)&aa
(uint32_t)&ADC1->DR(stm32结构体访问寄存器)
在STM32中,使用const定义的变量,是存储在Flash里面的
。自动重装和软件触发不能同时使用
DMA存储器到存储器的转运
1.初始化
开启时钟
DMA是AHB总线的设备,所以要用AHB开启时钟的函数
DMA1初始化
使能
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04}; //DMA转运的源端数组(数组名就是地址)
uint8_t DataB[] = {0, 0, 0, 0};///DMA转运的目的数组
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); //显示数组DataA
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); //显示数组DataB
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000); //延时1s,观察转运前的现象
MyDMA_Transfer(); //使用DMA转运数组,从DataA转运到DataB
OLED_ShowHexNum(2, 1, DataA[0], 2); //显示数组DataA
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); //显示数组DataB
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000); //延时1s,观察转运后的现象
}
}
///MyDMA.c
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size; //定义全局变量,用于记住Init函数的Size,供Transfer函数使用
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size; //将Size写入到全局变量,记住参数Size
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
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_Init,配置DMA1的通道1
//DMA使能
DMA_Cmd(DMA1_Channel1, DISABLE);
}
//调用一次这个函数,就再次启动一次DMA转运
void MyDMA_Transfer(void)//需要重新给传输计数器赋值(DATAA变化了也能继续转运)
{
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工作完成
DMA_ClearFlag(DMA1_FLAG_TC1);//清除工作完成标志位
}
ADC+DMA
AD多通道+DMA数据转运功能
AD扫描模式(PA0-PA3)
菜单点4个(菜做好后放在了ADC_DR寄存器中)
DMA服务员
源地址((uint32_t)&ADC1->DR)
数据宽度,我们想要DR奇存器低16位的数据,所以数据宽度,就是HalfWord,以半字,16位来转运
就是开启ADC到DMA的输出
这里有3个硬件触发源,具体使用哪个,取决于把哪个的DMA输出给开启了
ADC_DMACmd(ADC1,ENABLE)