目录
前言
本文讨论S32K144的24位递减定时器:SysTick定时器,虽然该定时器属于内核,所有Cortex-M4内核的芯片都有这个外设,但是每个芯片厂家在设计芯片的时候还是有点小差别。这使得笔者更有兴趣去研究一下NXP如何处理这个内核自带的定时器。
1.SysTick分析
在S32K144的参考手册中,关于SysTick定时器的描述只有短短的几行字,把他翻译过来大致描述如下:
- 在SysTick定时器的控制和状态寄存器(CSR)中的CLKSOURCE位(时钟源选择)总是选择内核时钟CORE_CLK(这里要注意,S32K144中的SysTick的时钟源只有一个)
- 由于S32K144的电源模式管理机制,使得CORE_CLK是一个可变的频率值,所以SysTick校验值寄存器(CVR)中的TENMS总是0
- SysTick校验值寄存器(CVR)中的NOREF位总是1,用来表示CORE_CLK是唯一可选的时钟源
反正说了一大堆,就是一句话,SysTick的时钟源只能是CORE_CLK。
翻遍了整个参考手册也没有找到有关于寄存器的具体定义,没有办法只能去找源头了,《Cortex-M4 Devices Generic User Guide》这个文档中详细阐述了Cortex-M4内核的SysTick。
1.1 SysTick概述
这个定时器的特性非常的简单,它是一个计数宽度为24位的定时器,计数时钟来源于CORE_CLK,当定时器开启时,它从当前计数值每个时钟周期递减1,如果计数器减为0,则会触发异常中断,并从重载寄存器中读取下一次的计数值。
参考一些其他文档和其他芯片的使用经验来看,这个定时器一般就做两个用处,如果是裸机的话一般用来作为延时函数的时间基准;如果是使用了rtos的话就用它来作为系统心跳定时器。
根据《Cortex-M4 Devices Generic User Guide》,SysTick正确的操作顺序是:
(1)写入重载值
(2)清空当前计数值
(3)启动定时器
1.2 SysTick寄存器

位域 | 名称 | 功能 |
[31:17] | - | 保留 |
[16] | COUNTFLAG | 如果计时器自上次读取以来计数为0,则返回1 |
[15:3] | - | 保留 |
[2] | CLCKSOURCE | 1:内核时钟 0:外部时钟(S32K144无此功能) |
[1] | TICKINT | 1:如果计数器递减为0,发出异常请求 0:如果计数器递减为0,不发出异常请求 |
[0] | ENABLE | 0:禁用 1:使能 |

位域 | 名称 | 功能 |
[31:24] | - | 保留 |
[23:0] | RELOAD | 自动重载值 |

位域 | 名称 | 功能 |
[31:24] | - | 保留 |
[23:0] | CURRENT | 当前定时器的计数值 |

位域 | 名称 | 功能 |
[31] | NOREF | 是否提供了一个参考时钟 1:已经提供参考时钟 0:无参考时钟 |
[30] | SKEW | 0:TENMS准确 1:TENMS不准确,或者未知 |
[29:24] | - | 保留 |
[23:0] | TENMS | 对于 10 毫秒(100Hz)定时的重新加载值,会受到系统时钟偏差误差的影响。如果该值读取为零,则校准值未知。 |
2.SysTick延时功能代码
由于该定时器的使用比较简单,此处附上代码。
2.1 定时器初始化代码
这里初始化代码是根据CM4用户手册推荐的顺序编写,具体代码如下:
void SysTickBegin(void)
{
S32_SysTick->CSR = S32_SysTick_CSR_ENABLE(0); //禁用 SysTick
S32_SysTick->RVR = 0xFFFFFF; //设定重装载值寄存器重装载值为 0xFFFFFF
S32_SysTick->CVR = 0; //设定当前值寄存器值为 0
S32_SysTick->CSR |= S32_SysTick_CSR_TICKINT(0) | S32_SysTick_CSR_ENABLE(1)
| S32_SysTick_CSR_CLKSOURCE(1); //禁用 SysTick 中断,使能 SysTick,时钟源选择内核时钟
}
2.1 延时函数
这里的延时函数是根据网上其他共享代码修改而来,注意此处使用了SDK的全局变量SystemCoreClock,由于系统时钟可能随着运行模式改变,所以每次使用延时函数时均需要更新这个全局变量以确保延时相对准确。
void DelayUsFromSysTick(uint16_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = S32_SysTick->RVR; //重装载值
told = S32_SysTick->CVR; //刚进入时计数器值
SystemCoreClockUpdate(); //获取当前的系统时钟
ticks = nus * (SystemCoreClock / 1000000u); //此处的括号必须要有,否则数据溢出
while (1)
{
tnow = S32_SysTick->CVR;
if (tnow != told)
{
if (tnow < told)
{
tcnt = tcnt + (told - tnow); //注意 SysTick 是递减计数器
}
else
{
tcnt = tcnt + (reload - tnow + told);
}
told = tnow;
if (tcnt >= ticks)
break; //时间等于或超过延迟时间退出循环
}
}
}
void DelayMsFromSysTick(uint16_t nms)
{
uint16_t i = 0;
for (i = 0; i < nms; ++i)
{
DelayUsFromSysTick(1000);
}
}