打印
[综合信息]

使用问题记录

[复制链接]
7883|18
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tfqi|  楼主 | 2021-10-5 18:41 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
中断向量偏移

  在实际项目中,在线升级一般是必须的。在以前 ST 的片子中,IAP + APP 的程序框架是个很不错的选择。该程序框架中,需要配置 APP 的中断向量表偏移(中断向量表偏移寄存器),然而,驱动库并没有给出配置接口。因此,需要自己来实现:

使用特权

评论回复
沙发
tfqi|  楼主 | 2021-10-5 18:43 | 只看该作者
中断弱函数
  驱动库提供了一个名为 hc32f46x_interrupts.c/h 的文件,该文件将所有的中断进行了统一的处理,然后以弱函数的形式开发对外接口(hc32f46x_it.c 中定义的函数均为 hc32f46x_interrupts.c/h 中声明的弱函数接口)。但是,驱动库的这种弱函数的使用并不是规范(可以说不正确,导致自己定义的同名函数根本无效)。下面我们以 ST HAL 库最为对比来说明一下。

首先,华大驱动库和 ST HAL 库弱函数所使用的的弱函数关键字是有区别的,如下对比图所示:


在使用 GUN 编译器的时候,两者均使用 __attribute__((weak)) 关键字,然而,在使用 ARM 自家编译器时,华大的驱动库仍然使用 __attribute__((weak)) 关键字,而 ST HAL 库则会使用 ARM 自家的 __weak 关键字。
再来看看两者对于弱函数的使用方法,如下对比图所示:


 那么,为啥说华大驱动库的这种使用是不正确的呢?这主要是由于 __attribute__((weak)) 关键字的原因。使用 __attribute__((weak)) 声明的函数,对于所有实现的实体都将被编译器认为为弱函数,不管我们定义多少个接口,都没有用。具体见博文 ARM 之十一__weak 和 attribute((weak)) 关键字的使用。

具体的解决方法有以下两个:

修改驱动库改为 ST HAL 库的使用方式。缺点就是需要修改驱动库,不方便后续升级驱动库。
如下配置 MDK-ARM 开发环境:

[color=rgba(0, 0, 0, 0.75)]缺点就是,对其他编译器可能并不适用!

使用特权

评论回复
板凳
tfqi|  楼主 | 2021-10-5 18:44 | 只看该作者
中断的使用
  在 hc32f46x_interrupts.c/h 的文件中定义的结构体中对中断区分为三大部分,这就导致在我们使用 en_result_t enIrqRegistration(const stc_irq_regi_conf_t *pstcIrqRegiConf) 进行中断注册的时候需要注意分配的中断号。如下图所示:


第一部分应该是 Cortex-M 核的中断,他这里处理后,我们就可以自己定义或函数的实体(忽略上面说的问题)来使用中断!
第二步部分应该是留给用户自己随便定义的
第三部分应该是对于常用的外设,可能为了方便大家使用,他给处理了一层(固定好了中断号)。但是,这里仍然需要我们配置外设中断。当然,我们可以不使用这部分,仍然使用第二部分来自定义。


使用特权

评论回复
地板
tfqi|  楼主 | 2021-10-5 18:44 | 只看该作者
  下面我以我最近使用的 ADC 来看看中断的具体配置。首先,hc32f46x_interrupts.c/h 中对于 ADC 的中断默认分配了中断号 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(); 等都是放给用户的弱函数接口。我们的使用方法基本就是,给 ADC 配置 142 号中断,然后实现对应的弱函数实体就应该可以了。下面是驱动库的示例给出的代码:


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); 要不然会死的很惨哦!从上面的示例可以看出,对于上面说的第三部分的中断使用,需要使用 enShareIrqEnable(stcAdcIrqCfg.enIntSrc); 而不是 enIrqRegistration(&stcAdcIrqCfg);。



使用特权

评论回复
5
tfqi|  楼主 | 2021-10-5 18:45 | 只看该作者
中断反注册

  由于我们使用中断(部分)时需要在 hc32f46x_interrupts.c/h 的文件中定义的结构体中进行注册,如果想第二次注册(例如,更改回调接口)时,必须要反注册,否则不能正常注册。这个问题在我们使用 IAP 和 APP 两个程序时尤为明显!


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)        /* 如果已经初始化过,这里将不能再初始化 */

        {

            stcIntSel->INTSEL = pstcIrqRegiConf->enIntSrc;

            IrqHandler[pstcIrqRegiConf->enIRQn] = pstcIrqRegiConf->pfnCallback;

        }

        else

        {

            enRet = ErrorUninitialized;

        }

    }

    return enRet;

}



具体方法就是使用 enIrqResign 函数进行重新标记!

使用特权

评论回复
6
tfqi|  楼主 | 2021-10-5 18:46 | 只看该作者
DMA 配置

  根据手册,使用 DMA 时需要先写寄存器将 DMA 控制器使能,使能方法是写 DMA 使能寄存器 DMA_EN.EN 位。 如下图所示:






