37. 多通道ADC采集
一、ADC内部温度传感器
STM32F407 有一个内部的温度传感器,可以用来测量 CPU 及周围的温度。对于STM32F407 系列来说,该温度传感器在内部和 ADC1_INP16 输入通道相连接,此通道把传感器输出的电压转换成数字值。STM32F4 的内部温度传感器支持的温度范围为:-40~125℃。精度为 ±1.5℃ 左右。
我们要使用 STM32F407 的内部温度传感器,必须先激活 ADC 的内部通道,这里通过 ADC_CCR 的 VSENSEEN 位(bit23)设置。设置该位为 1 则启用内部温度传感器。STM32F407ZGT6 的内部温度传感器固定的连接在 ADC1 的通道 16 上,所以,我们在设置好 ADC1 之后只要读取通道 16 的值,就是温度传感器返回来的电压值了。根据这个值,我们就可以计算出当前温度。计算公式如下:
式子中: 等于 25℃ 的 的数值(典型值为:0.76)。Avg_Slope 为温度与 曲线的平均斜率(单位:mv/℃ 或 uv/℃)(典型值:2.5mv/℃)。 是 ADC 采集到内部温度传感器的电压值。
二、ADC对应的通道引脚
通道号 | ADC1 | ADC2 | ADC3 |
---|---|---|---|
通道 0 | PA0 | PA0 | PA0 |
通道 1 | PA1 | PA1 | PA1 |
通道 2 | PA2 | PA2 | PA2 |
通道 3 | PA3 | PA3 | PA3 |
通道 4 | PA4 | PA4 | PF6 |
通道 5 | PA5 | PA5 | PF7 |
通道 6 | PA6 | PA6 | PF8 |
通道 7 | PA7 | PA7 | PF9 |
通道 8 | PB0 | PB0 | PF10 |
通道 9 | PB1 | PB1 | PF3 |
通道 10 | PC0 | PC0 | PC0 |
通道 11 | PC1 | PC0 | PC1 |
通道 12 | PC2 | PC2 | PC2 |
通道 13 | PC3 | PC3 | PC3 |
通道 14 | PC4 | PC3 | PF4 |
通道 15 | PC5 | PC5 | PF5 |
三、单通道ADC过采样采集步骤
3.1、使能ADC对应的GPIO时钟
使用 ADC 时钟:
#define __HAL_RCC_ADC1_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC1EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC1EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_ADC2_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC2EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC2EN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_ADC3_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC3EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_ADC3EN);\
UNUSED(tmpreg); \
} while(0U)
使能对应的 GPIO 时钟:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOB_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOBEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOC_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
UNUSED(tmpreg); \
} while(0U)
#define __HAL_RCC_GPIOF_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOFEN);\
UNUSED(tmpreg); \
} while(0U)
3.2、配置ADC工作参数
ADC 的初始化函数,其声明如下:
HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef *hadc);
形参 hdac 是 ADC_HandleTypeDef 结构体类型指针变量,其定义如下:
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;
Instance:指向 ADC 寄存器基地址。实际上这个基地址 HAL 库已经定义好了,可以选择范围如下:
#define ADC1 ((ADC_TypeDef *) ADC1_BASE)
#define ADC2 ((ADC_TypeDef *) ADC2_BASE)
#define ADC3 ((ADC_TypeDef *) ADC3_BASE)
Init:ADC初始化结构体,用于配置 ADC 的工作参数。
typedef struct
{
uint32_t ClockPrescaler; // 设置预分频系数,即PRESC[3:O]位
uint32_t Resolution; // 配置ADC的分辨率
uint32_t DataAlign; // 数据对齐方式
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;
ClockPrescaler:ADC 预分频系数选择,可选的分频系数为 2、4、6、8。
#define ADC_CLOCK_SYNC_PCLK_DIV2 0x00000000U
#define ADC_CLOCK_SYNC_PCLK_DIV4 ((uint32_t)ADC_CCR_ADCPRE_0)
#define ADC_CLOCK_SYNC_PCLK_DIV6 ((uint32_t)ADC_CCR_ADCPRE_1)
#define ADC_CLOCK_SYNC_PCLK_DIV8 ((uint32_t)ADC_CCR_ADCPRE)
Resolution:配置 ADC 的分辨率,可选的分辨率有 12 位、10 位、8 位和 6 位。分辨率越高,转换数据精度越高,转换时间也越长;反之分辨率越低,转换数据精度越低,转换时间也越短。
#define ADC_RESOLUTION_12B 0x00000000U
#define ADC_RESOLUTION_10B ((uint32_t)ADC_CR1_RES_0)
#define ADC_RESOLUTION_8B ((uint32_t)ADC_CR1_RES_1)
#define ADC_RESOLUTION_6B ((uint32_t)ADC_CR1_RES)
DataAlign:转换结果数据对齐模式,可选右对齐 ADC_DataAlign_Right 或者左对齐ADC_DataAlign_Left。一般我们选择右对齐模式。
#define ADC_DATAALIGN_RIGHT 0x00000000U
#define ADC_DATAALIGN_LEFT ((uint32_t)ADC_CR2_ALIGN)
ScanConvMode:配置是否使用扫描。如果是单通道转换使用 DISABLE,如果是多通道转换使用 ENABLE。
typedef enum
{
DISABLE = 0U,
ENABLE = !DISABLE
} FunctionalState;
EOCSelection:指定转换结束时是否产生 EOS 中断或事件标志,可选值如下:
#define ADC_EOC_SEQ_CONV 0x00000000U
#define ADC_EOC_SINGLE_CONV 0x00000001U
#define ADC_EOC_SINGLE_SEQ_CONV 0x00000002U /*!< reserved for future use */
ContinuousConvMode:配置自动连续转换还是单次转换。使用 ENABLE 配置为使能自动连续转换;使用 DISABLE 配置为单次转换,转换一次后停止需要手动控制才重新启动转换。
typedef enum
{
DISABLE = 0U,
ENABLE = !DISABLE
} FunctionalState;
NbrOfConversion:设置常规转换通道数目,范围是:1 ~ 16。
DiscontinuousConvMode:配置是否使用不连续的采样模式,比如要转换的通道有 1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。此参数只有将 ScanConvMode 使能,还有 ContinuousConvMode失能的情况下才有效,不可同时使能。
typedef enum
{
DISABLE = 0U,
ENABLE = !DISABLE
} FunctionalState;
NbrOfDiscConversion:不连续采样通道数。
ExternalTrigConv:外部触发方式的选择,如果使用软件触发,那么外部触发会关闭。
#define ADC_EXTERNALTRIGCONV_T1_CC1 0x00000000U
#define ADC_EXTERNALTRIGCONV_T1_CC2 ((uint32_t)ADC_CR2_EXTSEL_0)
#define ADC_EXTERNALTRIGCONV_T1_CC3 ((uint32_t)ADC_CR2_EXTSEL_1)
#define ADC_EXTERNALTRIGCONV_T2_CC2 ((uint32_t)(ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T2_CC3 ((uint32_t)ADC_CR2_EXTSEL_2)
#define ADC_EXTERNALTRIGCONV_T2_CC4 ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T2_TRGO ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_T3_CC1 ((uint32_t)(ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T3_TRGO ((uint32_t)ADC_CR2_EXTSEL_3)
#define ADC_EXTERNALTRIGCONV_T4_CC4 ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T5_CC1 ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_T5_CC2 ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T5_CC3 ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2))
#define ADC_EXTERNALTRIGCONV_T8_CC1 ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_0))
#define ADC_EXTERNALTRIGCONV_T8_TRGO ((uint32_t)(ADC_CR2_EXTSEL_3 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1))
#define ADC_EXTERNALTRIGCONV_Ext_IT11 ((uint32_t)ADC_CR2_EXTSEL)
#define ADC_SOFTWARE_START ((uint32_t)ADC_CR2_EXTSEL + 1U)
ExternalTrigConvEdge:外部触发极性选择,如果使用外部触发,可以选择触发的极性,可选有禁止触发检测、上升沿触发检测、下降沿触发检测以及上升沿和下降沿均可触发检测。
DMAContinuousRequests:指定 DMA 请求是否以一次性模式执行(当达到转换次数时,DMA 传输停止)或在连续模式下(DMA 传输无限制,无论转换的数量)。注:在连续模式下,DMA 必须配置为循环模式。否则,当达到 DMA 缓冲区最大指针时将触发溢出。注意:当常规组和注入组都没有转换时(禁用 ADC,或启用 ADC,没有连续模式或可以启动转换的外部触发器),必须修改此参数。该参数可设置为 “启用” 或 “禁用”。
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
typedef enum
{
HAL_OK = 0x00U, // 成功
HAL_ERROR = 0x01U, // 错误
HAL_BUSY = 0x02U, // 忙碌
HAL_TIMEOUT = 0x03U // 超时
} HAL_StatusTypeDef;
3.3、ADC底层初始化
HAL 库中,提供 HAL_GPIO_Init()
函数用于配置 GPIO 功能模式,初始化 GPIO。该函数的声明如下:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
该函数的第一个形参 GPIOx 用来指定端口号,可选值如下:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
第二个参数是 GPIO_InitTypeDef 类型的结构体变量,用来设置 GPIO 的工作模式,其定义如下:
typedef struct
{
uint32_t Pin; // 引脚号
uint32_t Mode; // 模式设置
uint32_t Pull; // 上下拉设置
uint32_t Speed; // 速度设置
uint32_t Alternate; // 复用功能设置
}GPIO_InitTypeDef;
成员 Pin 表示 引脚号,范围:GPIO_PIN_0 到 GPIO_PIN_15。
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */
#define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */
#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
#define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */
#define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
#define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */
#define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
#define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */
#define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */
#define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */
成员 Mode 是 GPIO 的 模式选择,有以下选择项:
#define GPIO_MODE_ANALOG MODE_ANALOG // 模式功能
成员 Pull 用于 配置上下拉电阻,有以下选择项:
#define GPIO_NOPULL 0x00000000U // 无上下拉
#define GPIO_PULLUP 0x00000001U // 上拉
#define GPIO_PULLDOWN 0x00000002U // 下拉
3.4、配置ADC通道
在 HAL 库中,通过 HAL_ADC_ConfigChannel()
函数来设置配置 ADC 的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。
HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef *hadc, ADC_ChannelConfTypeDef *sConfig);
形参 hdac 是 ADC_HandleTypeDef 结构体类型指针变量。
形参 sConfig 是 ADC_ChannelConfTypeDef 结构体类型指针变量,定义如下:
typedef struct
{
uint32_t Channel; // ADC转换通道
uint32_t Rank; // ADC转换顺序
uint32_t SamplingTime; // ADC采样周期
uint32_t Offset; // ADC偏移量
} ADC_ChannelConfTypeDef;
Channel:ADC 转换通道,范围:0 ~ 19。
Rank:在常规转换中的常规组的转换顺序,可以选择 1 ~ 16。
SamplingTime:ADC 的采样周期,最大 480 个 ADC 时钟周期,要求采样周期尽量大,以减少误差。
#define ADC_SAMPLETIME_3CYCLES 0x00000000U
#define ADC_SAMPLETIME_15CYCLES ((uint32_t)ADC_SMPR1_SMP10_0)
#define ADC_SAMPLETIME_28CYCLES ((uint32_t)ADC_SMPR1_SMP10_1)
#define ADC_SAMPLETIME_56CYCLES ((uint32_t)(ADC_SMPR1_SMP10_1 | ADC_SMPR1_SMP10_0))
#define ADC_SAMPLETIME_84CYCLES ((uint32_t)ADC_SMPR1_SMP10_2)
#define ADC_SAMPLETIME_112CYCLES ((uint32_t)(ADC_SMPR1_SMP10_2 | ADC_SMPR1_SMP10_0))
#define ADC_SAMPLETIME_144CYCLES ((uint32_t)(ADC_SMPR1_SMP10_2 | ADC_SMPR1_SMP10_1))
#define ADC_SAMPLETIME_480CYCLES ((uint32_t)ADC_SMPR1_SMP10)
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
3.5、配置DMA
3.5.1、使能DMA时钟
使能对应的 DMA 时钟。
#define __HAL_RCC_DMA2_CLK_ENABLE() do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DMA2EN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DMA2EN);\
UNUSED(tmpreg); \
} while(0U)
3.5.2、配置DMA工作参数
DMA 的初始化函数,其声明如下:
HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma);
形参 hdma 是 DMA_HandleTypeDef 结构体类型指针变量,其定义如下:
typedef struct __DMA_HandleTypeDef
{
DMA_Stream_TypeDef *Instance; // 寄存器基地址
DMA_InitTypeDef Init; // DAM通信参数
HAL_LockTypeDef Lock; // DMA锁对象
__IO HAL_DMA_StateTypeDef State; // DMA传输状态
void *Parent; // 父对象状态,HAL库处理的中间变量
void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA传输完成回调
void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA一半传输完成回调
void (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA传输完整的Memory1回调
void (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); // DMA传输半完全内存回调
void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); // DMA传输错误回调
void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); // DMA传输中止回调
__IO uint32_t ErrorCode; // DMA存取错误代码
uint32_t StreamBaseAddress; // DMA通道基地址
uint32_t StreamIndex; // DMA通道索引
}DMA_HandleTypeDef;
Instance:是用来 设置寄存器基地址。ADC1
使用 DMA2_Stream0
中的 DMA_CHANNEL_0
或 DMA2_Stream4
中的 DMA_CHANNEL_0
。ADC2
使用 DMA2_Stream2
中的 DMA_CHANNEL_1
或 DMA2_Stream3
中的 DMA_CHANNEL_1
。ADC3
使用 DMA2_Stream0
中的 DMA_CHANNEL_2
或 DMA2_Stream1
中的 DMA_CHANNEL_2
。
#define DMA2_Stream0 ((DMA_Stream_TypeDef *) DMA2_Stream0_BASE)
#define DMA2_Stream1 ((DMA_Stream_TypeDef *) DMA2_Stream1_BASE)
#define DMA2_Stream2 ((DMA_Stream_TypeDef *) DMA2_Stream2_BASE)
#define DMA2_Stream3 ((DMA_Stream_TypeDef *) DMA2_Stream3_BASE)
#define DMA2_Stream4 ((DMA_Stream_TypeDef *) DMA2_Stream4_BASE)
Parent:是 HAL 库处理中间变量,用来 指向 DMA 通道外设句柄。
StreamBaseAddress 和 StreamIndex 是 数据流基地址 和 索引号,这个是 HAL 库处理的时候会自动计算,用户无需设置。
Init,它是 DMA_InitTypeDef 结构体类型变量,该结构体定义如下:
typedef struct
{
uint32_t Channel; // 传输通道
uint32_t Direction; // 传输方向
uint32_t PeriphInc; // 外设地址是否自增
uint32_t MemInc; // 内存地址是否自增
uint32_t PeriphDataAlignment; // 外设数据大小
uint32_t MemDataAlignment; // 存储器数据大小
uint32_t Mode; // 传输模式
uint32_t Priority; // DMA优先级
uint32_t FIFOMode; // FIFO模式开启或者禁止
uint32_t FIFOThreshold; // FIFO阈值选择
uint32_t MemBurst; // 存储器突发模式
uint32_t PeriphBurst; // 外设突发模式
}DMA_InitTypeDef;
成员 Channel 用来 指定传输通道,可选值如下:
#define DMA_CHANNEL_0 0x00000000U /*!< DMA Channel 0 */
#define DMA_CHANNEL_1 0x02000000U /*!< DMA Channel 1 */
#define DMA_CHANNEL_2 0x04000000U /*!< DMA Channel 2 */
#define DMA_CHANNEL_3 0x06000000U /*!< DMA Channel 3 */
#define DMA_CHANNEL_4 0x08000000U /*!< DMA Channel 4 */
#define DMA_CHANNEL_5 0x0A000000U /*!< DMA Channel 5 */
#define DMA_CHANNEL_6 0x0C000000U /*!< DMA Channel 6 */
#define DMA_CHANNEL_7 0x0E000000U /*!< DMA Channel 7 */
成员 Direction 用来 指定传输方向,可选值如下:
#define DMA_PERIPH_TO_MEMORY 0x00000000U // 外设到内存
成员 PeriphInc 用来 设置外设地址是否自增,可选值如下:
#define DMA_PINC_DISABLE 0x00000000U /*!< Peripheral increment mode disable */
成员 MemInc 用来 设置内存地址是否自增,可选值如下:
#define DMA_MINC_ENABLE ((uint32_t)DMA_SxCR_MINC) /*!< Memory increment mode enable */
成员 PeriphDataAlignment 用来 外设数据大小,可选值如下:
#define DMA_PDATAALIGN_BYTE 0x00000000U /*!< Peripheral data alignment: Byte */
#define DMA_PDATAALIGN_HALFWORD ((uint32_t)DMA_SxCR_PSIZE_0) /*!< Peripheral data alignment: HalfWord */
#define DMA_PDATAALIGN_WORD ((uint32_t)DMA_SxCR_PSIZE_1) /*!< Peripheral data alignment: Word */
成员 MemDataAlignment 用来 存储器数据大小,可选值如下:
#define DMA_MDATAALIGN_BYTE 0x00000000U /*!< Memory data alignment: Byte */
#define DMA_MDATAALIGN_HALFWORD ((uint32_t)DMA_SxCR_MSIZE_0) /*!< Memory data alignment: HalfWord */
#define DMA_MDATAALIGN_WORD ((uint32_t)DMA_SxCR_MSIZE_1) /*!< Memory data alignment: Word */
成员 Mode 用来 设置传输模式,可选值如下:
#define DMA_NORMAL 0x00000000U // 普通模式
#define DMA_CIRCULAR ((uint32_t)DMA_SxCR_CIRC) // 循环模式
#define DMA_PFCTRL ((uint32_t)DMA_SxCR_PFCTRL) // 外设流控模式
成员 Priority 用来 DMA 优先级,可选值如下:
#define DMA_PRIORITY_LOW 0x00000000U /*!< Priority level: Low */
#define DMA_PRIORITY_MEDIUM ((uint32_t)DMA_SxCR_PL_0) /*!< Priority level: Medium */
#define DMA_PRIORITY_HIGH ((uint32_t)DMA_SxCR_PL_1) /*!< Priority level: High */
#define DMA_PRIORITY_VERY_HIGH ((uint32_t)DMA_SxCR_PL) /*!< Priority level: Very High */
成员 FIFOThreshold 用来 设置是否开启 FIFO,可选值如下:
#define DMA_FIFOMODE_DISABLE 0x00000000U /*!< FIFO mode disable */
#define DMA_FIFOMODE_ENABLE ((uint32_t)DMA_SxFCR_DMDIS) /*!< FIFO mode enable */
成员 FIFOMode 用来 设置 FIFO 的阈值,可选值如下:
#define DMA_FIFO_THRESHOLD_1QUARTERFULL 0x00000000U /*!< FIFO threshold 1 quart full configuration */
#define DMA_FIFO_THRESHOLD_HALFFULL ((uint32_t)DMA_SxFCR_FTH_0) /*!< FIFO threshold half full configuration */
#define DMA_FIFO_THRESHOLD_3QUARTERSFULL ((uint32_t)DMA_SxFCR_FTH_1) /*!< FIFO threshold 3 quarts full configuration */
#define DMA_FIFO_THRESHOLD_FULL ((uint32_t)DMA_SxFCR_FTH) /*!< FIFO threshold full configuration */
成员 MemBurst 用来 设置存储器突发模式,可选值如下:
#define DMA_MBURST_SINGLE 0x00000000U
#define DMA_MBURST_INC4 ((uint32_t)DMA_SxCR_MBURST_0)
#define DMA_MBURST_INC8 ((uint32_t)DMA_SxCR_MBURST_1)
#define DMA_MBURST_INC16 ((uint32_t)DMA_SxCR_MBURST)
成员 PeriphBurst 用来 设置外设突发模式,可选值如下:
#define DMA_PBURST_SINGLE 0x00000000U
#define DMA_PBURST_INC4 ((uint32_t)DMA_SxCR_PBURST_0)
#define DMA_PBURST_INC8 ((uint32_t)DMA_SxCR_PBURST_1)
#define DMA_PBURST_INC16 ((uint32_t)DMA_SxCR_PBURST)
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
3.6、连接DMA和外设
HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接 DMA 和外设句柄。
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__, __DMA_HANDLE__) \
do{ \
(__HANDLE__)->__PPP_DMA_FIELD__ = &(__DMA_HANDLE__); \
(__DMA_HANDLE__).Parent = (__HANDLE__); \
} while(0U)
其中 __HANDLE__
是外设初始化句柄。__PPP_DMA_FIELD__
是外设句柄结构体的成员变量名。__DMA_HANDLE__
是 DMA初始化句柄。
在 HAL 库中,任何一个可以使用 DMA 的外设,它的初始化结构体句柄都会有一个 DMA_HandleTypeDef 指针类型的成员变量,是 HAL 库用来做相关指向的。__PPP_DMA_FIELD__
就是 DMA_HandleTypeDef 结构体指针类型。这句话的含义就是把 __HANDLE__
句柄的成员变量 __PPP_DMA_FIELD__
和 DMA 句柄 __DMA_HANDLE__
连接起来,是纯软件处理,没有任何硬件操作。
3.7、使能DMA传输数据
配置好 ADC 通道之后,通过 HAL_ADC_Start()
函数启动 AD 转换器。
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length);
形参 hdac 是 ADC_HandleTypeDef 结构体类型指针变量。
形参 pData 是 保存读取的 ADC 值的指针。
形参 Length 是 要读取的长度。
该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功,HAL_ERROR 表示 错误,HAL_BUSY 表示 忙碌,HAL_TIMEOUT 表示 超时。
四、原理图
使用短接片可以将 STM_ADC(PA5)连接在电位器(R_ADC)上(开发板上 STM_ADC 对应的丝印为 ADC,R_ADC 对应的丝印是 R),调节电位器即可改变电压,通过 ADC 转换即可检测此电压值。
五、程序源码
5.1、ADC初始化
ADC1 初始化函数:
ADC_HandleTypeDef g_adc1_handle;
/**
* @brief ADC初始化函数
*
* @param hadc ADC句柄
* @param ADCx ADC寄存器基地址
* @param numOfConversion 常规转换的通道数
* @param continuousConvMode ADC是否连续转换, ENABLE:连续转换, DISABLE:单次转换
* @param dmaContinuousRequests DMA是否连续请求, ENABLE:使用DMA, DISABLE:不使用DMA
*/
void ADC_Init(ADC_HandleTypeDef *hadc, ADC_TypeDef *ADCx, uint32_t numOfConversion, FunctionalState continuousConvMode, FunctionalState dmaContinuousRequests)
{
hadc->Instance = ADCx; // ADC寄存器基地址
hadc->Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // ADC时钟分频因子
hadc->Init.Resolution = ADC_RESOLUTION_12B; // 12位模式
hadc->Init.DataAlign = ADC_DATAALIGN_RIGHT; // 数据右对齐
hadc->Init.EOCSelection = DISABLE; // 转换完成标志位
hadc->Init.ScanConvMode = ENABLE; // 配置是否使用扫描
hadc->Init.ContinuousConvMode = continuousConvMode; // ADC是否自动连续转换
hadc->Init.NbrOfConversion = numOfConversion; // 配置常规转换通道
hadc->Init.DiscontinuousConvMode = DISABLE; // 是否使用不连续的采样模式
hadc->Init.NbrOfDiscConversion = 0; // 不连续采样通道数
hadc->Init.ExternalTrigConv = ADC_SOFTWARE_START; // 外部触发方式的选择
hadc->Init.DMAContinuousRequests = dmaContinuousRequests; // 是否开启DMA连续转换
HAL_ADC_Init(hadc);
}
ADC 底层初始化函数:
/**
* @brief ADC底层初始化函数
*
* @param hadc ADC句柄
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (hadc->Instance == ADC1)
{
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_5; // PA5引脚设置为ADC1的通道5引脚
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 模拟功能
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不使用上下拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
ADC 通道配置函数:
/**
* @brief ADC通道配置函数
*
* @param hadc ADC句柄
* @param channel ADC通道,可选值: ADC_CHANNEL_0 ~ ADC_CHANNEL_18
* @param rank 采样序列
* @param samplingTime 采样时间,可选值:ADC_SAMPLETIME_xCYCLES,x: [3,15,28,56,112,144,480]
*/
void ADC_ConfigChannel(ADC_HandleTypeDef *hadc, uint32_t channel, uint32_t rank, uint32_t samplingTime)
{
ADC_ChannelConfTypeDef ADC_ChannelConfStruct = {0};
ADC_ChannelConfStruct.Channel = channel; // ADC通道
ADC_ChannelConfStruct.Rank = rank; // 采样序列
ADC_ChannelConfStruct.SamplingTime = samplingTime; // 采样时间
HAL_ADC_ConfigChannel(hadc, &ADC_ChannelConfStruct);
}
5.2、DMA初始化
DMA 外设到存储器间的数据传输初始化函数:
DMA_HandleTypeDef g_dma_handle;
/**
* @brief DMA外设到存储器间的数据传输初始化函数
*
* @param dma_stream DMA数据流,可选值: DMA1_Stream0 ~ DMA1_Stream7, DMA2_Stream0 ~ DMA2_Stream7
* @param channel DMA通道,可选值: DMA_CHANNEL_0 ~ DMA_CHANNEL_7
* @param dataLength 数据长度,可选值: [8, 16, 32]
* @param mode DMA工作模式,可选值: [DMA_NORMAL, DMA_CIRCULAR, DMA_PFCTRL]
* @param priority DMA通道优先级,可选值: [DMA_PRIORITY_LOW, DMA_PRIORITY_MEDIUM, DMA_PRIORITY_HIGH, DMA_PRIORITY_VERY_HIGH]
*/
void DMA_PeripheralToMemory_Init(DMA_Stream_TypeDef *dma_stream, uint32_t channel, uint8_t dataLength, uint32_t mode, uint32_t priority)
{
if ((uint32_t)dma_stream > (uint32_t)DMA2) // 得到当前stream是属于DMA2还是DMA1
{
__HAL_RCC_DMA2_CLK_ENABLE();
}
else
{
__HAL_RCC_DMA1_CLK_ENABLE();
}
g_dma_handle.Instance = dma_stream; // 数据流选择
g_dma_handle.Init.Channel = channel; // DMA通道选择
g_dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到存储器
g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; // 外设非增量模式
g_dma_handle.Init.MemInc = DMA_MINC_ENABLE; // 存储器增量模式
switch (dataLength)
{
case 8:
g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设地址长度
g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 存储器地址长度
break;
case 16:
g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 外设地址长度
g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; // 存储器地址长度
break;
case 32:
g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // 外设地址长度
g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; // 存储器地址长度
break;
default:
break;
}
g_dma_handle.Init.Mode = mode; // DMA模式
g_dma_handle.Init.Priority = priority; // DMA通道优先级
g_dma_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 关闭FIFO模式
g_dma_handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; // FIFO阈值配置
g_dma_handle.Init.MemBurst = DMA_MBURST_SINGLE; // 存储器突发单次传输
g_dma_handle.Init.PeriphBurst = DMA_PBURST_SINGLE; // 外设突发单次传输
HAL_DMA_Init(&g_dma_handle);
}
5.3、main()函数
main() 函数:
int main(void)
{
uint16_t data[2] = {0};
double potentiometer = 0, temperature = 0;
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
USART1_Init(115200);
ADC1_Init(2, ENABLE, ENABLE);
ADC_ConfigChannel(&g_adc1_handle, ADC_CHANNEL_5, 1, ADC_SAMPLETIME_3CYCLES);
ADC_ConfigChannel(&g_adc1_handle, ADC_CHANNEL_16, 2, ADC_SAMPLETIME_3CYCLES);
// 使用DMA2的Stream0的Channel0
DMA_PeripheralToMemory_Init(DMA2_Stream0, ADC_CHANNEL_0, 16, DMA_CIRCULAR, DMA_PRIORITY_HIGH);
__HAL_LINKDMA(&g_adc1_handle, DMA_Handle, g_dma_handle); // 链接DMA与ADC1
HAL_ADC_Start_DMA(&g_adc1_handle, (uint32_t *)data, 2);
while (1)
{
potentiometer = (double)data[0] * 3.3 / 4096;
printf("data0: %d\r\n", data[0]);
printf("potentiometer: %.2lf\r\n", potentiometer);
temperature = (double)data[1] * (3.3 / 4096); // 获取电压值
temperature = (temperature - 0.76) /0.0025 + 25;
printf("data1: %d\r\n", data[1]);
printf("temperature: %.2lf\r\n", temperature);
HAL_Delay(1000);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?