筆記內(nèi)容 針對RT-Thread官方源碼ADC驅(qū)動中沒有對STM32H7進(jìn)行支持,本文記錄了對原ADC驅(qū)動進(jìn)行修改以適配STM32H7的過程。 1.ADC驅(qū)動詳解 外設(shè)驅(qū)動的使用邏輯都大體一致,先是外設(shè)初始化,大體包括時鐘初始化、I/O初始化、外設(shè)初始化,正確初始化后即可通過外設(shè)接口控制外設(shè)的工作,對于adc來說就是控制adc的采集,返回采集值。 H7的adc可配置為 獨(dú)立/雙重 ,單/多通道,單次/連續(xù),單端/差分,輪詢/中斷/DMA 采集模式,可以配置的模式比較多,針對需求本文實(shí)現(xiàn)了 獨(dú)立 單通道 單純 單端的輪詢/中斷/DMA 采集模式。 2.ADC初始化 RT-Thread使用自動初始化完成ADC的初始化及設(shè)備注冊,初始化參數(shù)從 adc_config[] 中獲取。 ADC 初始化結(jié)構(gòu)體參數(shù)定義如下(位于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, \
}上述參數(shù)對于H7來說是不適用的,因此需進(jìn)行以下修改: #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, \
}每個參數(shù)詳解如下: (1) ClockPrescaler:ADC 時鐘分頻系數(shù)選擇,系數(shù)決定ADC 時鐘頻率,可選的分頻系數(shù)為1、2、4 和6 等。 (2) Resolution:配置ADC 的分辨率,可選的分辨率有16 位、12 位、10 位和8 位。 (3) ScanConvMode:可選參數(shù)為ENABLE 和DISABLE,配置是否使用掃描。如果是單通道AD 轉(zhuǎn)換使用DISABLE,如果是多通道AD 轉(zhuǎn)換使用ENABLE。 (4) EOCSelection:可選參數(shù)為ADC_EOC_SINGLE_CONV 和ADC_EOC_SEQ_CONV ,指定通過輪詢和中斷來使用EOC 標(biāo)志或者是EOS 標(biāo)志進(jìn)行轉(zhuǎn)換。 (5) LowPowerAutoWait:在低功耗模式下,自動調(diào)節(jié)ADC 的轉(zhuǎn)換頻率。 (6) ContinuousConvMode:可選參數(shù)為ENABLE 和DISABLE,配置是啟動自動連續(xù)轉(zhuǎn)換還是單次轉(zhuǎn)換。 (7) NbrOfConversion:指定AD 規(guī)則轉(zhuǎn)換通道數(shù)目,最大值為16。 (8) DiscontinuousConvMode:不連續(xù)采樣模式。一般為禁止模式。 (10) ExternalTrigConv:外部觸發(fā)選擇,一般使用軟件自動觸發(fā)。 (11) ExternalTrigConvEdge:外部觸發(fā)極性選擇,如果使用外部觸發(fā),可以選擇觸發(fā)的極性,可選有禁止觸發(fā)檢測、上升沿觸發(fā)檢測、下降沿觸發(fā)檢測以及上升沿和下降沿均可觸發(fā)檢測。 (12) ConversionDataManagement: ADC 轉(zhuǎn)換后的數(shù)據(jù)處理方式。可以選擇DMA 傳輸,存儲在數(shù)據(jù)寄存器中或者是傳輸?shù)紻FSDM寄存器中。 (13) Overrun:當(dāng)數(shù)據(jù)溢出時,可以選擇覆蓋寫入或者是丟棄新的數(shù)據(jù)。 (14) LeftBitShift:數(shù)據(jù)左移位數(shù),一般用于數(shù)據(jù)對齊。最多可支持左移15 位。 (16) OversamplingMode:是否使能過采樣模式。 3.ADC 轉(zhuǎn)換 RT-Thread使用stm32_adc_enabled 與 stm32_adc_get_channel 兩個函數(shù)完成轉(zhuǎn)換,stm32_adc_enabled中進(jìn)行ADC的使能和關(guān)閉操作,在實(shí)際測試過程中可不使用, stm32_adc_get_channel 完成ADC 通道配置、輪詢檢查轉(zhuǎn)換狀態(tài)、獲取轉(zhuǎn)換數(shù)據(jù)。 ADC_ChannelConfTypeDef 結(jié)構(gòu)體: typedef struct {
uint32_t Channel; /*!< ADC 轉(zhuǎn)換通道*/
uint32_t Rank; /*!< ADC 轉(zhuǎn)換順序 */
uint32_t SamplingTime; /*!< ADC 采樣周期 */
uint32_t SingleDiff; /*!< 輸入信號線的類型*/
uint32_t OffsetNumber; /*!< 采用偏移量的通道 */
uint32_t Offset; /*!< 偏移量 */
FunctionalState OffsetRightShift; /*!< 數(shù)據(jù)右移位數(shù)*/
FunctionalState OffsetSignedSaturation; /*!< 轉(zhuǎn)換數(shù)據(jù)格式為有符號位數(shù)據(jù) */
} ADC_ChannelConfTypeDef;(1) Channel:ADC 轉(zhuǎn)換通道。可以選擇0~19。 (2) Rank:ADC 轉(zhuǎn)換順序,可以選擇1~16。 (3) SamplingTime:ADC 的采樣周期。 (4) SingleDiff:選擇ADC 輸入信號的類型。可以選擇差分或者是單線。如果選擇差分模式,則需要將相應(yīng)的ADC_INNx 連接到相應(yīng)的信號線。 (5) OffsetNumber:使用偏移量的通道。當(dāng)選擇第一個通道時,則第一個通道轉(zhuǎn)換的值需要減去一個偏移量,才能得到最終結(jié)果。 (6) Offset:偏移量。根據(jù)ADC 的分辨率不同,支持的最大偏移量也不同,例如分辨率是16bit,,最大的偏移量為0xFFFF。 (7) OffsetRightShift:采樣值進(jìn)行右移的位數(shù)。 (8) OffsetSignedSaturation:是否使能ADC 采樣值的最高位為符號位。
因此對原驅(qū)動進(jìn)行更改 (1)修改通道限制(位于stm32_get_adc_value函數(shù)) #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)修改轉(zhuǎn)換順序賦值(位于stm32_get_adc_value函數(shù)) 原代碼:
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 輪詢采集 輪詢采集調(diào)用檢查EOC/EOS 置位的函數(shù) HAL_ADC_PollForConversion(),若進(jìn)行多通道采集,則可多次調(diào)用該函數(shù)后讀取數(shù)據(jù)。 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中斷后,轉(zhuǎn)換完成后調(diào)用HAL_ADC_ConvCpltCallback函數(shù),在函數(shù)中標(biāo)志轉(zhuǎn)換完成,檢測轉(zhuǎn)換完成后讀取數(shù)據(jù)即可。若進(jìn)行多通道采集,則可多次采集。 {
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); //軟件觸發(fā)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配置參數(shù),配置DMA通道等參數(shù)。 #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讀取數(shù)據(jù),下面的代碼為單通道的讀取方式,若使用多通道掃描讀取,可采用下面的方式多次采集,此外還可以通過定義數(shù)據(jù)存儲數(shù)組,打開ADC的ScanConvMode掃描選項(xiàng)、配置掃描通道數(shù)NbrOfConversion切對每個通道進(jìn)行初始化,之后采集的多個數(shù)據(jù)將按照通道配置的采集順序存儲搭配定義的數(shù)組中。 __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;
}補(bǔ)充由于作者的水平有限,本文可能存在一些描述不正確或存在錯誤的問題,若有錯誤,勞請告知,不勝感激。
|