笔记内容 针对RT-Thread官方源码ADC驱动中没有对STM32H7进行支持,本文记录了对原ADC驱动进行修改以适配STM32H7的过程。 1.ADC驱动详解 外设驱动的使用逻辑都大体一致,先是外设初始化,大体包括时钟初始化、I/O初始化、外设初始化,正确初始化后即可通过外设接口控制外设的工作,对于adc来说就是控制adc的采集,返回采集值。 H7的adc可配置为 独立/双重 ,单/多通道,单次/连续,单端/差分,轮询/中断/DMA 采集模式,可以配置的模式比较多,针对需求本文实现了 独立 单通道 单纯 单端的轮询/中断/DMA 采集模式。 2.ADC初始化 RT-Thread使用自动初始化完成ADC的初始化及设备注册,初始化参数从 adc_config[] 中获取。 ADC 初始化结构体参数定义如下(位于adc_config.h): #define ADC1_CONFIG \
{ \
.Instance = ADC1, \
.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4, \
.Init.Resolution = ADC_RESOLUTION_12B, \
.Init.DataAlign = ADC_DATAALIGN_RIGHT, \
.Init.ScanConvMode = DISABLE, \
.Init.EOCSelection = DISABLE, \
.Init.ContinuousConvMode = DISABLE, \
.Init.NbrOfConversion = 1, \
.Init.DiscontinuousConvMode = DISABLE, \
.Init.NbrOfDiscConversion = 0, \
.Init.ExternalTrigConv = ADC_SOFTWARE_START, \
.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE, \
.Init.DMAContinuousRequests = DISABLE, \
}上述参数对于H7来说是不适用的,因此需进行以下修改: #define ADC1_CONFIG \
{ \
.Instance = ADC1, \
.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1, \
.Init.Resolution = ADC_RESOLUTION_16B, \
.Init.ScanConvMode = ADC_SCAN_DISABLE, \
.Init.EOCSelection = ADC_EOC_SINGLE_CONV, \
.Init.LowPowerAutoWait = DISABLE, \
.Init.ContinuousConvMode = DISABLE, \
.Init.NbrOfConversion = 1, \
.Init.DiscontinuousConvMode = DISABLE, \
.Init.ExternalTrigConv = ADC_SOFTWARE_START, \
.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE,\
.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR, \
.Init.Overrun = OVR_DATA_OVERWRITTEN, \
.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE, \
.Init.OversamplingMode = DISABLE, \
}每个参数详解如下: (1) ClockPrescaler:ADC 时钟分频系数选择,系数决定ADC 时钟频率,可选的分频系数为1、2、4 和6 等。 (2) Resolution:配置ADC 的分辨率,可选的分辨率有16 位、12 位、10 位和8 位。 (3) ScanConvMode:可选参数为ENABLE 和DISABLE,配置是否使用扫描。如果是单通道AD 转换使用DISABLE,如果是多通道AD 转换使用ENABLE。 (4) EOCSelection:可选参数为ADC_EOC_SINGLE_CONV 和ADC_EOC_SEQ_CONV ,指定通过轮询和中断来使用EOC 标志或者是EOS 标志进行转换。 (5) LowPowerAutoWait:在低功耗模式下,自动调节ADC 的转换频率。 (6) ContinuousConvMode:可选参数为ENABLE 和DISABLE,配置是启动自动连续转换还是单次转换。 (7) NbrOfConversion:指定AD 规则转换通道数目,最大值为16。 (8) DiscontinuousConvMode:不连续采样模式。一般为禁止模式。 (10) ExternalTrigConv:外部触发选择,一般使用软件自动触发。 (11) ExternalTrigConvEdge:外部触发极性选择,如果使用外部触发,可以选择触发的极性,可选有禁止触发检测、上升沿触发检测、下降沿触发检测以及上升沿和下降沿均可触发检测。 (12) ConversionDataManagement: ADC 转换后的数据处理方式。可以选择DMA 传输,存储在数据寄存器中或者是传输到DFSDM寄存器中。 (13) Overrun:当数据溢出时,可以选择覆盖写入或者是丢弃新的数据。 (14) LeftBitShift:数据左移位数,一般用于数据对齐。最多可支持左移15 位。 (16) OversamplingMode:是否使能过采样模式。 3.ADC 转换 RT-Thread使用stm32_adc_enabled 与 stm32_adc_get_channel 两个函数完成转换,stm32_adc_enabled中进行ADC的使能和关闭操作,在实际测试过程中可不使用, stm32_adc_get_channel 完成ADC 通道配置、轮询检查转换状态、获取转换数据。 ADC_ChannelConfTypeDef 结构体: typedef struct {
uint32_t Channel; /*!< ADC 转换通道*/
uint32_t Rank; /*!< ADC 转换顺序 */
uint32_t SamplingTime; /*!< ADC 采样周期 */
uint32_t SingleDiff; /*!< 输入信号线的类型*/
uint32_t OffsetNumber; /*!< 采用偏移量的通道 */
uint32_t Offset; /*!< 偏移量 */
FunctionalState OffsetRightShift; /*!< 数据右移位数*/
FunctionalState OffsetSignedSaturation; /*!< 转换数据格式为有符号位数据 */
} ADC_ChannelConfTypeDef;(1) Channel:ADC 转换通道。可以选择0~19。 (2) Rank:ADC 转换顺序,可以选择1~16。 (3) SamplingTime:ADC 的采样周期。 (4) SingleDiff:选择ADC 输入信号的类型。可以选择差分或者是单线。如果选择差分模式,则需要将相应的ADC_INNx 连接到相应的信号线。 (5) OffsetNumber:使用偏移量的通道。当选择第一个通道时,则第一个通道转换的值需要减去一个偏移量,才能得到最终结果。 (6) Offset:偏移量。根据ADC 的分辨率不同,支持的最大偏移量也不同,例如分辨率是16bit,,最大的偏移量为0xFFFF。 (7) OffsetRightShift:采样值进行右移的位数。 (8) OffsetSignedSaturation:是否使能ADC 采样值的最高位为符号位。
因此对原驱动进行更改 (1)修改通道限制(位于stm32_get_adc_value函数) #if defined(SOC_SERIES_STM32F1)
if (channel <= 17)
#elif defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || defined(SOC_SERIES_STM32F7) \
|| defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32G0)
if (channel <= 18)
#elif defined(SOC_SERIES_STM32H7) //@dn 2020 1020
if (channel <= 19)
#endif
{
/* set stm32 ADC channel */
ADC_ChanConf.Channel = stm32_adc_get_channel(channel);
}
else
{
#if defined(SOC_SERIES_STM32F1)
LOG_E("ADC channel must be between 0 and 17.");
#elif defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || defined(SOC_SERIES_STM32F7) \
|| defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32G0)
LOG_E("ADC channel must be between 0 and 18.");
#elif defined(SOC_SERIES_STM32H7) //@dn 2020 1020
LOG_E("ADC channel must be between 0 and 19.");
#endif
return -RT_ERROR;
}(2)修改转换顺序赋值(位于stm32_get_adc_value函数) 原代码:
ADC_ChanConf.Rank = 1;
?
修改后:
ADC_ChanConf.Rank = ADC_REGULAR_RANK_1;//@dn 2020 1020(3)加入H7支持,赋值采样周期等 if defined(SOC_SERIES_STM32F0)
ADC_ChanConf.SamplingTime = ADC_SAMPLETIME_71CYCLES_5;
#elif defined(SOC_SERIES_STM32F1)
ADC_ChanConf.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;
#elif defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || defined(SOC_SERIES_STM32F7)
ADC_ChanConf.SamplingTime = ADC_SAMPLETIME_112CYCLES;
#elif defined(SOC_SERIES_STM32L4)
ADC_ChanConf.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
#elif defined(SOC_SERIES_STM32H7) //@dn 2020 1020
ADC_ChanConf.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;
#endif
#if defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32H7) //@dn 2020 1020
ADC_ChanConf.Offset = 0;
#endif
#if defined (SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32H7) //@dn 2020 1020
ADC_ChanConf.OffsetNumber = ADC_OFFSET_NONE;
ADC_ChanConf.SingleDiff = ADC_SINGLE_ENDED;
#endif3.1 轮询采集 轮询采集调用检查EOC/EOS 置位的函数 HAL_ADC_PollForConversion(),若进行多通道采集,则可多次调用该函数后读取数据。 HAL_ADC_Start(stm32_adc_handler);
/* Wait for the ADC to convert */
HAL_ADC_PollForConversion(stm32_adc_handler, 100);
/* get ADC value */
*value = (rt_uint32_t)HAL_ADC_GetValue(stm32_adc_handler);3.2 中断采集 使能ADC中断后,转换完成后调用HAL_ADC_ConvCpltCallback函数,在函数中标志转换完成,检测转换完成后读取数据即可。若进行多通道采集,则可多次采集。 {
if (stm32_adc_handler->Instance == ADC1)
{
HAL_NVIC_SetPriority(ADC_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn);
irq_adc_num = 1;
}
else if (stm32_adc_handler->Instance == ADC2)
{
HAL_NVIC_SetPriority(ADC_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn);
irq_adc_num = 2;
}
else
{
HAL_NVIC_SetPriority(ADC3_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(ADC3_IRQn);
}
?
HAL_ADC_Start_IT(stm32_adc_handler); //使能ADC中断
HAL_ADC_Start(stm32_adc_handler); //软件触发ADC采样
?
for (rt_uint8_t i = 20; i <= 0; i--)
{
if (adc_convert_stat)
{
adc_convert_stat = 0;
break;
}
else
{
if (i <= 1)
{
return RT_ERROR;
}
else
{
rt_thread_delay(5);
}
}
}
rt_thread_delay(5);
*value = (rt_uint32_t)HAL_ADC_GetValue(stm32_adc_handler);
HAL_ADC_Stop_IT(stm32_adc_handler);
}
?
void ADC_IRQHandler(void)
{
rt_interrupt_enter();
?
switch (irq_adc_num)
{
#ifdef BSP_USING_ADC1
case 1:
HAL_ADC_IRQHandler(&stm32_adc_obj[ADC1_INDEX].ADC_Handler);
break;
#endif
#ifdef BSP_USING_ADC2
case 2:
HAL_ADC_IRQHandler(&stm32_adc_obj[ADC2_INDEX].ADC_Handler);
break;
#endif
default:
HAL_ADC_IRQHandler(&stm32_adc_obj[ADC1_INDEX].ADC_Handler);
break;
}
?
rt_interrupt_leave();
}
void ADC3_IRQHandler(void)
{
rt_interrupt_enter();
?
HAL_ADC_IRQHandler(&stm32_adc_obj[ADC3_INDEX].ADC_Handler);
?
rt_interrupt_leave();
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *AdcHandle)
{
adc_convert_stat = 1;
}3.3 DMA采集ADC DMA配置参数,配置DMA通道等参数。 #define ADC1_DMA_CONFIG \
{ \
.Instance = DMA1_Stream1, \
.Init.Request = DMA_REQUEST_ADC1, \
.Init.Direction = DMA_PERIPH_TO_MEMORY, \
.Init.PeriphInc = DMA_PINC_DISABLE, \
.Init.MemInc = DMA_MINC_ENABLE, \
.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD, \
.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD, \
.Init.Mode = DMA_CIRCULAR, \
.Init.Priority = DMA_PRIORITY_LOW, \
.Init.FIFOMode = DMA_FIFOMODE_DISABLE, \
} DMA初始化,需要注意的地方是:stm32_adc_obj[ i].ADC_Handler.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;[ i],ADC选用DMA的传输方式。 [ i]#ifdef ADC_USING_DMA
__HAL_RCC_DMA1_CLK_ENABLE();
?
if (HAL_DMA_Init(&adc_dma_config[ i]) != HAL_OK)
{
LOG_E("%s dma init failed", name_buf);
result = -RT_ERROR;
}
LOG_D("%s dma init success.", name_buf);
?
__HAL_LINKDMA(&stm32_adc_obj[ i].ADC_Handler, DMA_Handle, adc_dma_config[ i]);
?
stm32_adc_obj[ i].ADC_Handler.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
#endif使用DMA读取数据,下面的代码为单通道的读取方式,若使用多通道扫描读取,可采用下面的方式多次采集,此外还可以通过定义数据存储数组,打开ADC的ScanConvMode扫描选项、配置扫描通道数NbrOfConversion切对每个通道进行初始化,之后采集的多个数据将按照通道配置的采集顺序存储搭配定义的数组中。 __IO uint16_t ADC_ConvertedValue_DMA;
{
HAL_ADC_Start_DMA(stm32_adc_handler, (uint32_t *)&ADC_ConvertedValue_DMA, 1);
?
rt_thread_delay(1);
?
SCB_InvalidateDCache_by_Addr((uint32_t *)&ADC_ConvertedValue_DMA, sizeof(ADC_ConvertedValue_DMA));
?
*value = ADC_ConvertedValue_DMA;
}补充由于作者的水平有限,本文可能存在一些描述不正确或存在错误的问题,若有错误,劳请告知,不胜感激。
|