看了前辈通过配置寄存器来点亮LED灯,大家对stm32理解深了很多吧?在正常情况下,大家都用的固件函数库来编程,这样能缩短开发时间,减少开发中的bug,提高开发的效率。所以前辈的帖子,就当做是初学时加深对stm32的认识吧。
都说人生如戏,那么编程也是这样。演戏,就要有剧本。固件库就是ST给我们写好的剧本,怎么样“演”好stm32中各个“角色”,就看大家发挥了。下面,就以SysTick()来熟悉下固件库(2.0.3)吧。
上次的程序中,SysTick配置函数共有4句。我们来逐句分析,这次要“打破砂锅问到底”!
void Systick_Configuration(void)
{
SysTick_CounterCmd(SysTick_Counter_Disable); // ①
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // ②
SysTick_CounterCmd(SysTick_Counter_Clear); // ③
SysTick_SetReload(9000*1000); //④
}
① SysTick_CounterCmd(SysTick_Counter_Disable);
③ SysTick_CounterCmd(SysTick_Counter_Clear);
打开我们在工程里添加的systick.c文件,找到SysTick_CounterCmd();。下面是代码,我们继续分析。
voidSysTick_CounterCmd(u32 SysTick_Counter)
{
assert_param(IS_SYSTICK_COUNTER(SysTick_Counter));
if (SysTick_Counter ==SysTick_Counter_Enable)
{
SysTick->CTRL |= SysTick_Counter_Enable;
}
else if (SysTick_Counter ==SysTick_Counter_Disable)
{
SysTick->CTRL &=SysTick_Counter_Disable;
}
else
{
SysTick->VAL = SysTick_Counter_Clear;
}
}
第一句,调用assert_param();函数。这个是干嘛的呢?在stm32F10xxx函数固件库手册中查找,里面有这个函数的说明:宏assert_param来实现运行时间检测,所有要求输入参数的函数都使用这个宏。它可以检查输入参数是否在允许的范围内。宏assert_param编写与文件stm32f10x_conf.h中。
这样大家就明白它的作用了。其实这个是在debug时用的,所以就不继续解释了,有兴趣的可以去stm32f10x_conf.h中看看,代码写的很清楚。
再往下,IS_SYSTICK_COUNTER(SysTick_Counter)是个嵌套的函数,什么作用呢?打开stm32f10x_systick.h,里面用它的宏定义,三个常量都是什么,上面也都有,很清楚的,不多说。然后就是if else结构,SysTick_Counter是传入的形参,后面还是刚才的常量,经过判断,然后是个赋值的表达式:
SysTick->CTRL|= SysTick_Counter_Enable;
SysTick->CTRL是结构体变量,定义的位置是stm32f10x_map.h,
typedef struct
{
vu32 CTRL;
vu32 LOAD;
vu32 VAL;
vuc32 CALIB;
}SysTick_TypeDef;
这里边的四个变量又是什么呢?就是systick的四个寄存器了。看下它们的地址,还是在stm32f10x_map.h中,
#defineSCS_BASE ((u32)0xE000E000)
#defineSysTick_BASE (SCS_BASE + 0x0010)
#ifdef _SysTick
EXT SysTick_TypeDef *SysTick;
#endif
为了访问SysTick寄存器,_SysTick在stm32f10x_conf.h中定义:#define _SysTick,再看上面的结构体,很清楚吧,四个变量的地址就是四个寄存器的地址,分别是:控制及状态寄存器(0xE000E010)、重装载数值寄存器(0xE000E014)、当前数值寄存器(0xE000E018)、校准数值寄存器(0xE000E01C)。所以SysTick->CTRL就是控制和状态寄存器,还记得上面看过的吧?
#define SysTick_Counter_Enable ((u32)0x00000001)
#define SysTick_Counter_Disable ((u32)0xFFFFFFFE)
#define SysTick_Counter_Clear ((u32)0x00000000)
再来看下:控制及状态寄存器CTRL(0xE000E010),最后一位是ENABLE,1 是计数器工作在连拍模式,0是禁能计数器。可以这么理解,用SysTick_Counter_Enable和这个寄存器“按位或”,就把状态置1,同样,清零的时候,就用SysTick_Counter_Disable 和下面这个寄存器“按位与”,就把状态复位了。最后
SysTick->VAL= SysTick_Counter_Clear;
这句,就是把VAL(当前数值寄存器)清零,准备重新计数了。
②SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
上面说了这么多,下面就简单点了。还是看systick.c文件,有下面的函数:
voidSysTick_CLKSourceConfig(u32 SysTick_CLKSource)
{
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource ==SysTick_CLKSource_HCLK)
{
SysTick->CTRL |=SysTick_CLKSource_HCLK;
}
else
{
SysTick->CTRL &=SysTick_CLKSource_HCLK_Div8;
}
}
第一句和上面一样,不再解释。后面的部分,设置的还是刚才的寄存器,也很清楚。
④ SysTick_SetReload(9000*1000);
还是在systick.c文件中,
void SysTick_SetReload(u32 Reload)
{
assert_param(IS_SYSTICK_RELOAD(Reload));
SysTick->LOAD = Reload;
}
啰嗦的话就不说了。
再看延时函数。
void Delay_Second(void)
{
SysTick_CounterCmd(SysTick_Counter_Enable);
while(SysTick_GetFlagStatus(SysTick_FLAG_COUNT)== 0);
SysTick_CounterCmd(SysTick_Counter_Disable);
SysTick_CounterCmd(SysTick_Counter_Clear);
}
这里主要看函数中的第二句
while(SysTick_GetFlagStatus(SysTick_FLAG_COUNT)== 0);
在systick.c中,有如下代码:
FlagStatus SysTick_GetFlagStatus(u8SysTick_FLAG)
{
u32statusreg = 0, tmp = 0 ;
FlagStatus bitstatus = RESET;
assert_param(IS_SYSTICK_FLAG(SysTick_FLAG));
tmp= SysTick_FLAG >> 3;
if(tmp == 2)
{
statusreg = SysTick->CTRL;
}
else
{
statusreg = SysTick->CALIB;
}
if((statusreg & ((u32)1 << SysTick_FLAG)) != (u32)RESET)
{
bitstatus = SET;
}
else
{
bitstatus = RESET;
}
return bitstatus;
}
这个函数,是检查指定的SysTick 标志位设置与否,在systick.h中有定义,
#define SysTick_FLAG_COUNT ((u32)0x00000010)
#define SysTick_FLAG_SKEW ((u32)0x0000001E)
#define SysTick_FLAG_NOREF ((u32)0x0000001F)
下面是说明,详细情况见固件函数库。
SysTick_FLAG_COUNT:自从上一次被读取,计数器计数至0
SysTick_FLAG_SKEW:由于时钟频率,校准值不精确等于10ms
SysTick_FLAG_NOREF:参考时钟未提供
所以当传入的值是SysTick_FLAG_COUNT时,tmp=2成立,执行statusreg = SysTick->CTRL。再看固件函数库中,关于COUNTFLAG的描述:从上次读取定时器开始,如果定时器计数到0,则返回1。此时,if ((statusreg & ((u32)1 << SysTick_FLAG)) != (u32)RESET)条件成立,返回SET,再回到main.c,while()条件不成立,结束循环,1s延时结束!