之前博客中讲解了如何通过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端口时钟使能;由于之前篇文章中使用的引脚如下图所示:
可以很直接的看到我们只是用了单片机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的时钟。其他端口也是相同道理。不在进行讲解,也可以从系统架构的图中查看到外设挂载于那一个时钟线上。
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具体配置。
首先该函数对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()对串口进行初始化配置。本篇文章将不会进行讲解。
首先先把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;