本文主要内容:
一:介绍ADC与DMA的基本情况与初始化
二:利用ADC+DMA+看门狗中断+传感器控制LED灯代码
三:总结实验过程中碰到的错误与问题
实验说明:
通过DMA转运ADC的数据,设置ADC的中断看门狗阈值,当光敏传感器的ADC采样值在看门狗高低阈值中间,不触发中断,如果超过看门狗的高低阈值就会触发中断,LED灯进行闪烁,并且利用串口打印出四个通道的ADC采样值(本实验传感器的D0口接PA0)。
1:介绍ADC与DMA的基本情况与初始化:
(1):ADC的模块框图,更多内容可以参考STM32官方文档。
(2)根据上图对照总结出大致ADC的执行流程图如下。
根据STM32F103系列可知,
ADC内有18个通道:
其中16个位外部通道和2个内部通道信号源。
ADC的转换器通道有两种:
规则组:ADC可以对一组指定的通道,按照指定的顺序,逐个转换这组通道,转换结束后,再从头循环;这指定的通道组就称为规则组。
注入组:但是实际应用中,有可能需要临时中断规则组的转换,对某些通道进行转换,这些需要中断规则组而进行转换的通道组,就称为注入组。
ADC模式选择:
独立模式:ADC转换完1个通道的数据后才能转换下一个通道的数据。
双重模式:具有两个ADC时可实现。
三重模式:具有三个 ADC时可实现
本次实验选择一个ADC独立模式,我们可以根据需求进行单通道,不循环模式。单通道,循环模式。多通道,不循环模式。多通道,循环模式。
ADC配置的大致流程:
选择配置好相关ADC的GPIO口后,根据ADC的初始化可选择所需要的转换通道(规则组或注入组),开启ADC_Cmd(),ADC校准,软件触发。如果需要看门狗中断就进行初始化。
(3) ADC的模块框图,更多内容可以参考STM32官方文档。
(4)根据上图对照总结出大致DMA的执行流程图如下。
DMA的传输模式:
外设与存储器之间:当配置完外设之后,就进行外设应答传输,将外设获取到的数据通过总线传输到存储器中。
存储器与存储器之间:当启动DMA传输模式后,DMA外设通过DMA总线,总线矩阵将Flash中的数据直接写到SRAM中。
一般ADC都会与DMA搭配进行使用,因为ADC获取数据之后如果在很短的时间内不转运出去就会被下一波数据覆盖掉,所以需要DMA进行数据的搬运,DMA的搬运不需要消耗CPU资源。
DMA的实现过程:
在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理 请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当 DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。
DMA配置的大致流程:
根据DMA的初始化配置,选择外设地址,内存储器地址,传输方向,缓存方向等,最后如果和ADC进行搭配一定要开启ADC_DMACmd()函数,进行ADC与DMA的硬件连接,最后开启DMA_Cmd().
二:利用ADC+DMA+看门狗中断+传感器控制LED灯代码
本次实验利用的是ADC+DMA多通道循环模式
(1)各种宏定义,方便后续移植。
#ifndef __ADG_H
#define __ADG_H
#include "stm32f10x.h"
#define ADC_x ADC1
#define ADC_Channel_x ADC_Channel_0
#define ADC_RCC_APB2Periph_ADC1 RCC_APB2Periph_ADC1
#define ADC_RCC_APB2PeriphClockCmd RCC_APB2PeriphClockCmd
#define DMAy_Channelx DMA1_Channel1
#define RCC_AHBPeriph_DMAx RCC_AHBPeriph_DMA1
#define GPIO_x GPIOA
#define GPIO_PIN_x GPIO_Pin_0
#define GPIO_Pin_x1 GPIO_Pin_1
#define GPIO_Pin_x2 GPIO_Pin_2
#define GPIO_Pin_x3 GPIO_Pin_3
#define GPIO_RCC_APB2Periph_ADC1 RCC_APB2Periph_GPIOA
#define GPIO_RCC_APB2PeriphClockCmd APB2PeriphClockCmd
void AD_DMA_GetValue(void);
extern uint16_t Value[4];
void ADCInit(void);
void AD_DMA_GetValue(void);
void ADC_Config(void);
#endif
(2)ADC的GPIO的初始化,这里特别注意配置GPIO_MODE模式一定要选择模拟输入GPIO_Mode_AIN。
//ADC的GPIO口初始化
void ADC_GPIOInit(void)
{
//开启GPIO的时钟
ADC_RCC_APB2PeriphClockCmd(GPIO_RCC_APB2Periph_ADC1,ENABLE);
GPIO_InitTypeDef GPIO_InitStruture;
GPIO_InitStruture.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStruture.GPIO_Pin = GPIO_PIN_x | GPIO_Pin_x1 | GPIO_Pin_x2 | GPIO_Pin_x3;
GPIO_InitStruture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_x, &GPIO_InitStruture);
}
(3) 模拟看门狗的初始化,特别注意看门狗的高低阈值内不发生中断。
void WatchDog_ADInit()
{
//模拟看门狗的单通道
ADC_AnalogWatchdogSingleChannelConfig(ADC_x, ADC_Channel_x);
//设置模拟看门狗的高低,看门狗的高低值内不触发中断
ADC_AnalogWatchdogThresholdsConfig(ADC_x,HighThreshold,LowThreshold);
//打开看门狗的单通道检测
ADC_AnalogWatchdogCmd(ADC_x,ADC_AnalogWatchdog_SingleRegEnable);
//使能中断
ADC_ITConfig(ADC_x,ADC_IT_AWD,ENABLE);
}
(4)中断优先级的设置。
//中断优先级的设置
void ADC_NVICInit(void)
{
//设置中断组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitTypeDef NVIC_InitStruture;
//设置中断源
NVIC_InitStruture.NVIC_IRQChannel = ADC1_2_IRQn;
//设置主优先级
NVIC_InitStruture.NVIC_IRQChannelPreemptionPriority = 1;
//设置子优先级
NVIC_InitStruture.NVIC_IRQChannelSubPriority = 2;
//使能中断
NVIC_InitStruture.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruture);
}
(5)DMA初始化。
void ADC_DMA_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAx,ENABLE);
DMA_InitTypeDef DMA_InitStruture;
//将DMA的通道1寄存器重设为缺省值
DMA_DeInit(DMA1_Channel1);
//外设站点的地址
DMA_InitStruture.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
//外设寄存器低16位地址
DMA_InitStruture.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
//外设地址寄存器不变
DMA_InitStruture.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//传输的目的地址
DMA_InitStruture.DMA_MemoryBaseAddr = (uint32_t)Value;
//选择内部寄存器的低16位地址
DMA_InitStruture.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//选择内存地址寄存器递增
DMA_InitStruture.DMA_MemoryInc = DMA_MemoryInc_Enable;
//传输方向,从外设到内设
DMA_InitStruture.DMA_DIR = DMA_DIR_PeripheralSRC;
//DMA通道的DMA缓存的大小
DMA_InitStruture.DMA_BufferSize = 4;
//选择正常模式还是循环模式
DMA_InitStruture.DMA_Mode = DMA_Mode_Circular;
//非内存到内存传输
DMA_InitStruture.DMA_M2M = DMA_M2M_Disable;
//DMA通道X拥有中优先级
DMA_InitStruture.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMAy_Channelx,&DMA_InitStruture);
//使能DMA
DMA_Cmd(DMAy_Channelx, ENABLE);
}
(6)ADC初始化。
void ADCInit(void)
{
//开启ADC的时钟
ADC_RCC_APB2PeriphClockCmd(ADC_RCC_APB2Periph_ADC1,ENABLE);
//ADC的时钟分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//输入规则组的通道的选择(扫描通道)
ADC_RegularChannelConfig(ADC_x,ADC_Channel_0,1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x,ADC_Channel_1,2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x,ADC_Channel_2,3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x,ADC_Channel_3,4, ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStruture;
//模式选择
ADC_InitStruture.ADC_Mode = ADC_Mode_Independent;
//选择多通道模式或单通道模式
ADC_InitStruture.ADC_ScanConvMode = ENABLE;
//指定选择模式是单词还是多次
ADC_InitStruture.ADC_ContinuousConvMode = DISABLE;
//软件触发
ADC_InitStruture.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//选择右对其方式
ADC_InitStruture.ADC_DataAlign = ADC_DataAlign_Right;
//选择使用的通道
ADC_InitStruture.ADC_NbrOfChannel = 4;
ADC_Init(ADC_x,&ADC_InitStruture);
//使能ADC
ADC_Cmd(ADC_x, ENABLE);
//使能ADC与DMA的硬件连接
ADC_DMACmd(ADC_x,ENABLE);
//复位校准
ADC_ResetCalibration(ADC_x);
//返回复位校准的状态
while(ADC_GetResetCalibrationStatus(ADC_x) != RESET);
//启动校准
ADC_StartCalibration(ADC_x);
//获取校准的状态
while(ADC_GetCalibrationStatus(ADC_x) != RESET);
//:软件触发
ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
}
(7)整体初始化放到一个函数中,特别注意WatchDog_ADInit();的位置,一定要放到ADC初始化的后面。
void ADC_Config(void)
{
ADC_GPIOInit();
ADC_NVICInit();
ADC_DMA_Init();
ADCInit();
WatchDog_ADInit();
}
(8)判断DMA获取的标志位,不断进行软件触发。
void AD_DMA_GetValue(void)
{
DMA_SetCurrDataCounter(DMAy_Channelx,4); //设置传输数据传输通道数
DMA_Cmd(DMAy_Channelx,ENABLE);
ADC_SoftwareStartConvCmd(ADC_x,ENABLE); //软件触发开启
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//获取标志位状态
DMA_ClearFlag(DMA1_FLAG_TC1);
}
(9)中断函数。
void ADC1_2_IRQHandler(void)
{
//获取中断标志中断位
if(ADC_GetITStatus(ADC_x,ADC_IT_AWD) == SET)
{
LED_R(ON);
Delay(0xFFFFF);
LED_R(OFF);
Delay(0xFFFFF);
//清楚中断标志位
ADC_ClearITPendingBit(ADC_x,ADC_IT_AWD);
//清楚ADC的标志位
ADC_ClearFlag(ADC_x,ADC_FLAG_AWD);
}
}
(10)实验完整代码。
//main.c
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp.usart.h"
#include "adg.h"
#include <stdio.h>
unsigned int AD_Value;
void Delay(uint32_t count)
{
for(int i =0;i<count;count--);
}
int main(void)
{
LED_GPIO_Config();
USART_Config();
ADC_Config();
while(1)
{
AD_DMA_GetValue();
printf("AD0 = %d\n",Value[0]);
printf("AD1 = %d\n",Value[1]);
printf("AD2 = %d\n",Value[2]);
printf("AD3 = %d\n",Value[3]);
Delay(0xFFFFF);
}
}
//stm32f10x_it.c
void ADC1_2_IRQHandler(void)
{
if(ADC_GetITStatus(ADC_x,ADC_IT_AWD) == SET)
{
//BrightEnough = 0;
LED_R(ON);
Delay(0xFFFFF);
LED_R(OFF);
Delay(0xFFFFF);
ADC_ClearITPendingBit(ADC_x,ADC_IT_AWD);
ADC_ClearFlag(ADC_x,ADC_FLAG_AWD);
}
}
//adg.c
#include "adg.h"
unsigned int HighThreshold = 200;
unsigned int LowThreshold =150;
extern unsigned int AD_Value;
uint16_t Value[4];
void ADC_GPIOInit(void)
{
ADC_RCC_APB2PeriphClockCmd(GPIO_RCC_APB2Periph_ADC1,ENABLE);
GPIO_InitTypeDef GPIO_InitStruture;
GPIO_InitStruture.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStruture.GPIO_Pin = GPIO_PIN_x | GPIO_Pin_x1 | GPIO_Pin_x2 | GPIO_Pin_x3;
GPIO_InitStruture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_x, &GPIO_InitStruture);
}
void WatchDog_ADInit()
{
//模拟看门狗的单通道
ADC_AnalogWatchdogSingleChannelConfig(ADC_x, ADC_Channel_x);
//设置模拟看门狗的高低,看门狗的高低值内不触发中断
ADC_AnalogWatchdogThresholdsConfig(ADC_x,HighThreshold,LowThreshold);
//打开看门狗的单通道检测
ADC_AnalogWatchdogCmd(ADC_x,ADC_AnalogWatchdog_SingleRegEnable);
//使能中断
ADC_ITConfig(ADC_x,ADC_IT_AWD,ENABLE);
}
//中断优先级的设置
void ADC_NVICInit(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitTypeDef NVIC_InitStruture;
NVIC_InitStruture.NVIC_IRQChannel = ADC1_2_IRQn;
NVIC_InitStruture.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruture.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStruture.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruture);
}
void ADC_DMA_Init(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMAx,ENABLE);
DMA_InitTypeDef DMA_InitStruture;
//将DMA的通道1寄存器重设为缺省值
DMA_DeInit(DMA1_Channel1);
//外设站点的地址
DMA_InitStruture.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
//外设寄存器低16位地址
DMA_InitStruture.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
//外设地址寄存器不变
DMA_InitStruture.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//传输的目的地址
DMA_InitStruture.DMA_MemoryBaseAddr = (uint32_t)Value;
//选择内部寄存器的低16位地址
DMA_InitStruture.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
//选择内存地址寄存器递增
DMA_InitStruture.DMA_MemoryInc = DMA_MemoryInc_Enable;
//传输方向,从外设到内设
DMA_InitStruture.DMA_DIR = DMA_DIR_PeripheralSRC;
//DMA通道的DMA缓存的大小
DMA_InitStruture.DMA_BufferSize = 4;
//选择正常模式还是循环模式
DMA_InitStruture.DMA_Mode = DMA_Mode_Circular;
//非内存到内存传输
DMA_InitStruture.DMA_M2M = DMA_M2M_Disable;
//DMA通道X拥有中优先级
DMA_InitStruture.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMAy_Channelx,&DMA_InitStruture);
//使能DMA
DMA_Cmd(DMAy_Channelx, ENABLE);
}
void ADCInit(void)
{
//开启ADC的时钟
ADC_RCC_APB2PeriphClockCmd(ADC_RCC_APB2Periph_ADC1,ENABLE);
//ADC的时钟分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//输入规则组的通道的选择(扫描通道)
ADC_RegularChannelConfig(ADC_x,ADC_Channel_0,1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x,ADC_Channel_1,2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x,ADC_Channel_2,3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x,ADC_Channel_3,4, ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStruture;
//模式选择
ADC_InitStruture.ADC_Mode = ADC_Mode_Independent;
//选择多通道模式或单通道模式
ADC_InitStruture.ADC_ScanConvMode = ENABLE;
//指定选择模式是单词还是多次
ADC_InitStruture.ADC_ContinuousConvMode = DISABLE;
//软件触发
ADC_InitStruture.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//选择右对其方式
ADC_InitStruture.ADC_DataAlign = ADC_DataAlign_Right;
//选择使用的通道
ADC_InitStruture.ADC_NbrOfChannel = 4;
ADC_Init(ADC_x,&ADC_InitStruture);
//使能ADC
ADC_Cmd(ADC_x, ENABLE);
//使能ADC与DMA的硬件连接
ADC_DMACmd(ADC_x,ENABLE);
//复位校准
ADC_ResetCalibration(ADC_x);
//返回复位校准的状态
while(ADC_GetResetCalibrationStatus(ADC_x) != RESET);
//启动校准
ADC_StartCalibration(ADC_x);
//获取校准的状态
while(ADC_GetCalibrationStatus(ADC_x) != RESET);
//:软件触发
ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
}
void ADC_Config(void)
{
ADC_GPIOInit();
ADC_NVICInit();
ADC_DMA_Init();
ADCInit();
WatchDog_ADInit();
}
//可以获取ADC单个值
unsigned int ADC_GetADVal()
{
ADC_SoftwareStartConvCmd(ADC_x,ENABLE);
while(ADC_GetFlagStatus(ADC_x,ADC_FLAG_EOC) == 0);
//等待规则转换完成
ADC_SoftwareStartConvCmd(ADC_x,DISABLE);
return(ADC_GetConversionValue(ADC_x));
}
void AD_DMA_GetValue(void)
{
DMA_SetCurrDataCounter(DMAy_Channelx,4); //设置传输数据传输通道数
DMA_Cmd(DMAy_Channelx,ENABLE);
ADC_SoftwareStartConvCmd(ADC_x,ENABLE); //软件触发开启
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//获取标志位状态
DMA_ClearFlag(DMA1_FLAG_TC1);
}
//adg.h
#ifndef __ADG_H
#define __ADG_H
#include "stm32f10x.h"
#define ADC_x ADC1
#define ADC_Channel_x ADC_Channel_0
#define ADC_RCC_APB2Periph_ADC1 RCC_APB2Periph_ADC1
#define ADC_RCC_APB2PeriphClockCmd RCC_APB2PeriphClockCmd
#define DMAy_Channelx DMA1_Channel1
#define RCC_AHBPeriph_DMAx RCC_AHBPeriph_DMA1
#define GPIO_x GPIOA
#define GPIO_PIN_x GPIO_Pin_0
#define GPIO_Pin_x1 GPIO_Pin_1
#define GPIO_Pin_x2 GPIO_Pin_2
#define GPIO_Pin_x3 GPIO_Pin_3
#define GPIO_RCC_APB2Periph_ADC1 RCC_APB2Periph_GPIOA
#define GPIO_RCC_APB2PeriphClockCmd APB2PeriphClockCmd
void AD_DMA_GetValue(void);
extern uint16_t Value[4];
//extern char BrightEnough;
void ADCInit(void);
unsigned int ADC_GetADVal();
void AD_DMA_GetValue(void);
void ADC_Config(void);
#endif
三:总结实验过程中碰到的错误与问题
1:DMA_PeripheralDataSize_HalfWord与DMA_MemoryDataSize_HalfWord在配置过程中十分相似,如果在配置DMA_PeripheralDataSize使用DMA_MemoryDataSize_HalfWord;就会丢失高位数据。
2:注意开启ADC_DMACmd();函数。不然ADC与DMA无法进行连接。
3:注意在DMA内存地址一定要uint32_t 强制转化,而且注意内存地址一定对照清楚。
4:注意在最后设置DMA完成的标志位判定,并进行ADC软件触发,不然就只会转运第一个ADC接收到转运的数据,不再工作。文章来源:https://www.toymoban.com/news/detail-766134.html
5:注意DMA配置DMA_DIR时DMA_DIR_PeripheralSRC(外设到内存的配置)。文章来源地址https://www.toymoban.com/news/detail-766134.html
到了这里,关于STM32_ADC————ADC+DMA多路数据传输,看门狗中断,传感器控制LED的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!