Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换
Keil MDK STM32系列
- Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发
- Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发
- Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发
- Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发
- Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构
- Keil MDK STM32系列(六) 基于HAL的ADC模数转换
- Keil MDK STM32系列(七) 基于HAL的PWM和定时器
- Keil MDK STM32系列(八) 基于HAL的PWM和定时器输出音频
- Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
配置 ADC
- 模式: 如果只启用了一个ADC, 这里只能配置为Independent mode
- 时钟分频: 这个选项是ADC的预分频器, 可设置为2/4/6/8, 决定了一个ADC时钟周期. 加入设置为2, 由于ADC是挂载在APB2总线(84M)上, 所以一个ADC时钟便是84 * M/2=42M
- 分辨率: 最高为12位分辨率, 分辨率越高转换时间越长
- 数据对齐方式: 如果选择12位分辨率, 右对齐, 得到的结果最大便是4096.
- 扫描模式: 转换完一个通道会不会继续转换下一个通道
- 连续转换模式: 使能的话转换将连续进行
- 不连续转换模式: 当使能多个转换通道时, 可单独设置不连续转换通道.
- DMA连续请求: 是否连续请求DMA.
- EOC标志设置: 当有多个转换通道时, 是每转换完一个通道设置一次EOC标志还是所有通道都转换完设置一次EOC标志.
- 转换的通道数:
- 触发模式: 可选择软件触发, 外部触发或定时器事件触发
- 秩序列表: 设置转换周期数和转换顺序
- 注入通道设置
- 窗口看门狗模式
配置 ADC 为主动请求模式
while (1)
{
  /*##-1- Start the conversion process #######################################*/
  HAL_ADC_Start(&hadc1);
  /*##-2- Wait for the end of conversion #####################################*/
   /*  Before starting a new conversion, you need to check the current state of
              the peripheral; if it’s busy you need to wait for the end of current
              conversion before starting a new one.
              For simplicity reasons, this example is just waiting till the end of the
              conversion, but application may perform other tasks while conversion
              operation is ongoing. */
  HAL_ADC_PollForConversion(&hadc1, 50);
  /* Check if the continous conversion of regular channel is finished */
  if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
  {
      /*##-3- Get the converted value of regular channel  ######################*/
      AD_Value = HAL_ADC_GetValue(&hadc1);
      printf("MCU Temperature : %.1f¡æ\r\n",((AD_Value*3300/4096-760)/2.5+25));
  }
  HAL_Delay(1000);
}
配置 ADC 为多通道连续扫描DMA模式
- 开ADC的IN0/IN1两个通道 - 在Pinout图上, 将PA0和PA1设为ADC1_IN0和ADC2_IN1
 
- 配置时钟 
- ADC1相关配置 
- ADCs_Common_Settings - Mode: Independent mode
 
- ADC_Settings - Clock Prescaler: PCLK2 divided by 4 可以在时钟配置页看到PCLK2的值
- Resolution: 12bits (15 ADC Clock cycles) 采样精度12bit, 此时每次采样需要15个时钟周期, 8bit对应11个时钟周期
- Data Alignment: Right alignment
- Scan Conversion Mode: Enabled
- Continuous Conversion Mode: Enabled --> for DMA
- Discontinuous Conversion Mode: Disabled
- DMA Continuous Requests: Enabled
- End Of Conversion Selection: EOC flag at the end of single channel conversion
 
- ADC_Regular_ConversionMode - Number of Conversion: 2 --> 2 channels
- External Trigger Conversion Source: Regular Conversion launched by software
- External Trigger Conversion Edge: None
- Rank: 1: Choose channel 0
- Rank: 2: Choose channel 1
 
- ADC_Injected_ConversionMode - Number of Conversions: 0
 
- DMA相关配置 
- ADC1 - Stream: DMA2 Stream 4
- Direction: Peripheral To Memory
- Priority: High
 
- DMA Request Settings - Mode: Circular
- Increment Address: Memory
- Datawidth: Peripheral->Half Word, Memory->Half Word
 
- NVIC Settings - ADC1 global interrupt: Enabled unchecked
- DMA2 stream4 global interrupt: Enabled checked
 
