M0系列作为stm32的入门,虽说现在用得比较多的是M3、M4系列的,不过刚好作者需要用到,网上查了大部分相关信息,也做过调试验证,顺便把代码配置也给说一下。
延时函数想必是每套代码里面最基本的函数之一了,但如果要自己写一套项目出来,配置延时函数就是必不可少的。我们可以利用32内部的SysTick定时器来实现延时,详情可参阅《ARM Cortex-M3权威指南》第133页,M3内核处理器包含了一个24位的倒计时定时器(systick),当计数到0时,则从RELOAD寄存器中自动重装载定时器的值。但其实在M0中也同样有systick可以利用,这样即不占用中断,也不占用系统定时器。
该函数初始化了两个重要参数:fac_us和fac_ms,同时将systick是时钟源配置为外部时钟,具体代码如下~
void delay_init()
{
//配置为外部时钟HCLK/8
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us = SystemCoreClock/8000000; //1us = 系统时钟/6
fac_ms = fac_us * 1000; //1ms = 1us * 1000
}`
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
这里把systick的时钟配置为外部时钟,要注意的是systick 的时钟源自与HCLK的8分频,如果外部晶振为8MHz,然后倍频到72MHz,那么systick的时钟就是72/8 = 9MHz,即是systick的计数器每减1,时间就过去了1/9us。所以fac_us = SystemCoreClock/8000000;
表示过了1us,可能有部分人对这句话不太理解,下面做个大概解释,上面说到systick为系统时钟的8分频,假设系统时钟倍频到72MHz,8分频即为9MHz,所以每一个systick时钟周期的时间为1/9us,SystemCoreClock/8000000
的意思是算出1us需要几个systick周期,即72000000/8000000 = 9个systick时钟周期,fac_us = 9 * 1/9us = 1us
没毛病,还没看懂的多看几遍就理解了,接下来的fac_ms = fac_us * 1000
1ms等于1000 us 这个不用怎么解释了。
该注意的是delay_init函数需要在main函数的开头调用作为对systick定时器的配置,fac_us和fac_ms变量需要在定义为全局变量,因为接下来的两个函数还将用到。
static uint8_t fac_us;
static uint16_t fac_ms;
该函数用来延时指定的us,其参数nus为要延时的毫秒数,代码如下~
//function: delay_us
//param: nus
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = nus * fac_us;
SysTick->VAL = 0x00;
SysTick->CTRL|= SysTick_CTRL_ENABLE_Msk;
do{
temp = SysTick->CTRL;
}while(temp&0x01&&! (temp&(1<<16)));
SysTick->CTRL&= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0x00;
}
以上是配置systick的寄存器实现的延时,有兴趣的可以参阅库文 stmfx_map.h
里面的 CTRL、LOAD、VAL、CALIB
这4个寄存器。
该函数用来延时指定的ms,其参数nms为要延时的毫秒数,代码如下~
//function: delay_ms
//param: nms
void delay_ms(uint16_t nms)
{
uint32_t temp;
SysTick->LOAD = nms * fac_ms;
SysTick->VAL = 0x00;
SysTick->CTRL|= SysTick_CTRL_ENABLE_Msk;
do
{
/* code */
temp = SysTick->CTRL;
} while (temp&0x01&&! (temp&(1<<16)));
SysTick->CTRL&= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0x00;
}
创建 delay.h
加入上面3个函数的声明,然后在main函数或者其他函数中直接调用 delay_us()
函数和 delay_ms()
函数就可以了。
下面给上ucos系统的systick延时配置代码~
void delay_init()
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us = SystemCoreClock/8000000;
reload = SystemCoreClock/8000000;
reload* = 1000000/OS_TICKS_PER_SEC;
fac_ms = 1000/OS_TICKS_PER_SEC;
SysTick->CTRL| =SysTick_CTRL_TICKINT_Msk;
SysTick->LOAD = reload;
SysTick->CTRL| =SysTick_CTRL_ENABLE_Msk;
}
void delay_us(u32 nus)
{
u32 ticks,told,tnow,tcnt = 0;
u32 reload = SysTick->LOAD;
tick = nus * fac_us;
tcnt = 0;
OSSchedLock();
told = SysTick->VAL;
while(1)
{
tnow = SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)
tcnt += told - tnow;
else
tcnt += reload - tnow + wold;
told = tnow;
if(tcnt>=ticks)
break;
}
};
OSSchedUnlock();
}
void delay_ms(u16 nms)
{
if(OSRunning == TRUE)
{
if(nms>=fac_ms){
OSTimeDly(nms/fac_ms);
}
nms%=fac_ms;
}
delay_us((u32)(nms*1000));
}
可以将两套配置代码(无系统和ucos系统)在程序中加个宏判断合并起来,这样便于管理而已也减少内存的占用。