stm32CubeMx ADC多通道扫描模式DMA + 采集STM32内部温度传感器的值之代码分析

之前博客中讲解了如何通过STM32CubeMx配置生成多通道ADC的DMA方式采集,以及内部温度传感器的使用,但还留下了一些疑问,问什么需要屏蔽DMA传输完成的中断,以及所生成的代码的详细分析。接下来就开始对上一篇文章的内容进一步讲解。
本次的分析思路将按照自动生成的主函数顺序进行讲解。但不介绍如下函数:

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  SystemClock_Config();

HAL_Init()函数的作用是重置所有外设,初始化FLASH接口和Systick时钟。
SystemClock_Config();该函数是用于按照时钟树配置系统时钟。
接下来就是配置所有使用的外围设备。

外设GPIO时钟使能配置:MX_GPIO_Init();

在该函数中只有GPIO端口时钟使能;由于之前篇文章中使用的引脚如下图所示:
使用的引脚
可以很直接的看到我们只是用了单片机GPIO的A、B、D端口,所以需要打开对应的端口时钟,该函数内容为就是这样来的。

void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

}

而__HAL_RCC_GPIOD_CLK_ENABLE();这是一句宏定义,那这是怎么实现的呢,可以通过keil跳转功能查找到定义的语句:

#define __HAL_RCC_GPIOD_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg; \
                                        SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPDEN);\
                                        /* Delay after an RCC peripheral clock enabling */\
                                        tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPDEN);\
                                        UNUSED(tmpreg); \
                                      } while(0U)

注意:这不是一个死循环,while中条件为0。
这里所涉及的宏为:

#define SET_BIT(REG, BIT)     ((REG) |= (BIT))
#define READ_BIT(REG, BIT)    ((REG) & (BIT))

#define RCC_APB2ENR_IOPDEN_Pos               (5U)                              
#define RCC_APB2ENR_IOPDEN_Msk               (0x1UL << RCC_APB2ENR_IOPDEN_Pos)  /*!< 0x00000020 */
#define RCC_APB2ENR_IOPDEN                   RCC_APB2ENR_IOPDEN_Msk            /*!< I/O port D clock enable */

这里其实就是
RCC->APB2ENR |= (0x01UL << 5U);
那这代表什么意思呢,可以通过数据手册查看到RCC->APB2ENR寄存器的描述:
在这里插入图片描述
在这里插入图片描述
通过或的方式将位5置1,从而打开GPIOD的时钟。其他端口也是相同道理。不在进行讲解,也可以从系统架构的图中查看到外设挂载于那一个时钟线上。

外设DMA传输配置:MX_DMA_Init();

void MX_DMA_Init(void) 
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();//开启DMA1的时钟
  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

}

值得注意的是DMA的时钟是直接搭载在AHB上的,所以得去RCC_AHBENR寄存器中使能对应的时钟。
在这里插入图片描述
接着就是CubeMx强制配置的DMA1中断DMA1_Channel1_IRQn。所以当ADC采集完成的时间较短时会造成DMA传输完成一次的时间极短,造成单片机一直反复进入中断,所以需要在主函数中写屏蔽该中断。
接着调用HAL_NVIC_SetPriority(),将DMA1的中断配置为最高级。接着HAL_NVIC_EnableIRQ()使能。
至此,MX_DMA_Init()函数内容到此结束。但是没有发现对于ADC部分使用的DMA1具体配置。

外设ADC采集配置:MX_ADC1_Init();

首先该函数对ADC1进行基础配置:

  hadc1.Instance = ADC1;//配置ADC1
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;//因为是多通道,使用扫描模式
  hadc1.Init.ContinuousConvMode = ENABLE;//允许连续采集
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;//软件触发启动
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;//数据右对齐
  hadc1.Init.NbrOfConversion = 7;//一共使用7个通道
  if (HAL_ADC_Init(&hadc1) != HAL_OK)//初始化配置
  {
    Error_Handler();
  }

HAL_ADC_Init()中先对参数进行检测,复位ADC的状态,初始化使用的引脚为浮空输入模式。
调用HAL_ADC_MspInit()函数实现。
该函数先初始化端口时钟。包括ADC1的时钟,然后又把GPIO端口重新初始化一次。并配置引脚。

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(adcHandle->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspInit 0 */

  /* USER CODE END ADC1_MspInit 0 */
    /* ADC1 clock enable */
    __HAL_RCC_ADC1_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**ADC1 GPIO Configuration    
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    PA2     ------> ADC1_IN2
    PB0     ------> ADC1_IN8
    PB1     ------> ADC1_IN9 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);//ADC使用的引脚

    /* ADC1 DMA Init */
    /* ADC1 Init *///这里在进行DMA初始化
    hdma_adc1.Instance = DMA1_Channel1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;//方向:外设到内存
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;//外设地址不递增,因为你一直在ADC家得到采集结果
    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_MEDIUM;//DMA传输的优先级:中级
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)//调用初始化函数配置
    {
      Error_Handler();
    }

    __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);//将DMA与ADC1联系起来

  /* USER CODE BEGIN ADC1_MspInit 1 */

  /* USER CODE END ADC1_MspInit 1 */
  }
}

接着就是将ADC进行相应的配置写入。然后回到
MX_ADC1_Init()函数继续,剩下的内容就是对ADC各个通道进行编号配置对应的采样时间。

  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_0;//通道0
  sConfig.Rank = ADC_REGULAR_RANK_1;//编号为序列1
  sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;//采样周期239.5
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)//写入对应的寄存器
  {
    Error_Handler();
  }

这部分就是通过ADC_SMPRx与ADC_SQRx实现的
在这里插入图片描述
接着就是MX_USART1_UART_Init()对串口进行初始化配置。本篇文章将不会进行讲解。

扫描二维码关注公众号,回复: 9140924 查看本文章

编写代码使能运转:

首先先把DMA1的中断禁止。调用实现:

HAL_NVIC_DisableIRQ(DMA1_Channel1_IRQn);

其实质是调用NVIC_DisableIRQ()函数。
ADC在开始使用前调用自检函数,校准ADC,减少其飘动:HAL_ADCEx_Calibration_Start(&hadc1);
接着就可以开始ADC的DMA传输了:HAL_ADC_Start_DMA(&hadc1,(uint32_t *)AdcValue,7);
AdcValue是装采集结果的数组名,7代表传输的总数。
使能UART发送功能:HAL_UART_AbortTransmit(&huart1);
然后就可以在while中循环采集了:
AdcValue中的值可以直接使用即可,因为DMA已经将ADC采集结果从外设传输至该数组。

内置温度采集

利用下列公式得出温度
温度(°C) = {(V 25 - V SENSE ) / Avg_Slope} + 25
这里:
V 25 = V SENSE 在25 °C时的数值
Avg_Slope = 温度与V SENSE 曲线的平均斜率(单位为mV/ °C 或 μV/ °C)

		temperature = AdcValue[5];
		temperature = temperature/4096*3.3;
		temperature = (1.43 - temperature)/0.0043 + 25;
发布了10 篇原创文章 · 获赞 1 · 访问量 406

猜你喜欢

转载自blog.csdn.net/qq_36561846/article/details/104276096