本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949
配套资料获取:https://renesas-docs.100ask.net
瑞萨MCU零基础入门系列教程汇总: https://blog.csdn.net/qq_35181236/article/details/132779862
本章目标
SysTick定时器也被称为滴答定时器。在Cortex架构的处理器里,有一个24bit的向下计数定时器,它就是滴答定时器。它不是处理器之外的、跟GPIO等设备同等地位的设备,而是位于处理器内部的定时器。
对于Cortex-M33内核而言,它拥有2个滴答定时器:一个用于非安全系的滴答定时器,另一个用于安全系的滴答定时器。如果开发者不使用Cortex-M33的TrustZone的程序安全功能,那么只能使用一个非安全系的滴答定时器。
滴答定时器有4个寄存器用于控制和获取状态:
a) 控制和状态寄存器:SYST_CSR
b) 重载值寄存器:SYST_RVR
c) 当前计数值寄存器:SYST_CVR
d) 校验值寄存器:SYST_CALIB
使用滴答定时器就是对这几个寄存器进行配置让它按照指定的频率进行计数,本章会实现几个驱动函数为后续章节的外设驱动使用。
在配置滴答定时器前,首先应该要熟悉其工作机制,其工作机制有如下几条:
① 当使能了滴答定时器的计数后,滴答定时器将会从重载值向下计数到零,然后在下一个时钟周期从重载值寄存器读取重载值,在紧随的下一个时钟周期又开始向下计数。
② 如果给重载值寄存器RVR写入了一个‘0’,那么本次计数循环(也就是本次向下计数到0)后就会停止计数。
③ 当计数到0使,控制状态寄存器CSR的计数标志COUNTFLAG位会被置1.,当读取CSR寄存器时会将这一位清零。
④ 如果给当前计数值寄存器CVR写入一个值时,会更新CVR的值且会将COUNTFLAG清零;
⑤ 如果程序处于调试状态,当开发者暂停调试时,滴答定时器也会暂停计数;
可以看到,这几个机制中使用到的寄存器只有3个:CSR(Control and Status Register,控制和状态寄存器)、RVR(Reload Value Register,重载值寄存器)和CVR(Current Value Register,当前值寄存器)。接下来就着重认识下这3个寄存器,并且学会如何配置他们。
控制状态寄存器各个位的描述如下图:
CSR寄存器上电复位默认值是0x00000000,一般情况下,程序是需要滴答定时器产生中断请求来判定滴答定时器计数到0的,所以TICKINT通常被设置为1;而时钟源的选择,惯用的是选择处理器时钟。所以在初始化的时候,一般将CSR的值设置为0x07。
重载值寄存器虽然是一个32bit的寄存器,但是鉴于滴答定时器的设计只有24位的计数值,因而此寄存器只有低24bit有效,高8bit保留,如下图所示:
所谓重载值,就是指滴答定时器计数到0时,又重新从这个值开始向下计数。例如程序中需要滴答定时器从100开始向下计数,那么这里就将RVR的低24bit设置为‘100-1’,也就是十六进制的0x63,二进制的0110 0011,那么RVR寄存器的值就是下图这样:
当前计数值寄存器CVR也是低24bit有效,用来表示滴答定时器当前的计数值,如图所示:
无论程序给此寄存器写入什么值,都会将此寄存器清零,并且会将计数标志位COUNTFLAG清零。
滴答定时器还有一个校准寄存器CALIB,它是只读寄存器,无需操作。从Cortex-M33的调试手册中其对滴答定时器的各个寄存器总结如下图:
那么开发者配置滴答定时器时,步骤如下:
① 选择抵达定时器的时钟源;
② 使能滴答定时器的中断请求;
③ 设置重载值;
④ 清零当前计数值;
⑤ 使能滴答定时器计数;
在Cortex内核源文件的core_cmXX.h,比如core_cm33.h定义滴答定时器的初始化函数,代码如下:
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
开发者只需要在自己的代码中调用此函数,传入一个指定的重载值即可,例如下面这个代码:
fsp_err_t SystickInit(void)
{
/* 获取处理器时钟uwSysclk */
uint32_t uwSysclk= R_BSP_SourceClockHzGet(FSP_PRIV_CLOCK_PLL);
/* 技术周期为uwSysclk/1000 */
if(SysTick_Config(uwSysclk/1000) != 0)
{
return FSP_ERR_ASSERTION;
}
/* Return function status */
return FSP_SUCCESS;
}
如上初始化滴答定时器后,它的计数时钟频率就是处理器的系统主频
在此频率下向下计数
个数后触发中断,也就是每秒钟触发1000次中断,换算公式如下:
假如
那么传入给SysTick_Config函数的值就是200K,滴答定时器就会以200MHz的频率从200K往0开始递减,递减为0时触发一次中断,中断触发频率就是
当滴答定时器计数到0时,会触发中断,中断服务函数SysTick_Handler被调用,这个函数需要开发者实现,比如给一个全局变量递增1,参考如下代码:
volatile uint32_t dwTick = 0;
void SysTick_Handler(void)
{
dwTick += 1;
}
本实验会用到板载LED外设和printf功能,请读者参考本书《第5章 GPIO输入输出》和《第7章 UART》来配置LED的GPIO和UART模块,并且移植drv_uart.c和drv_config.h到本节实验的工程“1101_systick_delay”中。
在上一小节已经初始化了滴答定时器、实现了中断服务函数。本节在此基础上实现一个ms级别的延时函数(因为初始化设置的滴答定时器是1KHz的中断触发频率)。参考如下代码:
#define HAL_MAX_DELAY 0xFFFFFFU
void HAL_Delay(uint32_t dwTime)
{
uint32_t dwStart = dwTick;
uint32_t dwWait = dwTime;
/* Add a freq to guarantee minimum wait */
if (dwWait < HAL_MAX_DELAY)
{
dwWait += (uint32_t)(1);
}
while((dwTick - dwStart) < dwWait)
{
}
}
为什么dwWait要先加1?这是因为执行HAL_Delay函数时必定是在2次滴答定时器中断之间,距离下一次中断的时间必定小于1ms。比如传入的dwTime等于2时,实际延时的时间是大于1ms、小于2ms。第10行里让dwWait值加1,目的是使得延时能满足下限:“至少延时dwWait毫秒”。
还可以读取dwTick获取系统运行时间/时刻,例如以下代码:
uint32_t HAL_GetTick(void)
{
return dwTick;
}
本书测试滴答定时器的方法是:使用基于滴答定时器的延时函数来闪烁LED,并且打印延时前后的tick值。代码如下:
void SystickAppTest(void)
{
uint8_t ucCount = 5;
uint32_t dwLastTick = 0, dwCurtick = 0;
bsp_io_level_t nLevel = false;
while(ucCount--)
{
dwLastTick = HAL_GetTick();
HAL_Delay(1000);
dwCurtick = HAL_GetTick();
本实验将滴答定时器的驱动在hal_systick.c中实现,在hal_systick.h中声明;测试函数在app_systick.c中实现,在app.h中声明;最后在hal_entry.c中调用初始化函数和测试函数,代码如下:
#include "app.h"
#include "hal_systick.h"
#include "drv_uart.h"
#include "hal_data.h"
void hal_entry(void)
{
/* TODO: add your own code here */
SystickInit();
UARTDrvInit();
SystickAppTest();
}
将程序编译出来的二进制可执行文件烧录到处理器中运行可以得到如下图的结果:
开发板上LED间隔1s改变状态。