在项目中使用了HC32F005这颗IC,遇到一个新版本DDL库的问题,现象是使能了BGR模块后,系统systick中断停止了。
int Adc_init(void)
{
if (Ok != Sysctrl_SetPeripheralGate(SysctrlPeripheralAdcBgr, TRUE)) // 内建电压模块时钟使能
{
return Error;
}
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE); //GPIO 外设时钟使能
Gpio_SetAnalogMode(BATT_ADC_PORT, BATT_ADC_PIN);
Adc_Enable();
Bgr_BgrEnable();
stc_adc_cfg_t stcAdcCfg;
stc_adc_norm_cfg_t stcAdcNormCfg;
DDL_ZERO_STRUCT(stcAdcCfg);
DDL_ZERO_STRUCT(stcAdcNormCfg);
stcAdcCfg.enAdcOpMode = AdcNormalMode; //单次采样模式
stcAdcCfg.enAdcClkSel = AdcClkSysTDiv1; //PCLK
stcAdcCfg.enAdcSampTimeSel = AdcSampTime4Clk; //4个采样时钟
stcAdcCfg.enAdcRefVolSel = RefVolSelInBgr2p5; //参考电压:内部2.5V(avdd>3V,SPS<=200kHz) SPS速率 = ADC时钟 / (采样时钟 + 16CLK)
stcAdcCfg.bAdcInBufEn = FALSE; //电压跟随器如果使能,SPS采样速率 <=200K
stcAdcCfg.u32AdcRegHighThd = 0u; //比较阈值上门限
stcAdcCfg.u32AdcRegLowThd = 0u; //比较阈值下门限
stcAdcCfg.enAdcTrig0Sel = AdcTrigDisable; //ADC转换自动触发设置
stcAdcCfg.enAdcTrig1Sel = AdcTrigDisable;
Adc_Init(&stcAdcCfg);
stcAdcNormCfg.enAdcNormModeCh = AdcExInputCH6; //通道6 P36
stcAdcNormCfg.bAdcResultAccEn = FALSE;
Adc_ConfigNormMode(&stcAdcCfg, &stcAdcNormCfg);
return 0;
}
通过排除法,定位到了关键代码:
Bgr_BgrEnable();
由于使用了新版本的DDL库(V1.9.0),对BGR模块的操作封装成了库函数 Bgr_BgrEnable(),代替旧版本(V1.8.0)直接操作寄存器的方式
M0P_BGR->CR_f.BGR_EN = 0x1u; //使能内建电压模块
M0P_BGR->CR_f.TS_EN = 0x0u;
而 Bgr_BgrEnable的实现如下:
void Bgr_BgrEnable(void)
{
M0P_BGR->CR |= 0x1u;
delay10us(2);
}
其中M0P_BGR的CR与CR_f成员是联合类型,其中CR_f又使用了位域的方式定义了几个寄存器:
typedef struct
{
__IO uint32_t BGR_EN : 1;
__IO uint32_t TS_EN : 1;
uint32_t RESERVED2 :29;
__IO uint32_t RSV : 1;
} stc_bgr_cr_field_t;
typedef struct
{
union
{
__IO uint32_t CR;
stc_bgr_cr_field_t CR_f;
};
}M0P_BGR_TypeDef;
CR的最低位就是CR_f.BGR_EN,这两种赋值方式最终的效果是一致的,对照HC32F005的手册,这个BIT就是使能BGR模块:
看起来没有什么问题,实际debug时,发现执行赋值语句后,两种方式的值都是一致的,但是后续运行的结果就是不一致,给位域结构CR_f赋值的方式系统正常运行,而给CR赋值的方式就是会导致整个MCU systick停止,系统停止了任务调度,真是见鬼了。
但是想想,这个DDL库是从官网下载的,通常情况下应该不会有问题。再次细读datasheet,注意到了一个说法:
同时注意到V1.9.0的DDL库封装的函数中,确实延时了20us,然而原来寄存器操作的方式并没有延时操作,那么可能就是调用延时函数delay10us(2)造成的,虽然内心极度不相信,忐忑地查看了delay10us的实现:
/**
* \brief delay10us
* delay approximately 10us.
* \param [in] u32Cnt
* \retval void
*/
void delay10us(uint32_t u32Cnt)
{
uint32_t u32end;
SysTick->LOAD = 0xFFFFFF;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_CLKSOURCE_Msk;
while(u32Cnt-- > 0)
{
SysTick->VAL = 0;
u32end = 0x1000000 - SystemCoreClock/100000;
while(SysTick->VAL > u32end)
{
;
}
}
SysTick->CTRL = (SysTick->CTRL & (~SysTick_CTRL_ENABLE_Msk));
}
好家伙,居然用SysTick定时器来实现延时,LOAD值都被修改了,难怪SysTick失效了,耗费了几个小时居然是这样一个问题。
那么最后修改起来也就简单了,还是采用寄存器赋值的方式,重写延时函数:
#if 0 // 1.9.0 ddl
Bgr_BgrEnable();
#else // 1.8.0 ddl
M0P_BGR->CR_f.BGR_EN = 0x1u; //使能内建电压模块
M0P_BGR->CR_f.TS_EN = 0x0u;
for( int i = 0; i < 320; i++ ){ // delay 20us 320*(1/16us) = 20us
__ASM("NOP");
}
#endif
因为系统PCLK是16Mhz,每条指令执行时间为1/16us, 需要执行320次才能耗费20us,因此用一个for()来实现,同时由于i++,i<320这些判断也会产生额外的耗时,最后的时间肯定会大于20us,也保证了BGR模块可以正常操作。
最后为了证明修改后的延时代码是否如分析的那样,看看编译器生成的汇编代码片段:
C语言的for从第9行开始:
1,第 9行,编译器为局部循环控制变量i分配给了寄存器R0,并用MOVS指令初始化为0
2,第11行,B 0x000013c8指令跳转到第30行
3,第30行,31行指令给计算了出了一个数值255+65=320,并赋值给寄存器R1
4,第32行,比较R0和R1的值,即 i<320 ?
5,第33行,BLT 0x000013c4, 若R0小于R1,即 i < 320, 则跳转第19行,否则for()结束,进入后续逻辑代码块
6,第19行,执行我们定义的NOP指令,接着执行第28行,对R0加1,即i++,接着跳转第30行,继续下一轮比较。
逻辑上与C语言是一致的,只不过除了NOP指令,还增加了6条控制指令,这样320次循环执行下来,耗时必定超过了20us,在这里我只需要保证满足20us即可,时间长一点对系统影响不大。
|