补充:
api容易理解,但是具体systic的时钟如何生成,由何生成是个头疼的问题。
systic计数的原理也是定时器:计数单元、重载单元、比较单元、触发单元。核心工作原理是按照一个设定的时钟频率,比如1000000 = 1M,就是1微妙/次,如果是80M,那么就是1微妙/80次。所以,当用作延时计数时,要看设定的频率是多少,当然频率越高,计时的精度就越高。
拿微妙来说,如果频率只有1M,那么VAL计数1后就会触发一个结束事件,其实这种时间间隔与延迟单位一致会很影响时间精度,所以频率越高,那么时间精度就越容易高。
而systic的频率是在系统的时钟分配时设定的。一般ARM架构的时钟单元架构分为
![](https://i-blog.csdnimg.cn/blog_migrate/6a2738349b0d348216f0523e6ae0ef06.png)
一般根据芯片说明先找到systic频率由何而来,前置各个环节频率是多少,这样就可以推导出实际SYSTIC所用频率,然后就可以进一步进行延时编程、验证。
我们除了这个systic,一般还有另一个纯代码操作的延时计数,多个平台,不同频率可能需要微调。就是下面说的加循环函数。
const int delayAdjustments = 12;
void delay_nus(uint16_t n)
{
uint16_t j;
while(n--)
{
j=delayAdjustments;//根据不同时钟频率的指令周期调整数值
while(j--);
}
}
12这个数看起来像是指令周期,具体应该是靠这种运算来模拟指令周期,指令周期和CPU主频有一定的比例关系(大部分)。所以这也可作为一种比较粗糙的延时计时方法,在200M的ARM-m4里误差相当小,可以拿来做一般的线性驱动来用,比如I2C,单总线等。
1、SysTick究竟是什么?
关于SysTick在STM32的资料中并没有详细的介绍,这可能由于SysTick是ARM内核的东西。在《STM32F10xxx参考手册》、《STM32F4xx参考手册》以及《STM32F7xx参考手册》中,介绍时钟的时候仅仅是在使用树上简单的画出了HCLK时钟经过8分频后送到了Cortex系统时钟。对这个时钟的描述也非常的简单。在《STM32F10xxx参考手册》中仅仅说:“RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。”同样在《STM32F4xx参考手册》和《STM32F7xx参考手册》中,也只是说:“RCC 向 Cortex 系统定时器 (SysTick) 馈送 8 分频的 AHB 时钟 (HCLK)。”
另外,STM32在中断部分对SysTick也有一句话的描述。如在《STM32F10xxx参考手册》中的“9.1.1 系统嘀嗒(SysTick)校准值寄存器”中提到:“系统嘀嗒校准值固定为9000,当系统嘀嗒时钟设定为9MHz(HCLK/8的最大值),产生1ms时间基准。”而在《STM32F4xx参考手册》的“10.1.2SysTick 校准值寄存器”一节和《STM32F7xx参考手册》“10.1.1SysTick 校准值寄存器”一节中也都有:“SysTick 校准值设置为 18750。当 SysTick 时钟设置为 18.75 MHz(HCLK/8,HCLK 设为150 MHz),会产生 1 ms 时间基准。”
仅看这些让我们觉得SysTick似乎都是定好的,但事实上并非如此,因为在库函数中有相关寄存器的操作函数。为了搞清楚这一点,我们必须查看Cortex-M3和M4的手册。在《Cortex-M3权威指南》的“第8章 NVIC与中断控制”中有比较详细的描述。首先很明确SysTick 就是一个定时器;其次SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常,主要适用于操作系统的“心跳”节律。关于SysTick的时钟来源最终还是由芯片厂商决定。
SysTick定时器能产生中断,并且是一个单独的异常类型,并且在向量表中有它的一席之地。并有四个寄存器来控制SysTick,在《Cortex-M3权威指南》中对他们的描述如下:
(1)、STK_CSR控制寄存器:寄存器内有4个位具有意义
(2)、STK_LOAD 重载寄存器
Systick 是一个递减的定时器,当定时器递减至0 时,重载寄存器中的值就 会被重装载,继续开始递减。STK_LOAD 重载寄存器是个24 位的寄存器最大计数0xFFFFFF。
(3)、STK_VAL当前值寄存器
也是个24 位的寄存器,读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG 标志。
(4)、STK_CALRB 校准值寄存器
2、STM32中的SysTick
作为一用于系统级的24位递减计时器,在STM32中又是处理的呢?ST在手册里介绍的比较简单,但在库函数中却有比较清楚的定义。不论是标准库还是HAL库都有较为清楚的定义,如在标准库中就定义SysTick寄存器结构体机器操作函数,在misc.C文件中的SysTick_CLKSourceConfig函数,它是一个时钟源配置函数,其定义如下:
/*@功能:配置SysTick时钟源
*@输入参数: SysTick_CLKSource: 指定 SysTick 时钟源.
* 该参数可以是以下其中一个值:
* @ SysTick_CLKSource_HCLK_Div8: AHB 时钟 8 分频作为SysTick时钟源
* @ SysTick_CLKSource_HCLK: AHB 时钟作为 SysTick 时钟源. */
void SysTick_CLKSourceConfig(uint32_t Sy sTick_CLKSource)
{
assert_param(IS_SYSTICK_CLK_SOURCE(SysTi ck_CLKSource));
if(SysTick_CLKSource== SysTick_CLKSource_HCLK)
{
SysTick->CTRL|= SysTi ck_CLKSource_HCLK;
}
else
{
SysTick->CTRL&= SysTick_CLKSource_HCLK_Div8;
}
}
由此可见,在STM32中时钟可以设置为HCLK或者HCLK的8分频,根据使用的需求而定。默认不配置的话时钟就是HCLK的8分频。同样在HAL苦衷的定义也是如此。
在core_cm3.h文件中有寄存器定义:
typedefstruct
{
__IOuint32_t CTRL;
__IOuint32_t LOAD;
__IOuint32_t VAL;
__Iuint32_t CALIB;
}SysTick_Type;
还定义了一个SysTick_Config 函数
#if (!defined(__Vendor_Sy sTickConfig )) ||(__Vendor_SysTickConfig == 0)
/**
* @功能 初始化并开启 Sy sTick 计数器及其中断
*@输入参数 ticks 两次中断间的 ticks 数值
*@返回值 1 = 失败, 0=成功
* 初始化系统滴答定时器及其中断并开启系统滴答定时器在自由运行模式下以产生周期中断*/
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if(ticks >SysTick_LOAD_RELOAD_Msk) return(1);/* 重装值超过了 24 位,是不可能的。返回失败值 0 */
SysTick->LOAD =(ticks & SysTick_LOAD_RELOAD_Msk) - 1;/* 设置重装载寄存器 */
NVIC_SetPriority(SysTick_IRQn,( 1<<__NVIC_PRIO_BITS) - 1); /*设置优先级for Cortex-M0系统中断*/
SysTick->VAL = 0; /*装载计数器值(当前计数值清 0) */
SysTick->CTRL =SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk| SysTick_CTRL_ENABLE_Msk;/* 使能 Sy sTick 中断请求和 Sy stick 定时*/
return(0); /*成功,返回 0 */
}
#endif
经过以上分析,我们我们需要,可以很方便的在自己的软件中操作SysTick来实现一些功能。
3、STM32中SysTick应用举例
既然SysTick我们已经清楚了他的原理及操作,也明白了STM32库中如何来操作它,那么我们能用他来做什么呢?首先我们可以在STM32中使用嵌入式操作系统的时候使用它来,并更具实际应用来设置。其实在不考虑操作系统是我们还可以使用SysTick来实现延时计时器。
一个实现的例子如下:
//定义延时计数的变量
__IO uint32_t TimingDelay;
const uint16_t delayAdjustments=12;
//函数名:ms延时函数
//描 述:参数1即为1ms,1000即为1s;只有几us的误差;
void Delayms(__IO uint32_t nTime)
{
while(SysTick_Config(SystemCoreClock/1000));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;// 关闭滴答定时器
TimingDelay = nTime;
SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk;// 使能滴答定时器
while(TimingDelay != 0);
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//函数名:us延时函数
//描 述:参数1即为1us,1000即为1ms;只有几us的误差;
void Delayus(__IO uint32_t nTime)
{
while(SysTick_Config(SystemCoreClock/1000000));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;// 关闭滴答定时器
TimingDelay = nTime;
SysTick->CTRL |=SysTick_CTRL_ENABLE_Msk;// 使能滴答定时器
while(TimingDelay != 0);
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//函数名:延时递减计数函数
//描 述:由systick的中断函数调用,实现延时数值的递减计数
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
//函数名:延时调整形式的delaynus函数
//描 述:参数1即为1us,1000即为1ms,根据时钟频率的不同时间不同
void delay_nus(uint16_t n)
{
uint16_t j;
while(n--)
{
j=delayAdjustments;//根据不同时钟频率的指令周期调整数值
while(j--);
}
}
//函数名:延时调整形式的delaynms函数
//描 述:参数1即为1ms,1000即为1s,根据时钟频率的不同时间不同
void delay_nms(uint16_t n)
{
while(n--)
{
delay_nus(1000);
}
}
4、参考文献
我们参考了一些经典的手册以及网络论坛上的一些讨论,无法一一列出,暂列出部分参考文献:
(1)、《Cortex-M3权威指南》
(2)、《STM32F10x参考手册》
(3)、《STM32F4参考手册》
(4)、《STM32F7参考手册》
(5)、《STM32F10x库函数说明》
(6)、《STM32F1 HAL库说明》
(7)、《STM32F4 HAL库说明》
(8)、《STM32F7 Hal库说明》