36. 单通道ADC过采样采集

一、ADC单通道过采样

  ADC 过采样技术,是利用 ADC 多次采集的方式,来提高 ADC 精度,采样速度每提高 4 倍,采样精度提高 1bit,同时,ADC 采样速度降低 4 倍,如提高 4bit 精度,需要 256 次采集才能得出 1 次数据,相当于 ADC 速度慢了 256 倍。理论上只要 ADC 足够块,我们可以无限提高 ADC 精度,但实际上 ADC 并不是无限块的,而且由于 ADC 性能限制,并不是位数无限提交结果就越好,需要根据自己的实际需求和 ADC 的实际性能来权衡。

  根据要增加的分辨率位数计算过采样频率方程如下:

\[f_{os} = 4^{w} * f_{s} \]

  其中,\(f_{os}\) 是过采样频率,w 是希望增加的分辨率位数,\(f_{s}\) 是初始采样频率要求。

  如果我们希望 12 位分辨率的 ADC 提高 4 位分辨率,采样频率就要提高 256 倍,即需要 256 次采集才能得到一次 16 位分辨率的数据。然后将这 256 次采集结果求和,求和的结果再右移 4 位,就得到提高分辨率后的结果。

提高 N 位分辨率,需要右移 N 位。

二、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);

  形参 hdacADC_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)

  InitADC初始化结构体,用于配置 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;

  ClockPrescalerADC 预分频系数选择,可选的分频系数为 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);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量。

  形参 sConfigADC_ChannelConfTypeDef 结构体类型指针变量,定义如下:

typedef struct
{
    uint32_t Channel;                   //  ADC转换通道
    uint32_t Rank;                      //  ADC转换顺序
    uint32_t SamplingTime;              //  ADC采样周期
    uint32_t Offset;                    //  ADC偏移量
} ADC_ChannelConfTypeDef;

  ChannelADC 转换通道,范围:0 ~ 19。

  Rank在常规转换中的常规组的转换顺序,可以选择 1 ~ 16。

  SamplingTimeADC 的采样周期,最大 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);

  形参 hdmaDMA_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_0DMA2_Stream4 中的 DMA_CHANNEL_0ADC2 使用 DMA2_Stream2 中的 DMA_CHANNEL_1DMA2_Stream3 中的 DMA_CHANNEL_1ADC3 使用 DMA2_Stream0 中的 DMA_CHANNEL_2DMA2_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 通道外设句柄

  StreamBaseAddressStreamIndex数据流基地址索引号,这个是 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中断

3.7.1、设置中断优先级分组

  HAL_NVIC_SetPriorityGrouping() 函数是设置中断优先级分组函数。其声明如下:

void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);

  其中,参数 PriorityGroup中断优先级分组号,可以选择范围如下:

#define NVIC_PRIORITYGROUP_0         0x00000007U /*!< 0 bits for pre-emption priority
                                                      4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1         0x00000006U /*!< 1 bits for pre-emption priority
                                                      3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2         0x00000005U /*!< 2 bits for pre-emption priority
                                                      2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3         0x00000004U /*!< 3 bits for pre-emption priority
                                                      1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4         0x00000003U /*!< 4 bits for pre-emption priority
                                                      0 bits for subpriority */

  这个函数在一个工程里基本只调用一次,而且是在程序 HAL 库初始化函数里面已经被调用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面设置好的抢占优先级和响应优先级不匹配。如果调用了多次,则以最后一次为准。

3.7.2、设置中断优先级

  HAL_NVIC_SetPriority() 函数是设置中断优先级函数。其声明如下:

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);

  其中,参数 IRQn中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。

typedef enum
{
  DMA2_Stream0_IRQn           = 56,     /*!< DMA2 Stream 0 global Interrupt                                    */
  DMA2_Stream1_IRQn           = 57,     /*!< DMA2 Stream 1 global Interrupt                                    */
  DMA2_Stream2_IRQn           = 58,     /*!< DMA2 Stream 2 global Interrupt                                    */
  DMA2_Stream3_IRQn           = 59,     /*!< DMA2 Stream 3 global Interrupt                                    */
  DMA2_Stream4_IRQn           = 60,     /*!< DMA2 Stream 4 global Interrupt                                    */
} IRQn_Type;

  参数 PreemptPriority抢占优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。

  参数 SubPriority响应优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。

3.7.3、使能中断

  HAL_NVIC_EnableIRQ() 函数是中断使能函数。其声明如下:

void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);

  其中,参数 IRQn中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。

3.8、使能DMA传输完成中断

#define __HAL_DMA_ENABLE_IT(__HANDLE__, __INTERRUPT__)   (((__INTERRUPT__) != DMA_IT_FE)? \
((__HANDLE__)->Instance->CR |= (__INTERRUPT__)) : ((__HANDLE__)->Instance->FCR |= (__INTERRUPT__)))

  其中,__HANDLE__ 是 DMA 句柄,__INTERRUPT__ 是中断标志,传输完成中断是 __DMA_IT_TC

3.9、编写DMA中断服务函数

  每开启一个中断,就必须编写其对应的中断服务函数,否则将导致死机(CPU 将找不到中断服务函数)。中断服务函数接口厂家已经在 startup_stm32f407xx.s 中写好了。

