CH58x嘀嗒定时器(SysTick)
一、基本概念
SysTick是一个定时器,只是它放在了NVIC中,主要的目的是为了给操作系统提供一个硬件上的中断(号称滴答中断)。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。SysTick是基于32M高频晶振工作,所以32M不停止,则嘀嗒定时器一直存在。
CH592的嘀嗒定时器是内核自带的64位计数器。
设置好嘀嗒时间后,计数满会进入到中断并需要手动清除标志
SysTick代码参考:
#include "CH58x_common.h"
void DebugInit(void){
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
UART1_DefInit();
}
int main(){
SetSysClock(CLK_SOURCE_PLL_60MHz);
DebugInit(); /* 配置串口调试 */
PRINT("Start @ChipID=%02X\n", R8_CHIP_ID);
SysTick_Config(FREQ_SYS); //设置为60M,则1s进一次中断。设置为FREQ_SYS/1000,则1ms进入一次中断。
while(1){
PRINT("SYS_GetSysTickCnt = %d\r\n", SYS_GetSysTickCnt()); //获取计数时间,计数满会进入中断并清0。
}
}
__INTERRUPT
__HIGH_CODE
void SysTick_Handler() /***嘀嗒定时器中断函数***/
{
SysTick->SR = 0; //清除中断标志
}
通过嘀嗒定时器计算运行时间:
先使能嘀嗒定时器,然后函数获取时间即可。注意进入中断会清0,所以592计算时间如果是反过来,说明已经清0过,需要加上进入中断前清0的时间。
SysTick_Config(FREQ_SYS);
PRINT("%d\r\n", SYS_GetSysTickCnt());
noptest();
PRINT("%d\r\n", SYS_GetSysTickCnt());
__INTERRUPT
__HIGH_CODE
void SysTick_Handler() /***嘀嗒定时器中断函数***/
{
SysTick->SR = 0; //清除中断标志
}
定时器获取时间与嘀嗒定时器获取用法相同,接口函数:printf("time:%d\n", TMR0_GetCurrentTimer());
嘀嗒定时器在经过睡眠唤醒后,计数会被清除。需要重新使能。
二、扩展:32M校准32K,射频调整
直接通过示波器抓取32K的波形误差比较大,抓取32M的波形误差比较小。
32M可以产生嘀嗒定时器,如果32M的波形非常稳定,则嘀嗒定时器可以产生非常稳定的1s的波形。
32K可以产生RTC定时器,通过1s1次的RTC定时器与滴答定时器进行对比。
从而通过滴答定时器校准RTC定时器,这样可以使32K的波形变稳定。
1、通过蓝牙连接苹果手机调整32M波形
首先需要确保32M的波形是准确的,因此可以通过程序里添加一个TMOS任务,跟苹果手机连接后开启这个任务,然后看一下打印的值是多少,如果不在0附近,可以尝试调整频偏,调整频偏函数:HSECFG_Capacitance(HSECap_20p);
需要调整为打印出来的值为0附近,然后测试一下连接的情况。
原理:苹果手机(较新的手机)的蓝牙频偏比较准,蓝牙连接后可以大致估算出从机蓝牙的频偏,进行匹配电容的调整。使用仪器测试更加准确。
if ( events & ReadCfo_EVT )
{
int16 readcfo = 0;
readcfo = BLE_ReadCfo();
PRINT("ReadCfo_Count = %d\r\n", readcfo);
tmos_start_task( Peripheral_TaskID, ReadCfo_EVT, 1600 );
return (events ^ ReadCfo_EVT);
}
2、通过32M校准32K
32MHZ晶振:10PPM、12PF(立创商城YXC扬兴科技3225型号)
32768晶振:20PPM、12.5PF
如果参数如上,可以直接尝试将32M的匹配电容通过程序调整为20pf(差不多在0附近)。
调整32M的波形:HSECFG_Capacitance(HSECap_20p);
调整32K:LSECFG_Capacitance(LSECap_20p);
#include "CH58x_common.h"
void DebugInit(void){
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
UART1_DefInit();
}
void HAL_TimeInit( void ){
#if( CLK_OSC32K ) //使用内部的32K RC作为RTC的时钟,由于rc振荡器是误差比较大,所以wch提供了校准函数,并且根据需求是校准到32Khz 还是32.768khz
R8_SAFE_ACCESS_SIG = 0x57;
R8_SAFE_ACCESS_SIG = 0xa8;
R8_CK32K_CONFIG &= ~(RB_CLK_OSC32K_XT|RB_CLK_XT32K_PON);
R8_CK32K_CONFIG |= RB_CLK_INT32K_PON;
//Lib_Calibration_LSI(); // 32K rc的校准,如果不需要高精度(比如一些非ble应用),可以注释掉这个代码,可以省去一些RAM和flash空间
Calibration_LSI_FLASH();
//Calibration_LSI_RAM();
#else
R8_SAFE_ACCESS_SIG = 0x57;
R8_SAFE_ACCESS_SIG = 0xa8;
R8_CK32K_CONFIG |= RB_CLK_OSC32K_XT | RB_CLK_INT32K_PON | RB_CLK_XT32K_PON;
R8_SAFE_ACCESS_SIG = 0;
#endif
RTC_InitTime( 2020, 1, 1, 0, 0, 0 ); //RTC时钟初始化当前时间,这里实际上年月日时分秒的计算都是软件实现,如果不需要,可以注释掉.同样可以省区一些RAM和flash资源
//TMOS_TimerInit( 0 ); //tmos的时间初始化,当我么没有用到tmos时候,这里注释掉即可
}
void ch573_systick_init(void) {
SysTick_Config(0xffffffff);
//HCLK DIV 8
//SysTick->CTLR &= ~(0x000001<<2);
PFIC_DisableIRQ(SysTick_IRQn);
}
int main(){
uint8_t i;
SetSysClock(CLK_SOURCE_PLL_60MHz);
/* 配置串口调试 */
DebugInit();
PRINT("Start @ChipID=%02X\n", R8_CHIP_ID);
HSECFG_Capacitance(HSECap_20p);
LSECFG_Capacitance(LSECap_20p);
HAL_TimeInit();
PFIC_EnableIRQ(RTC_IRQn);
RTC_TMRFunCfg(Period_1_S);
ch573_systick_init();
while(1);
}
__INTERRUPT
__HIGH_CODE
void RTC_IRQHandler(void){
R8_RTC_FLAG_CTRL = RB_RTC_TMR_CLR;
static uint32_t last_systick_cnt,current_systick_cnt;
current_systick_cnt = (SysTick->CNT)&0xffffffff;
#if 0
int32_t gap = (last_systick_cnt - current_systick_cnt)&0xffffffff; //573
#else
int32_t gap = (current_systick_cnt - last_systick_cnt)&0xffffffff; //582
#endif
#if 0
PRINT("gap = %d\r\n", gap);
PRINT("last_systick_cnt = %d\r\n", last_systick_cnt);
PRINT("current_systick_cnt = %d\r\n", current_systick_cnt);
#endif
PRINT("rtc error = %5d ppm\n",(int32_t)(60e6- gap)/60);
last_systick_cnt = current_systick_cnt;
}