STM32F407 系列文章 -内部ADC采集和DMA传输(八)
目录
前言
一、ADC特性
二、DMA特性
三、ADC采集
1.单通道ADC采集
1.头文件定义
2.函数adc_init()
3.函数HAL_ADC_MspInit()
4.函数adc_channel_set()
5.函数adc_get_result()
6.函数adc_get_result_average()
2.多通道ADC采集
四、DMA传输
1.单通道采集-DMA读取
1.头文件定义
2.函数adc_dma_init()
3.函数adc_dma_enable()
4.函数ADC_ADCX_DMASx_IRQHandler()
5.用户使用
2.多通道采集-DMA读取
1.函数adc_nch_dma_init()
2.函数adc_nch_dma_gpio_init()
3.函数adc_nch_dma_enable()
4.中断函数
5.用户使用
总结
前言
本文将讲解ADC采集设计和DMA传输,其中单纯的ADC采集是直接将ADC采集到的结果输出,供使用;而DMA传输设计,则是在ADC采集的基础之上,将ADC采集到的结果值,存储到DMA上,供用户使用。一般MCU都会自带ADC接口,不需要外部ADC芯片,可以直接通过MCU上引脚进行采集信号,不过要注意下采集信号。一般市场上所卖的板子都带这一功能的,因此要实现此功能,需准备开发板一块。
一、ADC特性
ADC(Analog-to-digital converters,模数转换器),完成将模拟信号转变为数字信号,即一个输入电压信号转换为一个输出的数字信号。本文采用F407芯片进行设计,其ADC具备如下特性:
- 该芯片拥有3个ADC,这些ADC可以独立使用,其中ADC1有19个通道,可测量16个外部和2个内部信号源和Vbat通道的信号,而ADC2和ADC3只有有16个外部通道;
- ADC中的各个通道的A/D转换可以单次、连续、扫描或间断模式执行;
- ADC采集到的结果可以以左对齐或右对齐的方式存储在16位数据寄存器中;
- 其最大转换速率为 2.4MHz, 即最快转换时间为 0.41us;
- ADC供电要求为2.4V到3.6V,采集电压为0V到3.3V,输出数字信号转换结果为0到4095;
- 具有模拟看门狗的特性,允许应用检测输入电压是否超过了用户自 定义的阈值上限或下限。
二、DMA特性
DMA(Direct Memory Access,直接存储器访问),完成外设与存储器之间以及存储器与存储器之间的数据高速传输。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。本文采用F407芯片进行设计,其DMA具备如下特性:
- 该芯片拥有2个DMA控制器(DMA1和DMA2),每个DMA控制器拥有8个数据流通道,每个数据流有多达8个通道(请求),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求,还有一个仲裁器来协调各个DMA请求的优先权;
- 每个数据流有单独的四级32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式;
- 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道;
- DMA数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1);
- 5 个事件标志(DMA 半传输、DMA传输完成、DMA传输错误、DMA FIFO错误、 直接模式错误),进行逻辑或运算,从而产生每个数据流的单个中断请求。DMA控制器框图如下所示。
三、ADC采集
1.单通道ADC采集
407芯片的ADC可以进行很多种不同的转换模式,本文使用规则单通道的单次转换模式。 ADC在单次转换模式下(CONT位为0),只执行一次转换,该模式可以通过ADC_CR2寄存器的ADON 位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通道,但是必须先设置 EXTTRIG/JEXTTRIG 位)。以规则通道为例,一旦所选择的通道转换完成,转换结果将被存在ADC_DR寄存器中,EOC(转换结束)标志将被置位,如果设置了EOCIE,则会产生中断。然后ADC将停止,直到下次启动,具体实现程序如下。
1.头文件定义
这里完成对IO引脚的定义和操作方式,并定义了ADC的一些操作函数定义,代码如下(示例):
/* ADC及引脚 定义 */
#define ADC_ADCX_CHY_GPIO_PORT GPIOA
#define ADC_ADCX_CHY_GPIO_PIN GPIO_PIN_5
#define ADC_ADCX_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define ADC_ADCX ADC1
#define ADC_ADCX_CHY ADC_CHANNEL_5 /* 通道Y, 0 <= Y <= 17 */
#define ADC_ADCX_CHY_CLK_ENABLE() do{ __HAL_RCC_ADC1_CLK_ENABLE(); }while(0) /* ADC1 时钟使能 */
/***************************************************************************************/void adc_init(void); /* ADC初始化 */
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime); /* ADC通道设置 */
uint32_t adc_get_result(uint32_t ch); /* 获得某个通道值 */
uint32_t adc_get_result_average(uint32_t ch, uint8_t times);/* 某个通道给定次数采样的平均值*/
2.函数adc_init()
ADC初始化函数,规定ADC采样精度,采样时钟、采样周期、和一些参数定义设置等等,主要是完成对ADC_HandleTypeDef结构体类型指针变量的初始化,该函数被main()函数调用,代码如下(示例):
typedef struct { ADC_TypeDef *Instance; /* ADC 寄存器基地址 */ ADC_InitTypeDef Init; /* ADC 参数初始化结构体变量 */ __IO uint32_t NbrOfCurrentConversionRank; /* 当前转换等级的 ADC 数 */ DMA_HandleTypeDef *DMA_Handle; /* DMA 配置结构体 */ HAL_LockTypeDef Lock; /* ADC 锁定对象 */ __IO uint32_t State; /* ADC 工作状态 */ __IO uint32_t ErrorCode; /* ADC 错误代码 */
}ADC_HandleTypeDef;typedef struct { uint32_t ClockPrescaler; /* 设置预分频系数,即 PRESC[3:0]位 */ uint32_t Resolution; /* 配置 ADC 的分辨率 */ uint32_t ScanConvMode; /* 扫描模式 */ uint32_t EOCSelection; /* 转换完成标志位 */ FunctionalState ContinuousConvMode; /* 开启连续转换模式否则就是单次转换模式 */ uint32_t NbrOfConversion; /* 设置转换通道数目 */ FunctionalState DiscontinuousConvMode; /* 单次转换模式选择 */ uint32_t NbrOfDiscConversion; /* 单次转换通道的数目 */ uint32_t ExternalTrigConv; /* ADC 外部触发源选择 */ uint32_t ExternalTrigConvEdge; /* ADC 外部触发极性*/ FunctionalState DMAContinuousRequests; /* DMA 转换请求模式*/
} ADC_InitTypeDef;ADC_HandleTypeDef g_adc_handle; /* ADC句柄 */
/*** @brief ADC初始化函数* @note 本函数支持ADC1/ADC2任意通道, 但是不支持ADC3* 我们使用12位精度, ADC采样时钟=21M, 转换时间为: 采样周期 + 12个ADC周期* 设置最大采样周期: 480, 则转换时间 = 492 个ADC周期 = 23.42us* @param 无* @retval 无*/
void adc_init(void)
{g_adc_handle.Instance = ADC_ADCX;/* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */g_adc_handle.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV4;g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式 */g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐 */g_adc_handle.Init.ScanConvMode = DISABLE; /* 非扫描模式 */g_adc_handle.Init.ContinuousConvMode = DISABLE; /* 关闭连续转换 */g_adc_handle.Init.NbrOfConversion = 1; /* 1个转换在规则序列中 也就是只转换规则序列1 */g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */g_adc_handle.Init.NbrOfDiscConversion = 0; /* 不连续采样通道数为0 */g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 *//* 使用软件触发 */g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; g_adc_handle.Init.DMAContinuousRequests = DISABLE; /* 关闭DMA请求 */HAL_ADC_Init(&g_adc_handle); /* 初始化 */
}
3.函数HAL_ADC_MspInit()
ADC底层驱动函数,完成ADC的IO引脚时钟使能、引脚工作模式设置,此函数会被HAL_ADC_Init()调用,代码如下(示例):
/*** @brief ADC底层驱动,引脚配置,时钟使能此函数会被HAL_ADC_Init()调用* @param hadc:ADC句柄* @retval 无*/
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{if(hadc->Instance == ADC_ADCX){GPIO_InitTypeDef gpio_init_struct;ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADCx时钟 */ADC_ADCX_CHY_GPIO_CLK_ENABLE(); /* 开启GPIO时钟 *//* AD采集引脚模式设置,模拟输入 */gpio_init_struct.Pin = ADC_ADCX_CHY_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_ANALOG;gpio_init_struct.Pull = GPIO_NOPULL;HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);}
}
4.函数adc_channel_set()
设置ADC采样通道、转换序列、以及采样周期等功能,通过HAL库里面的HAL_ADC_ConfigChannel()函数实现。代码如下(示例):
typedef struct { uint32_t Channel; /* ADC 转换通道*/ uint32_t Rank; /* ADC 转换顺序 */ uint32_t SamplingTime; /* ADC 采样周期 */ uint32_t Offset; /* ADC 偏移量 */
} ADC_ChannelConfTypeDef;/*** @brief 设置ADC通道采样时间* @param adcx : adc句柄指针,ADC_HandleTypeDef* @param ch : 通道号, ADC_CHANNEL_0~ADC_CHANNEL_17* @param stime: 采样时间 0~7, 对应关系为:* @arg ADC_SAMPLETIME_3CYCLES, 3个ADC时钟周期 ADC_SAMPLETIME_15CYCLES, 15个ADC时钟周期* @arg ADC_SAMPLETIME_28CYCLES, 28个ADC时钟周期 ADC_SAMPLETIME_56CYCLES, 56个ADC时钟周期* @arg ADC_SAMPLETIME_84CYCLES, 84个ADC时钟周期 ADC_SAMPLETIME_112CYCLES,112个ADC时钟周期* @arg ADC_SAMPLETIME_144CYCLES,144个ADC周期 ADC_SAMPLETIME_480CYCLES,480个ADC时钟周期* @param rank: 多通道采集时需要设置的采集编号,假设你定义channel1的rank=1,channel2的 rank=2,那么对应你在DMA缓存空间的变量数组AdcDMA[0] 就i是channel1的转换结果, AdcDMA[1]就是通道2的转换结果。 单通道DMA设置为 ADC_REGULAR_RANK_1* @arg 编号1~16:ADC_REGULAR_RANK_1~ADC_REGULAR_RANK_16* @retval 无*/
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime)
{/* 配置对应ADC通道 */ADC_ChannelConfTypeDef adc_channel;adc_channel.Channel = ch; /* 设置ADCX对通道ch */adc_channel.Rank = rank; /* 设置采样序列 */adc_channel.SamplingTime = stime; /* 设置采样时间 */HAL_ADC_ConfigChannel( adc_handle, &adc_channel);
}
5.函数adc_get_result()
该函数的功能是获得ADC转换后的结果。通过调用上面的adc_channel_set()函数,来选择需要采样的ADC通道、采样时间、转换序列和采样周期等;再是调用HAL库里面的HAL_ADC_Start()启动转换、HAL_ADC_PollForConversion()函数等待转换完成、HAL_ADC_GetValue()函数获取转换后的当前结果,代码如下(示例):
/*** @brief 获得ADC转换后的结果* @param ch: 通道值 0~17,取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_17* @retval 无*/
uint32_t adc_get_result(uint32_t ch)
{/* 设置通道,序列和采样时间 */adc_channel_set(&g_adc_handle, ch, 1, ADC_SAMPLETIME_480CYCLES); HAL_ADC_Start(&g_adc_handle); /* 开启ADC */HAL_ADC_PollForConversion(&g_adc_handle, 10); /* 轮询转换 */return (uint16_t)HAL_ADC_GetValue(&g_adc_handle); /* 返回最近一次ADC1规则组的转换结果 */
}
6.函数adc_get_result_average()
该函数看需求需要,如不需要精准的采样值,该函数无需使用。该函数完成对ADC通道的若干次转换结果取平均值,代码如下(示例):
/*** @brief 获取通道ch的转换值,取times次, 然后平均* @param ch : 通道号, 0~17* @param times : 获取次数* @retval 通道ch的times次转换结果平均值*/
uint32_t adc_get_result_average(uint32_t ch, uint8_t times)
{uint32_t temp_val = 0;uint8_t t;for (t = 0; t < times; t++) /* 获取times次数据 */{temp_val += adc_get_result(ch);delay_ms(5);}return temp_val / times; /* 返回平均值 */
}
2.多通道ADC采集
多通道函数设计与单通道是一样的,两者调用的函数一样。只是需要在调用adc_get_result()函数时,给出不同的参数(通道值)即可,代码如下(示例):
uint32_t AD0 = adc_get_result(ADC_Channel_0); //单次启动ADC,转换通道0
uint32_t AD1 = adc_get_result(ADC_Channel_1); //单次启动ADC,转换通道1
uint32_t AD2 = adc_get_result(ADC_Channel_2); //单次启动ADC,转换通道2
uint32_t AD3 = adc_get_result(ADC_Channel_3); //单次启动ADC,转换通道3
四、DMA传输
DMA的应用非常广泛,例如,在串行通信接口、SPI接口、I2C接口等都需要大量的数据传输,DMA技术能够有效地支持这些需求。今天本文使用ADC单/多通道的连续转换模式,运用DMA读取ADC的数据。其中ADC这块设计如上面一章是一样的,主要不同就在于配置DMA这块。
1.单通道采集-DMA读取
1.头文件定义
这里完成对参数定义,代码如下(示例):
/* ADC单通道/多通道 DMA采集 DMA数据流相关 定义 */
#define ADC_ADCX_DMASx DMA2_Stream4
#define ADC_ADCX_DMASx_Chanel DMA_CHANNEL_0 /* ADC1_DMA请求源 */
#define ADC_ADCX_DMASx_IRQn DMA2_Stream4_IRQn
#define ADC_ADCX_DMASx_IRQHandler DMA2_Stream4_IRQHandler#define ADC_ADCX_DMASx_IS_TC() ( DMA2->HISR & (1 << 5) ) /* 判断 DMA2_Stream4 传输完成标志, 这是一个假函数形式 */
#define ADC_ADCX_DMASx_CLR_TC() do{ DMA2->HIFCR |= 1 << 5; }while(0) /* 清除 DMA2_Stream4 传输完成标志 */void adc_dma_init(uint32_t mar); /* ADC DMA采集初始化 */
void adc_dma_enable( uint16_t ndtr); /* 使能一次ADC DMA采集传输 */void adc_nch_dma_init(uint32_t tmr); /* ADC多通道 DMA采集初始化 */
void adc_nch_dma_gpio_init(void); /* ADC多通道 GPIO初始化 */
void adc_nch_dma_enable(uint16_t ndtr); /* 使能一次ADC DMA多通道采集传输 */
2.函数adc_dma_init()
该函数主要分为两部分,ADC配置和DMA配置,先完成DMA配置,再完成ADC配置。
DMA配置,首先使能DMA的时钟,然后进行DMA相关参数的配置,这里我们使用的是DMA2数据流 4,这个可以通过407芯片的DMA章节查询到。配置完成后用HAL_DMA_Init()函数对DMA进行初始化,调用HAL_DMA_Start()函数配置DMA传输参数。
对DMA配置完成之后,对ADC进行配置,沿用上一章节中的adc_init()函数对ADC进行初始化,与上一章节有所不同的是,需要使能连续转换模式和DMA单次传输ADC数据,代码如下(示例):
/* 单通道ADC采集 DMA读取 */
ADC_HandleTypeDef g_adc_handle; /* ADC句柄 */
DMA_HandleTypeDef g_dma_adc_handle; /* 与ADC关联的DMA句柄 */
uint8_t g_adc_dma_sta = 0; /* DMA传输状态标志, 0,未完成; 1, 已完成 *//*** @brief ADC DMA读取 初始化函数* @note 本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置* @param par : 外设地址* @param mar : 存储器地址* @retval 无*/
void adc_dma_init(uint32_t mar)
{if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2) /* 大于DMA1_Stream7, 则为DMA2的通道了 */__HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */else __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 *//* DMA配置 */g_dma_adc_handle.Instance = ADC_ADCX_DMASx; /* 设置DMA数据流 */g_dma_adc_handle.Init.Channel = DMA_CHANNEL_0; /* 设置DMA通道 */g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* DIR=1 外设到存储器模式 */g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 *//* 外设数据长度:16位 */g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;/* 存储器数据长度:16位 */ g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; g_dma_adc_handle.Init.Mode = DMA_NORMAL; /* 外设流控模式 */g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */HAL_DMA_Init(&g_dma_adc_handle); /* 初始化DMA */HAL_DMA_Start(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0); /* 配置DMA传输参数 */g_adc_handle.DMA_Handle = &g_dma_adc_handle; /* 配置ADC对应的DMA */adc_init(); /* ADC初始化 */SET_BIT(g_adc_handle.Instance->CR2, ADC_CR2_CONT); /* CONT = 1, 连续转换模式 *//* 配置对应ADC通道和采样序列 */adc_channel_set(&g_adc_handle , ADC_ADCX_CHY, 1, ADC_SAMPLETIME_480CYCLES); HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3); /* 设置中断优先级为3,子优先级3 */HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); /* 使能DMA中断 */HAL_ADC_Start_DMA(&g_adc_handle, &mar, sizeof(uint16_t)); /* 开始DMA中断传输 */__HAL_DMA_ENABLE_IT(&g_dma_adc_handle, DMA_IT_TC); /* TCIE =1 , 使能传输完成中断 */
}
3.函数adc_dma_enable()
该功能主要完成一次ADC采集,和一次DMA传输。使用时,先关闭ADC,关闭DMA,然后在开启DMA传输,重启ADC,代码如下(示例):
/*** @brief 使能一次ADC DMA传输 * @param ndtr: DMA传输的次数* @retval 无*/
void adc_dma_enable(uint16_t ndtr)
{__HAL_ADC_DISABLE(&g_adc_handle); /* 先关闭ADC */__HAL_DMA_DISABLE(&g_dma_adc_handle); /* 关闭DMA传输 */g_dma_adc_handle.Instance->NDTR = ndtr; /* 重设DMA传输数据量 */__HAL_DMA_ENABLE(&g_dma_adc_handle); /* 开启DMA传输 */__HAL_ADC_ENABLE(&g_adc_handle); /* 重新启动ADC */ADC_ADCX->CR2 |= 1 << 30; /* 启动规则转换通道 */
}
4.函数ADC_ADCX_DMASx_IRQHandler()
该中断服务函数主要完成,标记DMA传输完成,以及清除DMA2数据流4传输完成中断标志位,代码如下(示例):
/*** @brief ADC DMA采集中断服务函数* @param 无* @retval 无*/
void ADC_ADCX_DMASx_IRQHandler(void)
{if (ADC_ADCX_DMASx_IS_TC()) /* 判断DMA数据传输完成 */{g_adc_dma_sta = 1; /* 标记DMA传输完成 */ADC_ADCX_DMASx_CLR_TC(); /* 清除DMA2 数据流4 传输完成中断 */}
}
5.用户使用
用户在主函数main()中如何调用使用DMA读取ADC采集值,代码如下(示例):
#define ADC_DMA_BUF_SIZE 50 /* ADC DMA采集 BUF大小 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */int main(void)
{uint16_t adcx;uint32_t sum;float temp;HAL_Init(); /* 初始化HAL库 */sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */delay_init(168); /* 延时初始化 */adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */while (1){if (g_adc_dma_sta == 1){/* 计算DMA 采集到的ADC数据的平均值 */sum = 0;for (uint16_t i = 0; i < ADC_DMA_BUF_SIZE; i++) /* 累加 */sum += g_adc_dma_buf[i];adcx = sum / ADC_DMA_BUF_SIZE; /* ADC采样后的原始值 取平均值 */temp = (float)adcx * (3.3 / 4096); /* 转换后电压值 */g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */}delay_ms(100);}
}
2.多通道采集-DMA读取
这里多通道采集-DMA读取在初始化配置上面,有很多的设置与单通道是一样的,主要不同的在多个通道的选择、和确定IO脚这块,头文件定义还是采用上一小节的。具体区别见下面代码。
1.函数adc_nch_dma_init()
配置功能同单通道采集-DMA读取小节一样,先完成DMA配置,再完成ADC配置。在DMA和ADC配置上各有点不同,具体代码如下(示例):
/* 多通道ADC采集(DMA读取)程序 */
ADC_HandleTypeDef g_adc_nch_dma_handle; /* 与DMA关联的ADC句柄 */
DMA_HandleTypeDef g_dma_nch_adc_handle; /* 与ADC关联的DMA句柄 */
uint8_t g_adc_dma_sta = 0; /* DMA传输状态标志, 0,未完成; 1, 已完成 *//*** @brief ADC N通道(5通道) DMA读取 初始化函数* @note 本函数还是使用adc_init对ADC进行大部分配置, 有差异的地方再单独配置。* 另外, 由于本函数用到了5个通道, 宏定义会比较多内容* 注意: 本函数还是使用 ADC_ADCX(默认=ADC1) 和 ADC_ADCX_DMASx( DMA2_Stream4 ) 及其相关定义* @param mar : 存储器地址* @retval 无*/
void adc_nch_dma_init(uint32_t mar)
{ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADCx时钟 */if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2) /* 大于DMA1_Stream7, 则为DMA2 */__HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */else__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 *//* DMA配置 */g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx; /* 设置DMA数据流 */g_dma_nch_adc_handle.Init.Channel = DMA_CHANNEL_0; /* 设置DMA通道 */g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /*DIR=1,外设到存储器模式*/g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 *//* 外设数据长度:16位 */g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;/* 存储器数据长度:16位 */g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;g_dma_nch_adc_handle.Init.Mode = DMA_NORMAL; /* 外设流控模式 */g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */HAL_DMA_Init(&g_dma_nch_adc_handle); /* 初始化DMA *//* 配置DMA传输参数 */HAL_DMA_Start(&g_dma_nch_adc_handle, (uint32_t)&ADC_ADCX->DR, mar, 0);g_adc_nch_dma_handle.DMA_Handle = &g_dma_nch_adc_handle; /* 设置ADC对应的DMA */g_adc_nch_dma_handle.Instance = ADC_ADCX;/* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式 */g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐 */g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE; /* 扫描模式 *//* 连续转换模式,转换完成之后接着继续转换 */g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 *//* 使用转换通道数,需根据实际转换通道去设置 */g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM; g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0; /* 不连续采样通道数为0 */g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 *//* 使用软件触发, 此位忽略 */g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE; /* 开启DMA连续转换 */HAL_ADC_Init(&g_adc_nch_dma_handle); /* 初始化ADC */adc_nch_dma_gpio_init(); /* GPIO 初始化 *///adc_temperature_init(); /* 使能内部温度传感器 *//* 屏蔽的是被占用了 *///adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_480CYCLES); /* 设置采样规则序列1~5 */adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_1, 1, ADC_SAMPLETIME_3CYCLES);/* 串口2占用 *///adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_2, 3, ADC_SAMPLETIME_480CYCLES); //adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_3, 4, ADC_SAMPLETIME_480CYCLES);adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_4, 2, ADC_SAMPLETIME_3CYCLES);adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_5, 3, ADC_SAMPLETIME_3CYCLES);adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_6, 4, ADC_SAMPLETIME_3CYCLES);/* 内部温度 *///adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_16, 5, ADC_SAMPLETIME_3CYCLES); HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3); /* 设置DMA中断优先级为3,子优先级为3 */HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); /* 使能DMA中断 */HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, sizeof(uint16_t)); /*开始DMA数据传输*/__HAL_DMA_ENABLE_IT(&g_dma_nch_adc_handle, DMA_IT_TC); /* TCIE=1 使能传输完成中断 */
}
2.函数adc_nch_dma_gpio_init()
ADC底层驱动函数,完成ADC的多个IO引脚参数设置,代码如下(示例):
/*** @brief 多通道ADC的gpio初始化函数* @param 无* @note 此函数会被adc_nch_dma_init()调用* @note PA0-ADC_CHANNEL_0、PA1-ADC_CHANNEL_1、PA2-ADC_CHANNEL_2PA3-ADC_CHANNEL_3、PA4-ADC_CHANNEL_4、PA5-ADC_CHANNEL_5
* @retval 无*/
void adc_nch_dma_gpio_init(void)
{GPIO_InitTypeDef gpio_init_struct;__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA引脚时钟 *//* AD采集引脚模式设置,模拟输入 GPIO_PIN_0和GPIO_PIN_2和GPIO_PIN_3被其它地方占用 */ gpio_init_struct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6;gpio_init_struct.Mode = GPIO_MODE_ANALOG;gpio_init_struct.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOA, &gpio_init_struct);
}
3.函数adc_nch_dma_enable()
该函数完成的功能与上一小节的adc_dma_enable()函数功能是一样的,,代码如下(示例):
/*** @brief 使能一次ADC DMA传输* @param ndtr: DMA传输的次数* @retval 无*/
void adc_nch_dma_enable(uint16_t ndtr)
{__HAL_ADC_DISABLE(&g_adc_nch_dma_handle); /* 先关闭ADC */__HAL_DMA_DISABLE(&g_dma_nch_adc_handle); /* 关闭DMA传输 */g_dma_nch_adc_handle.Instance->NDTR = ndtr; /* 重设DMA传输数据量 */__HAL_DMA_ENABLE(&g_dma_nch_adc_handle); /* 开启DMA传输 */__HAL_ADC_ENABLE(&g_adc_nch_dma_handle); /* 重新启动ADC */ADC_ADCX->CR2 |= 1 << 30; /* 启动规则转换通道 */
}
4.中断函数
采用上一小节(单通道采集-DMA读取)的中断函数。
5.用户使用
用户使用上,与上一小节(单通道采集-DMA读取)的差不多,不一样的见代码:
/* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
#define ADC_DMA_BUF_SIZE 50 * 5 **保持代码一样**while (1)
{if (g_adc_dma_sta == 1){/* 循环显示通道1~通道5的结果 */for(j = 0; j < 5; j++) /* 遍历5个通道 */{sum = 0; /* 清零 *//* 计算DMA 采集到的ADC数据的平均值 */for (i = 0; i < ADC_DMA_BUF_SIZE / 5; i++)sum += g_adc_dma_buf[(5 * i) + j]; /* 相同通道的转换数据累加 */adcx = sum / (ADC_DMA_BUF_SIZE / 5); /* ADC采样后的原始值 取平均值 */temp = (float)adcx * (3.3 / 4096); /* 转换后电压值 */}g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */}delay_ms(100);
}
总结
下面提供的代码,基于STM32F407ZGT芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。
相应的代码链接:代码程序