void DMA2_Stream0_IRQHandler(void);
void DMA2_Stream1_IRQHandler(void);
void DMA2_Stream2_IRQHandler(void);  
void DMA2_Stream3_IRQHandler(void);
void DMA2_Stream4_IRQHandler(void);   

3.10、开启DMA传输

HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);

  形参 hdmaDMA 句柄SrcAddress源地址DstAddress目的地址DataLength 是要 传输的数据长度

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

3.11、使用DMA传输ADC读取的数据

  配置好 ADC 通道之后,通过 HAL_ADC_Start() 函数启动 AD 转换器。

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量。

  形参 pData保存读取的 ADC 值的指针

  形参 Length要读取的长度

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

四、原理图

ADC模块

ADC通道

  使用短接片可以将 STM_ADC(PA5)连接在电位器(R_ADC)上(开发板上 STM_ADC 对应的丝印为 ADC,R_ADC 对应的丝印是 R),调节电位器即可改变电压,通过 ADC 转换即可检测此电压值。

五、程序源码

5.1、ADC初始化

  ADC 初始化函数:

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);

    HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

  DMA2 的 Stream0 中断函数:

uint8_t dma_is_finished = 0;

/**
 * @brief DMA2的Stream0中断函数
 * 
 */
void DMA2_Stream0_IRQHandler(void)
{
    if (DMA2->LISR & (1 << 5))                                                  // DMA2_Stream0传输完成标志
    {
        dma_is_finished = 1;                                                    // 标记DMA传输完成
        DMA2->LIFCR |= (1 << 5);                                                // 清除DMA2_Stream0传输完成标志
    }
}

  使能一次 ADC 的 DMA 传输函数:

/**
 * @brief 使能一次ADC的DMA传输函数
 * 
 * @param hadc ADC句柄
 * @param hdma DMA句柄
 * @param ndtr DMA传输的次数
 */
void ADC_DMA_Enable(ADC_HandleTypeDef *hadc, DMA_HandleTypeDef *hdma, uint16_t ndtr) 
{
    __HAL_ADC_DISABLE(hadc);                                                    // 先关闭ADC
    __HAL_DMA_DISABLE(hdma);                                                    // 关闭DMA传输
    hdma->Instance->NDTR= ndtr;                                                 // 重设DMA传输数据量 
    __HAL_DMA_ENABLE(hdma);                                                     // 开启DMA传输
    __HAL_ADC_ENABLE(hadc);                                                     // 重新启动ADC
    hadc->Instance->CR2 |= 1 << 30;                                             // 启动规则转换通道
}

5.3、main()函数

  main() 函数:

#define ADC_OVERSAMPLE_TIMES    256                                             // ADC过采样次数,从12位提高到16位需要采样256次
#define ADC_DMA_BUFFER_SIZE     ADC_OVERSAMPLE_TIMES * 10                       // 累计过采样10次

uint16_t g_adc_dma_buffer[ADC_DMA_BUFFER_SIZE] = {0};

int main(void)
{
    uint32_t sum = 0, value = 0;
    double result = 0;

    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

    UART_Init(&g_usart1_handle, USART1, 115200);

    ADC_Init(&g_adc1_handle, ADC1, 1, ENABLE, ENABLE);
    ADC_ConfigChannel(&g_adc1_handle, ADC_CHANNEL_5, 1, ADC_SAMPLETIME_3CYCLES);

    // 使用DMA2的Stream0的Channel0
    DMA_PeripheralToMemory_Init(DMA2_Stream0, ADC_CHANNEL_0, 16, DMA_NORMAL, DMA_PRIORITY_HIGH);
    __HAL_LINKDMA(&g_adc1_handle, DMA_Handle, g_dma_handle);                    // 链接DMA与ADC1
  
    __HAL_DMA_ENABLE_IT(&g_dma_handle, DMA_IT_TC);                              // TCIE=1,使能传输完成中断
    HAL_DMA_Start_IT(&g_dma_handle, (uint32_t)&ADC1->DR, (uint32_t)g_adc_dma_buffer, 0);
    HAL_ADC_Start_DMA(&g_adc1_handle, (uint32_t *)g_adc_dma_buffer, 0);

    ADC_DMA_Enable(&g_adc1_handle, &g_dma_handle, ADC_DMA_BUFFER_SIZE);

    while (1)
    {
        if (dma_is_finished)
        {
            sum = 0;
            for (uint32_t i = 0; i < ADC_DMA_BUFFER_SIZE; i++)
            {
                sum += g_adc_dma_buffer[i];
            }
  
            value = sum / (ADC_DMA_BUFFER_SIZE / ADC_OVERSAMPLE_TIMES);         // 过采样10次的平均值
            value >>= 4;                                                        // 右移12位,从12位变成16位
            printf("value: %ld\r\n", value);
            result = (double)value * 3.3 / 65536;
            printf("result: %.2lf\r\n", result);

            dma_is_finished = 0;
            ADC_DMA_Enable(&g_adc1_handle, &g_dma_handle, ADC_DMA_BUFFER_SIZE);
        }
  
       HAL_Delay(1000);
    }
  
    return 0;
}
posted @ 2023-12-30 19:39  星光樱梦  阅读(13)  评论(0编辑  收藏  举报