STM32之ADC采集和DMA传输(八)

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具备如下特性:

  1. 该芯片拥有3个ADC,这些ADC可以独立使用,其中ADC1有19个通道,可测量16个外部和2个内部信号源和Vbat通道的信号,而ADC2和ADC3只有有16个外部通道;
  2. ADC中的各个通道的A/D转换可以单次、连续、扫描或间断模式执行;
  3. ADC采集到的结果可以以左对齐或右对齐的方式存储在16位数据寄存器中;
  4. 其最大转换速率为 2.4MHz, 即最快转换时间为 0.41us;
  5. ADC供电要求为2.4V到3.6V,采集电压为0V到3.3V,输出数字信号转换结果为0到4095;
  6. 具有模拟看门狗的特性,允许应用检测输入电压是否超过了用户自 定义的阈值上限或下限。

二、DMA特性

DMA(Direct Memory Access,直接存储器访问),完成外设与存储器之间以及存储器与存储器之间的数据高速传输。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。本文采用F407芯片进行设计,其DMA具备如下特性:

  1. 该芯片拥有2个DMA控制器(DMA1和DMA2),每个DMA控制器拥有8个数据流通道,每个数据流有多达8个通道(请求),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求,还有一个仲裁器来协调各个DMA请求的优先权;
  2. 每个数据流有单独的四级32位先进先出存储器缓冲区(FIFO),可用于FIFO模式或直接模式;
  3. 支持外设到存储器、存储器到外设和存储器到存储器传输的常规通道;
  4. DMA数据流请求之间的优先级可用软件编程(4 个级别:非常高、高、中、低),在软件优先级相同的情况下可以通过硬件决定优先级(例如,请求 0 的优先级高于请求 1);
  5. 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芯片编写,可直接在原子开发板上运行,也可运行在各工程项目上,但需要注意各接口以及相应的引脚应和原子开发板上保持一致。

相应的代码链接:代码程序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/33933.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

三菱人机界面GOT SIMPLE 系列 GS2107\GS2110\GS2512

以客户需求为核心的全面升级!GOT SIMPLE系列新功能 GOT SIMPLE升级版重磅更新&#xff0c;增添了许多期待已久的新功能&#xff0c;帮助用户实现远程维护! 扩充用户存储器容量至15MB&#xff0c;并支持轮廓字体&#xff0c;以实现平滑、靓丽的字体显示。此外&#xff0c;可使用…

VLTVG代码复现并讲解

train.py 在main函数中找到这个构建模型的地方&#xff0c;ctrl&#xff0b;左键点进这个函数中去 来到了这里 又来到了这里&#xff0c;这里就是构建模型的地方&#xff1a; 又来到了这里&#xff0c;还是在VLTVG.py这个文件中&#xff1a; Method The Overall Network Visua…

转换思维是为智

转换思维是为智 2023年11月08日(节选) 我们人的思维分为人间思维&#xff0c;圣人思维&#xff0c;菩萨思维。人间思维讲得通俗一点就是世间智慧&#xff0c;他拥有的是人间的智慧&#xff0c;讲得再简单一点&#xff0c;就是人间的聪明。圣人的思维是什么&#xff0c;是一种脱…

qtcanpool 知 10:包管理雏形

文章目录 前言痛点转机雏形实践后语 前言 曾听闻&#xff1a;C/Qt 没有包管理器&#xff0c;开发起来太不方便。这是一个有过 node.js 开发经验的人对 Qt 的吐槽。 确实&#xff0c;像 python、golang、node.js 这些编程语言都有包管理器&#xff0c;给用户带来了极佳的开发体…

光敏传感器实验

用到 ADC 采集&#xff0c;通过 ADC 采集电压&#xff0c;获取光敏传感器的电阻变化&#xff0c;从而得出环境光线的变化&#xff0c;并在 TFTLCD 上面显示出来。 光敏传感器是最常见的传感器之一&#xff0c;它的种类繁多&#xff0c;主要有&#xff1a;光电管、光电倍增管…

Modbus RTU转Profinet接4台流量器配置案例

Modbus RTU转Profinet是工业自动化领域常见的通讯协议。Modbus RTU因其简单、可靠而被广泛应用于各种设备间的数据传输&#xff0c;而Profinet则以其高速、实时性在现代工业4.0场景中扮演着重要角色。本文将详细解析如何将Modbus RTU转换为Profinet&#xff0c;并通过实际案例来…

【AI系统】推理系统架构

推理系统架构 推理系统架构是 AI 领域中的一个关键组成部分&#xff0c;它负责将训练好的模型应用于实际问题&#xff0c;从而实现智能决策和自动化。在构建一个高效的推理系统时&#xff0c;我们不仅需要考虑其性能和准确性&#xff0c;还需要确保系统的可扩展性、灵活性以及…

家事速配社区新经济与消费新业态创新峰会成功举办,开启多元合作新篇章

