一、概述
大家平时都是怎样实现延时功能的呢?for 循环比较简单,但不够精确,定时器比较准确,但稍微更复杂一点,而且还要占用用一个定时器资源,那么有没有既简单又精确的延时方法呢?当然有,这不,SysTick 就登场了。SysTick 定时器是 Cortex-M4 的一个组成部分,它是一个 24 位计数器,使用专用异常向量,由系统时钟或 SYSTICKCLK 内部计时。 由于 SysTick 定时器是 CPU 的一部分,它提供了一个基于 ARM cortex 的设备的标准定时器,从而促进了软件的移植。
二、SysTick 基础知识
2.1 结构框图
SysTick 计时器的结构框图如下图所示。
图 1. SysTick 结构框图
2.2 SysTick 相关寄存器
SysTick 相关的寄存器比较简单,只有以下四个。
图 2. SysTick 寄存器
(1) SYST_CSR 控制状态寄存器主要是使能、中断使能、时钟源的选择等,当使用 SysTick 分频时, CPU 的频率必须不低于SysTick 分频时钟的 2.5 倍。
(2) SYST_RVR 的值是当计数到 0 时,加载到 SysTick 中的值。
(3) SYST_CVR存储 SysTick 当前的计数值。
(4) SYST_CALIB 是一个只读的校准寄存器,其值在配置系统时钟时由 SYSTCKCAL 寄存器提供,我们无需配置。
三、SysTick 的使用
前面描述了 SysTick 的寄存器,可以通过直接写寄存器来使用,也可以通过调用 SDK 的函数来使用,这里以后者为例,介绍一下 SysTick 的使用。
3.1 SysTick_Config() 函数
打开文件 core_cm4.h 可以找到 SysTick_Config() 函数,如下图所示。
图 3. SDK 的 SysTick_Config() 函数
这个函数主要做了以下配置:
- 设置重装值
- 将当前计数值清 0 ,防止之前的计数值影响
- 配置控制寄存器,使能 SysTick 、使能中断、选择系统时钟作为时钟源
我们可以直接利用这个函数进行 SysTick 的配置,当然,喜欢写寄存器的小伙伴也可以通过直接配置寄存器来完成。
3.2 具体使用
直接调用 SysTick_Config() 进行配置,入口参数为两次中断之间的计数值,比如配置为 1ms 中断一次,如下图。
图 4. 配置 SysTick
接下来是延时函数,比较简单,n 为延时的 ms ,如下图。
图 5. 延时函数
最后,别忘了 SysTick 的中断服务函数,让延时函数的时间变量自减即可,如下图。
图 6. SysTick_Handler() 函数
至此,我们就配置好了一个 1ms 级的延时函数了,接下来在需要延时的地方直接调用 “ SysTick_DelayTicks(uint32_t n); ”就可以了。
四、LPC54608 之 SysTick 延时实验
4.1 实验目的
通过本实验,理解并掌握 SysTick 延时功能。
4.2 开发环境
硬件: LPC54608 开发板
软件: MCUXpresso v11.3.0 开发环境,SDK_2.9.0_LPC54608J512
4.3 实验描述
本实验以 延时 1000ms 翻转 LED 为例,使用 SysTick 进行精确延时。
4.4 软件设计
(1)按照上述步骤配置 SysTick 即可使用,这里我们延时 1000ms 如下图。
图 7. 软件设计
4.5 实验结果
可以看到 LED3 在有节奏地翻转,抓取引脚波形,可以看到延时时间比较精确。
图 8. LED 引脚波形
4.6 实验代码
/*********************** Standard C Included Files ***********************/
#include "pin_mux.h"
#include "board.h"
#include "fsl_debug_console.h"
#include "fsl_gpio.h"
#include "stdio.h"
#define APP_BOARD_TEST_LED_PORT 2U
#define APP_BOARD_TEST_LED_PIN 2U
/******************************* Variables *******************************/
volatile uint32_t g_systickCounter;
/******************************* Code *******************************/
void SysTick_Handler(void)
{
if (g_systickCounter != 0U)
{
g_systickCounter--;
}
}
void SysTick_DelayTicks(uint32_t n)
{
g_systickCounter = n;
while (g_systickCounter != 0U)
{
}
}
int main(void)
{
uint32_t port_state = 0;
/* Define the init structure for the output LED pin*/
gpio_pin_config_t led_config = {
kGPIO_DigitalOutput,
0,
};
/* Board pin, clock, debug console init */
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
BOARD_InitPins();
BOARD_BootClockPLL180M();
BOARD_InitDebugConsole();
/* Print a note to terminal. */
PRINTF("\r\n GPIO Driver example\r\n");
PRINTF("\r\n The LED is taking turns to shine.\r\n");
/* Init output LED GPIO. */
GPIO_PinInit(GPIO, APP_BOARD_TEST_LED_PORT, APP_BOARD_TEST_LED_PIN, &led_config);
GPIO_PinWrite(GPIO, APP_BOARD_TEST_LED_PORT, APP_BOARD_TEST_LED_PIN, 1);
/* Port masking */
GPIO_PortMaskedSet(GPIO, APP_BOARD_TEST_LED_PORT, 0x0000FFFF);
GPIO_PortMaskedWrite(GPIO, APP_BOARD_TEST_LED_PORT, 0xFFFFFFFF);
port_state = GPIO_PortRead(GPIO, APP_BOARD_TEST_LED_PORT);
PRINTF("\r\n Standard port read: %x\r\n", port_state);
port_state = GPIO_PortMaskedRead(GPIO, APP_BOARD_TEST_LED_PORT);
PRINTF("\r\n Masked port read: %x\r\n", port_state);
/* Set systick reload value to generate 1ms interrupt */
SysTick_Config(SystemCoreClock / 1000U);
while (1)
{
GPIO_PortToggle(GPIO, APP_BOARD_TEST_LED_PORT, 1u << APP_BOARD_TEST_LED_PIN);
/* Delay 1000 ms */
SysTick_DelayTicks(1000U);
}
}
五、参考资料
(1)LPC546XX 系列相关资料均可在 NXP 官网下载,网址如下: