Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换

2022-10-28,,,,

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的ADC模数转换.doc》

下载本文的Word格式文档,以方便收藏与打印。