目录
前言
编辑
技术实现
连线图
代码实现
技术要点
实验结果
问题记录
前言
DMA(Direct Memory Access)直接存储器存取,用来提供在外设和存储器 之间或者存储器和存储器之间的高速数据传输。无需CPU干预,数据可以通过DMA快速地移动,这样可以节省CPU的资源进行其他操作。两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自与一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
DMA的主要特性
- 12个独立的可配置的通道(请求):DMA1有7个 通道,DMA2有5个通道
- 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
- 在同一个DMA模块上多个请求见的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先与请求1,以此类推)。
- 独立数据源和目标数据区的传输字节宽度(字节、半字、字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
- 支持循环的缓冲器管理
- 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这三个事件标志逻辑或成为一个单独的中断请求。
- 存储器和存储器之间的传输。
- 外设和存储器、存储器和外设之间的传输。
- Flash、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。
- 可编程的数据传输数目。
ADC(Analog-Digital Converter)模拟-数字转换器,ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁.12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和两个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐对齐方式存储在16位数据寄存器中。
逐次逼近型ADC通过二进制搜索算法逐步确定输入模拟信号的数值,具体步骤如下:
- 初始化:
- 逐次逼近寄存器(SAR)将最高有效位(MSB)设为1,其余位设为0,形成一个初始猜测值。
- 该值通过内部DAC(数模转换器)转换为模拟电压,并与输入信号进行比较。
- 比较与调整:
- 比较器将DAC输出的电压与输入模拟信号进行比较:
- 若DAC电压 < 输入电压 → 保持该位为1,继续测试下一位(次高位)。
- 若DAC电压 > 输入电压 → 将该位清零(设为0),继续测试下一位。
- 重复上述步骤,逐位确定每一位的值(从MSB到LSB),直到所有位完成判断。
- 输出结果:
- 最终,SAR寄存器中保存的二进制数值即为输入模拟信号的数字表示。
ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。
ADC主要特征:
● 12位分辨率
● 转换结束、注入转换结束和发生模拟看门狗事件时产生中断
● 单次和连续转换模式
● 从通道0到通道n的自动扫描模式
● 自校准
● 带内嵌数据一致性的数据对齐
● 采样间隔可以按通道分别编程
● 规则转换和注入转换均有外部触发选项
● 间断模式
● 双重模式(带2个或以上ADC的器件)
● ADC转换时间:
─
STM32F103xx增强型产品:时钟为56MHz时为1μs(时钟为72MHz为1.17μs)
─
STM32F101xx基本型产品:时钟为28MHz时为1μs(时钟为36MHz为1.55μs)
─
STM32F102xxUSB型产品:时钟为48MHz时为1.2μs
─
STM32F105xx和STM32F107xx产品:时钟为56MHz时为1μs(时钟为72MHz为1.17μs)
● ADC供电要求:2.4V到3.6V
● ADC输入范围:VREF- ≤ VIN ≤ VREF+
● 规则通道转换期间有DMA请求产生。
ADC功能描述:
技术实现
连线图
代码实现
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h" //延时函数
#include "OLED.h"
#include "AD.h"int main(void)
{/*OLED初始化*/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){ AD_GetValue();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);}
}
AD.h
#ifndef AD_H
#define AD_H#include "stm32f10x.h"extern uint16_t AD_Value[4];void AD_Init(void);
void AD_GetValue(void);#endif
AD.c
#include "AD.h"uint16_t AD_Value[4]; //将数据存储在SRAM /*** @brief AD Initialization * @param None* @retval None* @note Initialize AD basic structure*/
void AD_Init(void)
{//开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //开启DMA时钟//配置ADC时钟 12MHzRCC_ADCCLKConfig(RCC_PCLK2_Div6);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; //模拟输入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//配置ADC规则组输入通道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);//初始化ADCADC_InitTypeDef ADC_InitStruct;ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;ADC_InitStruct.ADC_ScanConvMode = ENABLE; //扫描模式ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; //单次转换ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //软件触发,非外部触发ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐ADC_InitStruct.ADC_NbrOfChannel = 4; //共4个序列ADC_Init(ADC1,&ADC_InitStruct);//初始化DMA DMA_InitTypeDef DMA_InitStruct;DMA_InitStruct.DMA_BufferSize = 4; //缓冲区的大小,由于CNDTR寄存器的高16位保留,这里缓冲区大小的变量的大小使用//uint16_t即可DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //外设站点是数据源DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器站点的基地址,将数据转运到SRAMDMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //接收的数据以半字形式传输DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //DAM传输计数器不使用自动重装模式DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //DMA为外设到存储器传输的方式,使用硬件触发DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器站点自增DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设站点地址不自增DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //ADC规则数据寄存器(ADC_DR)的地址为要传输的数据的基地址DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //传输的数据以半字节形式传输DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //只有一个通道,DMA优先级随便选DMA_Init(DMA1_Channel1,&DMA_InitStruct); //使用硬件触发,通道为固定的通道1,不可更改DMA_Cmd(DMA1_Channel1,ENABLE);ADC_DMACmd(ADC1,ENABLE); //开启ADC的DMA触发源//关闭ADC电源ADC_Cmd(ADC1,DISABLE);//维持ADC处于掉电状态至少两个周期Delay_us(1);//ADC校准ADC_ResetCalibration(ADC1); //复位校准,将ADC_CR2寄存器中的RSTCAL位置1,初始化校准寄存器while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准完成,校准寄存器被初始化后RSTAL位由硬件清零ADC_StartCalibration(ADC1); //AD校准,将ADC_CR2寄存器中的CAL位置1,开始校准while(ADC_GetCalibrationStatus(ADC1)); //等待AD校准完成,校准完成后CAL位由硬件清零//开启ADC电源ADC_Cmd(ADC1,ENABLE);
}/*** @brief AD触发,DMA计数器赋值* @param None* @retval None* @note 为DMA计数器重新赋值*/
void AD_GetValue(void)
{DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能DMA_SetCurrDataCounter(DMA1_Channel1,4); //传输计数器赋值,非自动重装模式,若使用自动重装模式,则只需在初始化中进行一次赋值DMA_Cmd(DMA1_Channel1,ENABLE); ADC_SoftwareStartConvCmd(ADC1,ENABLE); //ADC为单次转换模式,仍需软件触发AD转换。循环转换则只需在初始化时触发一次while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA转运完成DMA_ClearFlag(DMA1_FLAG_TC1);
}
OLED部分代码参照文章《STM32基础教程——OLED显示》http://【STM32基础教程 ——OLED显示 - CSDN App】https://blog.csdn.net/2301_80319641/article/details/145837521?sharetype=blog&shareId=145837521&sharerefer=APP&sharesource=2301_80319641&sharefrom=link
技术要点
//开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //开启DMA时钟
本实验设计到DMA和ADC1,而GPIO作为ADC1的通道,故应分别开启他们的时钟,ADC1和GPIO隶属于APB2外设,DMA隶属AHB外设。
//配置ADC时钟 12MHzRCC_ADCCLKConfig(RCC_PCLK2_Div6);
系统时钟为72MHz,这里分频因子选择6分频,经分频后ADC时钟为12MHz 。
ADC输入时钟有PCLK2分频得到,最大不得超过14MHz。
GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; //模拟输入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);
由于进行AD转换,GPIO的输入量为模拟量,故应将GPIO的输入模式配置为模拟输入模式, 由于使用AD转换通道0,通道1,通道2,通道3,这里GPIO配置PA0、PA1、PA2、PA3引脚。AD通道与GPIO引脚对应见下图:
//配置ADC规则组输入通道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多通道是单次转换,非扫描模式,利用了多个通道,但是只利用了转换序列1.这里ADC使用单次转换,扫描模式。每次转换可以扫描多个通道,利用DMA,可以防止只有一个数据寄存器而数据无法及时运出导致多个通道转换后数据被覆盖。
//初始化ADCADC_InitTypeDef ADC_InitStruct;ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;ADC_InitStruct.ADC_ScanConvMode = ENABLE; //扫描模式ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; //单次转换ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//软件触发,非外部触发ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐ADC_InitStruct.ADC_NbrOfChannel = 4; //共4个序列ADC_Init(ADC1,&ADC_InitStruct);
初始化ADC,将ADC设置为单次转换,扫描模式。同样触发方式选择软件触发,数据右对齐,要转换的ADC的通道的数量为4。
//初始化DMA DMA_InitTypeDef DMA_InitStruct;DMA_InitStruct.DMA_BufferSize = 4; //缓冲区的大小,由于CNDTR寄存器的高16位保留,这里缓冲区大小的变量的大小使用//uint16_t即可DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //外设站点是数据源DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器站点的基地址,将数据转运到SRAMDMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //接收的数据以半字形式传输DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //DAM传输计数器不使用自动重装模式DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //DMA为外设到存储器传输的方式,使用硬件触发DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器站点自增DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设站点地址自增DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //ADC规则数据寄存器(ADC_DR)的地址为要传输的数据的基地址DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //传输的数据以半字节形式传输DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //只有一个通道,DMA优先级随便选DMA_Init(DMA1_Channel1,&DMA_InitStruct); //使用硬件触发,通道为固定的通道1,不可更改
将数据缓冲区(DMA通道的DMA缓存的大小)的大小设置为4。本实验是读取AD转换后的值,DMA转运的方向应为外设到存储器。结构体成员DMA_DIR指示数据传输方向,故其值设置为DMA_DIR_PeripheralSRC,即从存储器读。实验使用数组存储AD转换的值,故存储器地址设置为AD_Value,AD_Value为指向数组首地址的指针,其值为数组的地址,应强转为uint32_t类型。同时存储器站点自增。
ADC_DR寄存器为规则组转换的数据寄存器,存储的数据宽度为半字,故外设基地址设置为&ADC->DR外设和存储器的数据宽度都设置为半字。
DMA数据传输由外设到存储器,故结构体成员DMA_M2M的值应设置为DMA_M2M_Disable,且DMA由硬件触发(因为数据传输方向是外设到存储器)。
ADC_DMACmd(ADC1,ENABLE); //开启ADC的DMA触发源
开启ADC的DMA触发源,通过ADC进行硬件触发DMA数据转运。
void AD_GetValue(void)
{DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能DMA_SetCurrDataCounter(DMA1_Channel1,4); //传输计数器赋值,非自动重装模式,若使用自动重装模式,则只需在初始化中进行一次赋值DMA_Cmd(DMA1_Channel1,ENABLE); ADC_SoftwareStartConvCmd(ADC1,ENABLE); //ADC为单次转换模式,仍需软件触发AD转换。循环转换则只需在初始化时触发一次while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA转运完成DMA_ClearFlag(DMA1_FLAG_TC1);
}
ADC为单次转换模式,故每次循环开始转换前都应使用软件触发的方式触发ADC转换。
实验结果
DMA+ADC多通道
问题记录
1.江科大的教学视频中ADC校验是在开启ADC电源后开始校验,ADC校准应先关闭ADC电源并维持两个周期以上,然后开启ADC电源。