ADC+DMA配置, 体现在代码上的变化
- stm32f4xx_hal_conf.h 去掉了ADC的注释
#define HAL_ADC_MODULE_ENABLED
- stm32f4xx_it.h 增加了方法声明
void DMA2_Stream4_IRQHandler(void);
- stm32f4xx_it.c 增加了对应的typeDef和方法定义
extern DMA_HandleTypeDef hdma_adc1;
void DMA2_Stream4_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_adc1);
}
- stm32f4xx_hal_msp.c
extern DMA_HandleTypeDef hdma_adc1;
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();
    /**ADC1 GPIO Configuration
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA2_Stream4;
    hdma_adc1.Init.Channel = DMA_CHANNEL_0;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
      Error_Handler();
    }
    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
  }
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc)
{
  if(hadc->Instance==ADC1)
  {
    /* Peripheral clock disable */
    __HAL_RCC_ADC1_CLK_DISABLE();
    /**ADC1 GPIO Configuration
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1);
    /* ADC1 DMA DeInit */
    HAL_DMA_DeInit(hadc->DMA_Handle);
  }
}
- main.c
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = 2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
}
/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{
  __HAL_RCC_DMA2_CLK_ENABLE();
  HAL_NVIC_SetPriority(DMA2_Stream4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);
}
最后, 在main.c中增加用于存储DMA数据的数组, 将数组地址传给HAL_ADC_Start_DMA()开启DMA传输就可以得到数据了.
DMA数组大小和中断的问题
数组的大小与HAL_ADC_Start_DMA()方法第三个参数length一致, 这里length代表的是数据的个数. 在设置这个大小时, 如果开启了DMAx_Streamx_IRQn的中断, 要考虑sConfig.SamplingTime指定的采样时间不能太短, 太短的话会一直卡在中断里(因为中断什么都不做也需要时间). 这个与SYSCLK大小无关, 在两个通道采样时
- 如果这里指定的值为ADC_SAMPLETIME_3CYCLES, 这个数组大小至少为6, 如果等于4采样循环会卡住
- 如果指定的值为ADC_SAMPLETIME_15CYCLES, 这个数组大小至少为4
- 如果指定的值为ADC_SAMPLETIME_28CYCLES, 数组大小可以为2
如果不需要使用DMA中断, 可以在 MX_DMA_Init()方法中, 将HAL_NVIC_EnableIRQ(DMA2_Stream4_IRQn);这句注释掉或者改成HAL_NVIC_DisableIRQ(DMA2_Stream4_IRQn);指定禁用它, 这个数组就可以设到最小(和采样通道数一致)了.
uint16_t ADC_Value[6];
main(void) {
   HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value, 6);    // Enable DMA transfer
   while (1)
  {
    printf("%d %d %d %d\r\n",
      ADC_Value[0], ADC_Value[1], ADC_Value[2], ADC_Value[3]);
    HAL_Delay(100);
  }
}
DMA中断处理回调
查看代码可以看到, 在stm32f4xxx_hal_dma.h中, 定义的 DMA_HandleTypeDef 类型中, 包含了几个对应中断的处理方法
typedef struct __DMA_HandleTypeDef
{
  DMA_Stream_TypeDef         *Instance;                                                        /*!< Register base address                  */
  DMA_InitTypeDef            Init;                                                             /*!< DMA communication parameters           */
  HAL_LockTypeDef            Lock;                                                             /*!< DMA locking object                     */
  __IO HAL_DMA_StateTypeDef  State;                                                            /*!< DMA transfer state                     */
  void                       *Parent;                                                          /*!< Parent object state                    */
  void                       (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma);         /*!< DMA transfer complete callback         */
  void                       (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);     /*!< DMA Half transfer complete callback    */
  void                       (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma);       /*!< DMA transfer complete Memory1 callback */
  void                       (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma);   /*!< DMA transfer Half complete Memory1 callback */
  void                       (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA transfer error callback            */
  void                       (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma);        /*!< DMA transfer Abort callback            */
  __IO uint32_t              ErrorCode;                                                        /*!< DMA Error code                          */
  uint32_t                   StreamBaseAddress;                                                /*!< DMA Stream Base Address                */
  uint32_t                   StreamIndex;                                                      /*!< DMA Stream Index                       */
}DMA_HandleTypeDef;
其中处理接收完成的方法是 XferCpltCallback , 这个在 stm32f4xx_hal_adc.c 中, 被指定为相应的静态方法
stm32f4xx_hal_adc.c
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length)
{
  //...
  /* Set the DMA transfer complete callback */
  hadc->DMA_Handle->XferCpltCallback = ADC_DMAConvCplt;
对应不同外设, 指定的方法是不同的, 例如对于uart, stm32f4xx_hal_uart.c中指定的是
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  //...
  /* Set the UART DMA transfer complete callback */
  huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;
对于ADC, 再进一步在ADC_DMAConvCplt()方法中定义了处理方法
static void ADC_DMAConvCplt(DMA_HandleTypeDef *hdma)
{
  //...
    /* Conversion complete callback */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
    hadc->ConvCpltCallback(hadc);
#else
    HAL_ADC_ConvCpltCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
  }
  else /* DMA and-or internal error occurred */
  {
    if ((hadc->State & HAL_ADC_STATE_ERROR_INTERNAL) != 0UL)
    {
      /* Call HAL ADC Error Callback function */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
      hadc->ErrorCallback(hadc);
#else
      HAL_ADC_ErrorCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
    }
  else
  {
      /* Call DMA error callback */
      hadc->DMA_Handle->XferErrorCallback(hdma);
    }
  }
}
所以, 开发时只需要定义 HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) 和 HAL_ADC_ErrorCallback(ADC_HandleTypeDef* hadc)方法, 就能处理DMA传输完成的中断
参考
- https://blog.csdn.net/weixin_42157650/article/details/88913871
- 仅用2个字节做buffer的ADC https://controllerstech.blogspot.com/2017/08/adc-using-dma.html
- 禁用DMA中断 https://shequ.stmicroelectronics.cn/forum.php?mod=viewthread&tid=615792
Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换的更多相关文章
- Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构
		Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ... 