2024 年 11 月 28 日&#xff0c;家事速配社区新经济与消费新业态创新峰会在福建福州隆重举行&#xff0c;此次峰会汇聚了各界精英嘉宾&#xff0c;共同见证了一系列具有里程碑意义的合作签约仪式&#xff0c;为社区新经济与消费新业态的融合发展注入强大动力。 上午时分&#…

数字逻辑——二进制

目录 1 信息与编码 1.1 什么是信息&#xff1f; 1.2 什么是编码&#xff1f; 2 数制和码制 2.1 数制 3 一些基本概念 3.1 位&#xff08;bit&#xff09; 3.2 字节&#xff08;byte&#xff09; 3.3 数据量的大小表示符号 4 二进制 4.1 二进制简介 4.2 二进制的…

PyQt信号槽实现页面的登录与跳转 #页面进一步优化

将登录框中的取消按钮使用信号和槽的机制&#xff0c;关闭界面。 将登录按钮使用信号和槽连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为"123456",如果账号密码匹配成功&#xff0c;当前界面关…

博客园-添加统计图

&#x1f496;简介 通过WPS在线列表构建博客园每日相关数据统计图。 &#x1f449;效果 &#x1f4d6;实现 前往WPS https://www.kdocs.cn/latest 新建多维表格 创建表格视图 新建仪表盘 新建卡片、折线图 卡片配置示例 折线图配置示例 点击分享获取链接 ⭐链接配置 在co…

浏览器指纹是什么?14种指纹的技术原理

视频版链接&#xff1a; 浏览器指纹是什么&#xff1f;14种指纹背后的技术原理 浏览器指纹简介 这个网站在我没登录的情况下&#xff0c;就能生成一个用户ID。即使我打开了浏览器的无痕模式&#xff0c;生成出来的ID也是一模一样。这背后的技术就是浏览器指纹。即使用户没有登…

Junit5 单元测试入门

基础知识 常用注解含义 Test&#xff1a;标记一个方法为测试方法BeforeEach&#xff1a;标记的方法会在每个测试方法执行前执行AfterEach&#xff1a;标记的方法会在每个测试方法执行后执行BeforeAll&#xff1a;标记的方法会在所有测试方法执行前执行一次AfterAll&#xff1…

JAVA-平台模块系统原理

菜鸟为了巩固所写 目录 菜鸟为了巩固所写 代码之间的依赖性 绘制类型依赖图 扩展到包之间的依赖关系 进一步延伸到jar包之间的依赖性 组件依赖图 JAVA技术领域中的两个著名的“擦除” Java类型的“大泥球” JAVA模块解析 模块解析的过程 模块路径明确模块的搜索与…

DevExtreme JS ASP.NET Core v24.2新功能预览 - 全新的聊天组件

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序。从Angular和Reac&#xff0c…

总结与反思-50天小总结

作者&#xff1a;麻瓜也要学魔法 时间&#xff1a;2024/12/4 不知不觉中咱博客的码龄居然50天了&#xff01;内容主要就是运维方面的知识&#xff0c;主打一个学到哪发到哪。 看了看&#xff0c;50天欸&#xff01;刚好就50篇文章&#xff0c;不得不说&#xff08;咱还是真稳定…

centos8 mysql 主从复制

原理 一、一主一从 准备工作 1.主库配置 1、修改配置文件 /etc/my.cnf #mysql 服务ID&#xff0c;保证整个集群环境中唯一&#xff0c;取值范围:1-232-1&#xff0c;默认为 server-id1 #是否只读,1 代表只读,0代表读写 read-only0 #忽略的数据,指不需要同步的数据库 #binlog…

iptables防火墙SNAT与DNAT

第二章 iptables防火墙SNAT与DNAT 文章目录 第二章 iptables防火墙SNAT与DNAT1 SNAT1.1 SNAT原理与应用1.2 SNAT工作原理1.3 SNAT转换前提条件2 SNAT示例2.1 网关[服务器配置](https://so.csdn.net/so/search?q服务器配置&spm1001.2101.3001.7020)2.1.1 网关服务器配置网卡…

【推荐100个unity插件之36】Unity6使用DOTS基础篇——Entities(非常适合做一些弹幕射击游戏)

文章目录 前言DOTS 核心组成DOTS 解决传统问题的痛点1、优化内存布局&#xff1a;2、减少垃圾回收和内存管理开销&#xff1a;3、提高并行计算能力&#xff1a;4、高效的系统和组件设计&#xff1a;5、易于扩展和优化&#xff1a; 安装文档在编辑器下构建 ECS World查看Entity的…

AI一键生成原创圣诞印花图案

一、引言 随着科技的飞速发展&#xff0c;AI 已经深入到我们生活和工作的各个角落&#xff0c;为创意设计领域带来了前所未有的变革。在圣诞即将来临之际&#xff0c;想要设计独特的圣诞印花图案却又担心缺乏灵感或专业技能&#xff1f;别担心&#xff0c;千鹿 AI 为我们提供了…