我试了一下,不先使能貌似也没法发现啥问题啊!不知道为啥!?







使用特权

评论回复
7
tfqi|  楼主 | 2021-10-5 18:46 | 只看该作者
DMA 无法获取当前传出数据长度
  先来说一下需求:在串口驱动中,串口的接收使用 DMA 来实现,DMA 配置为循环模式,在指定缓冲区中循环存放收到的数据,通过读写指针来标记数据的读和写位置。
  然而,驱动库中 DMA 接口并没有能获取当前 DMA 传输了多少字节的接口!!!无奈只能选择修改驱动库,添加一些指定的接口,如下图所示:


  后来,为了不修改驱动库,我直接把接口放到了自己的驱动里面,然后使用寄存器直接读取:M4DMA1->MONRPT0_f.DRPT,这样的话,需要注意与通道号的对应关系,如 MONRPT0 即通道 0。

使用特权

评论回复
8
tfqi|  楼主 | 2021-10-5 18:47 | 只看该作者
DMA 中断的使用

  在华大的 DMA 中,中断默认都是开启的,这点在配置 DMA 的时候需要特殊注意。我们需要使用 DMA 的屏蔽中断寄存器来屏蔽不使用的中断。如下图示:


在实际写代码时,需要调用 en_result_t DMA_DisableIrq(M4_DMA_TypeDef* pstcDmaReg, uint8_t u8Ch, en_dma_irq_sel_t enIrqSel); 来关闭不需要的中中断。例如,DMA 的块传输完成中断 和 传输完成中断 通常不会一起使用!这点对于用惯了 ST MCU 的人来说需要特殊注意!



使用特权

评论回复
9
tfqi|  楼主 | 2021-10-5 18:48 | 只看该作者
看门狗
  在华大 MCU 中,看门狗也是有两个:SWDT 和 WDT,看手册的介绍应该是分别对应于 ST 的 IWDG 和 WWDG。在实际使用中,多数情况下我们需要使用 SWDT。

  目前,SWDT 的配置可以必须在库文件 hc32f46x_icg.h 中进行配置(WDT 可以使用 hc32f46x_wdt.c/h 中进行配置),然后将 hc32f46x_icg.c 包含到自己的项目中,否则配置依旧无效!在更改了驱动库源码之后,在更新驱动库时需要注意!


  此外,上面这种配置方**间接导致一个问题:由于我们的程序分为 IAP 和 APP 两部分。看门狗的配置必须放到 IAP 中,且 APP 中不能再包含该文件,否则在调试烧写时会报错!这是由于 APP 位置的偏移地址导致的!

使用特权

评论回复
10
tfqi|  楼主 | 2021-10-5 18:49 | 只看该作者
enumeration value is out of “int” range
在使用 MDK-ARM 编译华大驱动库时,会提示 warning: enumeration value is out of "int" range,这是由于驱动库中(hc32f46x_exint_nmi_swi.h、hc32f46x_interrupts.h)对于枚举类型的值超过了 enum 中的枚举值默认为 int 类型的限制:


我们可以通过以上修改以消除警告!

使用特权

评论回复
11
zchong| | 2021-10-8 07:36 | 只看该作者
不错

使用特权

评论回复
12
zchong| | 2021-10-8 07:37 | 只看该作者
赞!官方应该收集一些信息,出一些app note

使用特权

评论回复
13
两只袜子| | 2021-10-10 17:21 | 只看该作者
楼主的发帖习惯很独特,哈哈

使用特权

评论回复
14
martinhu| | 2021-10-11 08:59 | 只看该作者
APP里面的中断向量偏移没这么复杂吧,就是SCB->VTOR寄存器重新赋值一下的事情。

使用特权

评论回复
15
martinhu| | 2021-10-11 09:02 | 只看该作者
tfqi 发表于 2021-10-5 18:44
中断的使用
  在 hc32f46x_interrupts.c/h 的文件中定义的结构体中对中断区分为三大部分,这就导致在我们 ...

第三部分不是固定好了中断号,那几个是共享使用的。一般前面的中断号如果可以满足需求,就不要使用后面共享的中断号了,因为如果不修改库,中断的判断处理会多花时间。

使用特权

评论回复
16
martinhu| | 2021-10-11 09:10 | 只看该作者
tfqi 发表于 2021-10-5 18:44
  下面我以我最近使用的 ADC 来看看中断的具体配置。首先,hc32f46x_interrupts.c/h 中对于 ADC 的中断默 ...

如果0~31,116~121这些中断号够用了的话,没必要用142,增加中断响应处理的时间成本。而且代码量也大,有的人使用,如果没用到共享中断号,可以注释掉,省掉几K的代码量。

使用特权

评论回复
评论
wubangmi 2021-10-11 14:03 回复TA
说的非常到位,手动给你点赞 
17
lesheng002| | 2021-10-16 10:03 | 只看该作者
不错,给楼主赞一个

使用特权

评论回复
18
豌豆爹| | 2021-10-18 16:52 | 只看该作者
不错,赞

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

56

主题

3316

帖子

4

粉丝