华大 MCU 之四 使用问题记录
??在最近使用華大 MCU 時(shí),遇到了不少坑,這里記錄一下,以方便在以后升級(jí)驅(qū)動(dòng)庫(kù)!其中,有些問題僅僅是在由 ST 切換到 華大之后不太適于的問題,有些是驅(qū)動(dòng)庫(kù)的更改問題!
??如果您發(fā)現(xiàn)我說的問題是由于我還沒完全了解華大 MCU 造成的,歡迎在評(píng)論區(qū)指出。亦或是您有更好的見解也歡迎給與指點(diǎn)。
進(jìn)步始于交流,收獲源于分享,不勝感激!
中斷向量偏移
??在實(shí)際項(xiàng)目中,在線升級(jí)一般是必須的。在以前 ST 的片子中,IAP + APP 的程序框架是個(gè)很不錯(cuò)的選擇。該程序框架中,需要配置 APP 的中斷向量表偏移(中斷向量表偏移寄存器),然而,驅(qū)動(dòng)庫(kù)并沒有給出配置接口。因此,需要自己來實(shí)現(xiàn):
中斷弱函數(shù)
??驅(qū)動(dòng)庫(kù)提供了一個(gè)名為 hc32f46x_interrupts.c/h 的文件,該文件將所有的中斷進(jìn)行了統(tǒng)一的處理,然后以弱函數(shù)的形式開發(fā)對(duì)外接口(hc32f46x_it.c 中定義的函數(shù)均為 hc32f46x_interrupts.c/h 中聲明的弱函數(shù)接口)。但是,驅(qū)動(dòng)庫(kù)的這種弱函數(shù)的使用并不是規(guī)范(可以說不正確,導(dǎo)致自己定義的同名函數(shù)根本無效)。下面我們以 ST HAL 庫(kù)最為對(duì)比來說明一下。
??在使用 GUN 編譯器的時(shí)候,兩者均使用 __attribute__((weak)) 關(guān)鍵字,然而,在使用 ARM 自家編譯器時(shí),華大的驅(qū)動(dòng)庫(kù)仍然使用 __attribute__((weak)) 關(guān)鍵字,而 ST HAL 庫(kù)則會(huì)使用 ARM 自家的 __weak 關(guān)鍵字。
??那么,為啥說華大驅(qū)動(dòng)庫(kù)的這種使用是不正確的呢?這主要是由于 __attribute__((weak)) 關(guān)鍵字的原因。使用 __attribute__((weak)) 聲明的函數(shù),對(duì)于所有實(shí)現(xiàn)的實(shí)體都將被編譯器認(rèn)為為弱函數(shù),不管我們定義多少個(gè)接口,都沒有用。具體見博文 ARM 之十一__weak 和 attribute((weak)) 關(guān)鍵字的使用。
具體的解決方法有以下兩個(gè):
缺點(diǎn)就是,對(duì)其他編譯器可能并不適用!
中斷的使用
??在 hc32f46x_interrupts.c/h 的文件中定義的結(jié)構(gòu)體中對(duì)中斷區(qū)分為三大部分,這就導(dǎo)致在我們使用 en_result_t enIrqRegistration(const stc_irq_regi_conf_t *pstcIrqRegiConf) 進(jìn)行中斷注冊(cè)的時(shí)候需要注意分配的中斷號(hào)。如下圖所示:
??下面我以我最近使用的 ADC 來看看中斷的具體配置。首先,hc32f46x_interrupts.c/h 中對(duì)于 ADC 的中斷默認(rèn)分配了中斷號(hào) 142,如下:
/*********************************************************************************** \brief Int No.142 share IRQ handler********************************************************************************/ void IRQ142_Handler(void) {uint32_t u32VSSEL142 = M4_INTC->VSSEL142;uint16_t u16Tmp = 0u;/* ADC unit.1 seq. A */if (1ul == bM4_ADC1_ICR_EOCAIEN){if ((1ul == bM4_ADC1_ISR_EOCAF) && (u32VSSEL142 & BIT_MASK_00)){ADC1A_IrqHandler();}}/* ADC unit.1 seq. B */if (1ul == bM4_ADC1_ICR_EOCBIEN){if ((1ul == bM4_ADC1_ISR_EOCBF) && (u32VSSEL142 & BIT_MASK_01)){ADC1B_IrqHandler();}}/* ADC unit.1 seq. A */u16Tmp = M4_ADC1->AWDSR0;if (1ul == bM4_ADC1_AWDCR_AWDIEN){if (((1ul == bM4_ADC1_AWDSR1_AWDF16) || (u16Tmp)) && (u32VSSEL142 & BIT_MASK_02)){ADC1ChCmp_IrqHandler();}}/* ADC unit.1 seq. cmp */if (1ul == bM4_ADC1_AWDCR_AWDIEN){if (((1ul == bM4_ADC1_AWDSR1_AWDF16) || (u16Tmp)) && (u32VSSEL142 & BIT_MASK_03)){ADC1SeqCmp_IrqHandler();}}/* ADC unit.2 seq. A */if (1ul == bM4_ADC2_ICR_EOCAIEN){if ((1ul == bM4_ADC2_ISR_EOCAF) && (u32VSSEL142 & BIT_MASK_04)){ADC2A_IrqHandler();}}/* ADC unit.2 seq. B */if (1ul == bM4_ADC2_ICR_EOCBIEN){if ((1ul == bM4_ADC2_ISR_EOCBF) && (u32VSSEL142 & BIT_MASK_05)){ADC2B_IrqHandler();}}/* ADC unit.2 seq. A */if (1ul == bM4_ADC2_AWDCR_AWDIEN){if ((M4_ADC2->AWDSR0 & 0x1FFu) && (u32VSSEL142 & BIT_MASK_06)){ADC2ChCmp_IrqHandler();}}/* ADC unit.2 seq. cmp */if (1ul == bM4_ADC2_AWDCR_AWDIEN){if ((M4_ADC2->AWDSR0 & 0x1FFu) && (u32VSSEL142 & BIT_MASK_07)){ADC2SeqCmp_IrqHandler();}} }其中的 ADC1A_IrqHandler(); 等都是放給用戶的弱函數(shù)接口。我們的使用方法基本就是,給 ADC 配置 142 號(hào)中斷,然后實(shí)現(xiàn)對(duì)應(yīng)的弱函數(shù)實(shí)體就應(yīng)該可以了。下面是驅(qū)動(dòng)庫(kù)的示例給出的代碼:
static void AdcIrqConfig(void) {en_result_t enIrqRegResult;stc_irq_regi_conf_t stcAdcIrqCfg;/* Config ADC1 interrupt. */stcAdcIrqCfg.enIntSrc = INT_ADC1_EOCA;stcAdcIrqCfg.enIRQn = Int117_IRQn;stcAdcIrqCfg.pfnCallback = &ADC1A_IrqHandler;enIrqRegResult = enIrqRegistration(&stcAdcIrqCfg);if (Ok == enIrqRegResult){NVIC_ClearPendingIRQ(stcAdcIrqCfg.enIRQn);NVIC_SetPriority(stcAdcIrqCfg.enIRQn, DDL_IRQ_PRIORITY_03);NVIC_EnableIRQ(stcAdcIrqCfg.enIRQn);}/* Config ADC2 interrupt, use sharing interrupt. */stcAdcIrqCfg.enIntSrc = INT_ADC2_EOCA;stcAdcIrqCfg.enIRQn = Int142_IRQn;enShareIrqEnable(stcAdcIrqCfg.enIntSrc);NVIC_ClearPendingIRQ(stcAdcIrqCfg.enIRQn);NVIC_SetPriority(stcAdcIrqCfg.enIRQn, DDL_IRQ_PRIORITY_04);NVIC_EnableIRQ(stcAdcIrqCfg.enIRQn); }注意上面的 enShareIrqEnable(stcAdcIrqCfg.enIntSrc); 要不然會(huì)死的很慘哦!從上面的示例可以看出,對(duì)于上面說的第三部分的中斷使用,需要使用 enShareIrqEnable(stcAdcIrqCfg.enIntSrc); 而不是 enIrqRegistration(&stcAdcIrqCfg);。
中斷反注冊(cè)
??由于我們使用中斷(部分)時(shí)需要在 hc32f46x_interrupts.c/h 的文件中定義的結(jié)構(gòu)體中進(jìn)行注冊(cè),如果想第二次注冊(cè)(例如,更改回調(diào)接口)時(shí),必須要反注冊(cè),否則不能正常注冊(cè)。這個(gè)問題在我們使用 IAP 和 APP 兩個(gè)程序時(shí)尤為明顯!
en_result_t enIrqRegistration(const stc_irq_regi_conf_t *pstcIrqRegiConf) {// todo, assert ...stc_intc_sel_field_t *stcIntSel;en_result_t enRet = Ok;//DDL_ASSERT(NULL != pstcIrqRegiConf->pfnCallback);DDL_ASSERT(IS_NULL_POINT(pstcIrqRegiConf->pfnCallback));/* IRQ032~127 whether out of range */if (((((pstcIrqRegiConf->enIntSrc/32)*6 + 32) > pstcIrqRegiConf->enIRQn) || \(((pstcIrqRegiConf->enIntSrc/32)*6 + 37) < pstcIrqRegiConf->enIRQn)) && \(pstcIrqRegiConf->enIRQn >= 32)){enRet = ErrorInvalidParameter;}else{stcIntSel = (stc_intc_sel_field_t *)((uint32_t)(&M4_INTC->SEL0) + \(4u * pstcIrqRegiConf->enIRQn));if (0x1FFu == stcIntSel->INTSEL) /* 如果已經(jīng)初始化過,這里將不能再初始化 */{stcIntSel->INTSEL = pstcIrqRegiConf->enIntSrc;IrqHandler[pstcIrqRegiConf->enIRQn] = pstcIrqRegiConf->pfnCallback;}else{enRet = ErrorUninitialized;}}return enRet; }具體方法就是使用 enIrqResign 函數(shù)進(jìn)行重新標(biāo)記!
DMA 配置
??根據(jù)手冊(cè),使用 DMA 時(shí)需要先寫寄存器將 DMA 控制器使能,使能方法是寫 DMA 使能寄存器 DMA_EN.EN 位。 如下圖所示:
我試了一下,不先使能貌似也沒法發(fā)現(xiàn)啥問題啊!不知道為啥!?
DMA 無法獲取當(dāng)前傳出數(shù)據(jù)長(zhǎng)度
??先來說一下需求:在串口驅(qū)動(dòng)中,串口的接收使用 DMA 來實(shí)現(xiàn),DMA 配置為循環(huán)模式,在指定緩沖區(qū)中循環(huán)存放收到的數(shù)據(jù),通過讀寫指針來標(biāo)記數(shù)據(jù)的讀和寫位置。
??然而,驅(qū)動(dòng)庫(kù)中 DMA 接口并沒有能獲取當(dāng)前 DMA 傳輸了多少字節(jié)的接口!!!無奈只能選擇修改驅(qū)動(dòng)庫(kù),添加一些指定的接口,如下圖所示:
??后來,為了不修改驅(qū)動(dòng)庫(kù),我直接把接口放到了自己的驅(qū)動(dòng)里面,然后使用寄存器直接讀取:M4DMA1->MONRPT0_f.DRPT,這樣的話,需要注意與通道號(hào)的對(duì)應(yīng)關(guān)系,如 MONRPT0 即通道 0。
DMA 中斷的使用
??在華大的 DMA 中,中斷默認(rèn)都是開啟的,這點(diǎn)在配置 DMA 的時(shí)候需要特殊注意。我們需要使用 DMA 的屏蔽中斷寄存器來屏蔽不使用的中斷。如下圖示:
在實(shí)際寫代碼時(shí),需要調(diào)用 en_result_t DMA_DisableIrq(M4_DMA_TypeDef* pstcDmaReg, uint8_t u8Ch, en_dma_irq_sel_t enIrqSel); 來關(guān)閉不需要的中中斷。例如,DMA 的塊傳輸完成中斷 和 傳輸完成中斷 通常不會(huì)一起使用!這點(diǎn)對(duì)于用慣了 ST MCU 的人來說需要特殊注意!
看門狗
??在華大 MCU 中,看門狗也是有兩個(gè):SWDT 和 WDT,看手冊(cè)的介紹應(yīng)該是分別對(duì)應(yīng)于 ST 的 IWDG 和 WWDG。在實(shí)際使用中,多數(shù)情況下我們需要使用 SWDT。
??目前,SWDT 的配置可以必須在庫(kù)文件 hc32f46x_icg.h 中進(jìn)行配置(WDT 可以使用 hc32f46x_wdt.c/h 中進(jìn)行配置),然后將 hc32f46x_icg.c 包含到自己的項(xiàng)目中,否則配置依舊無效!在更改了驅(qū)動(dòng)庫(kù)源碼之后,在更新驅(qū)動(dòng)庫(kù)時(shí)需要注意!
??此外,上面這種配置方法會(huì)間接導(dǎo)致一個(gè)問題:由于我們的程序分為 IAP 和 APP 兩部分。看門狗的配置必須放到 IAP 中,且 APP 中不能再包含該文件,否則在調(diào)試燒寫時(shí)會(huì)報(bào)錯(cuò)!這是由于 APP 位置的偏移地址導(dǎo)致的!
enumeration value is out of “int” range
在使用 MDK-ARM 編譯華大驅(qū)動(dòng)庫(kù)時(shí),會(huì)提示 warning: enumeration value is out of "int" range,這是由于驅(qū)動(dòng)庫(kù)中(hc32f46x_exint_nmi_swi.h、hc32f46x_interrupts.h)對(duì)于枚舉類型的值超過了 enum 中的枚舉值默認(rèn)為 int 類型的限制:
我們可以通過以上修改以消除警告!
總結(jié)
以上是生活随笔為你收集整理的华大 MCU 之四 使用问题记录的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 之二 Linux 多线程
- 下一篇: 华大 MCU 之五 SPI 从机 DMA