- AIR32F103(三) Linux环境基于标准外设库的项目模板
		目录 AIR32F103(一) 合宙AIR32F103CBT6开发板上手报告 AIR32F103(二) Linux环境和LibOpenCM3项目模板 AIR32F103(三) Linux环境基于标准外 ... 
随机推荐
- .NET 云原生架构师训练营(系统架构)--学习笔记
			目录 对外展现的功能 内部功能 功能交互与价值通路 系统架构 目标 认识系统的价值通路 认识功能架构,通过把功能结构与形式结构结合来描述系统架构 受益原则 好的架构必须使人受益,要想把架构做好,就要专 ... 
- WebApi的前端调用
			WebApi前端调用 HTML代码: <!DOCTYPE html><html> <head> <meta charset="utf-8" ... 
- response.setHeader("xxx","大侠")如果赋值中文,那么将不会在页面出值,
			response.setHeader("xxx","大侠")如果赋值中文,那么将不会在页面出值,而非中文就可以在页面出值 
- c++11之 algorithm 算法库新增 minmax_element同时计算最大值和最小值
			0.时刻提醒自己 Note: vector的释放 1. minmax_element 功能 寻找范围 [first, last) 中最小和最大的元素. 2. 头文件 #include <algo ... 
- Pikachu漏洞练习-SQL-inject(持续更新)
			本来在bup中repeater模式可以多次测试,但不知为何总是出错 这里把我们想查询的数据库和版本进行联合查询,放包,页面回显数据库名称和版本分别为pikachu,5.7.26 数据库版本大于5那么i ... 
- 问题--ImportError: DLL load failed: 找不到指定的模块
			今天在运行别人的项目时出现了问题: ImportError: DLL load failed: 找不到指定的模块. 解决方法: 卸载后重新安装. 详情参考: Python报错:ImportError: ... 
- python学习第一天:window安装python开发环境完整篇
			我是跟着廖雪峰老师的的博客来一步一步来进行学习和实践后记录下来的,讲的非常地详细,推荐大家一起学习https://www.liaoxuefeng.com/wiki/0014316089557264a6 ... 
- DEFENSE-GAN: PROTECTING CLASSIFIERS AGAINST ADVERSARIAL ATTACKS USING GENERATIVE MODELS
			目录 概 主要内容 Samangouei P, Kabkab M, Chellappa R, et al. Defense-GAN: Protecting Classifiers Against Ad ... 
- css怎么实现雪人
			冬天来了,怎么能少的了雪人呢,不管是现实中还是程序员的代码中统统都的安排上,那就一次安排几个雪人兄弟,咱们先看效果图: 有喜欢的就赶紧cv拿走吧!!! 其详细代码如下: 图1 html部分: < ... 
- oracle中connect by prior的使用
			作用 connect by主要用于父子,祖孙,上下级等层级关系的查询 语法 { CONNECT BY [ NOCYCLE ] condition [AND condition]... [ START ... 
