因为是使用SysTick来作为延时时钟,因此在这里给出SysTick时钟的寄存器;
CTRL:SysTick控制及状态寄存器
位段 |
名称 |
类型 |
复位值 |
描述 |
16 |
COUNTFLAG |
R/W |
0 |
如果在上次读取本寄存器后, SysTick 已经计到 |
2 |
CLKSOURCE |
R/W |
0 |
时钟源选择位,0=AHB/8,1=处理器时钟AHB |
1 |
TICKINT |
R/W |
0 |
1=SysTick倒数计数到 0时产生 SysTick异常请 |
0 |
ENABLE |
R/W |
0 |
SysTick 定时器的使能位 |
LOAD:SysTick 重装载数值寄存器
位段 |
名称 |
类型 |
复位值 |
描述 |
23:0 |
RELOAD |
R/W |
0 |
当倒数计数至零时,将被重装载的值 |
VAL:SysTick当前数值寄存器
位段 |
名称 |
类型 |
复位值 |
描述 |
23:0 |
CURRENT |
R/W |
0 |
读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick控制及状态寄存器中的COUNTFLAG 标志 |
此处参考外设库的例子,程序结构和逻辑大致如下:
通过SystemInit()配置好时钟;
通过SysTick_Config(SystemCoreClock/1000)配置SysTick时钟;
创建Delay(__IO uint32_t nTime)毫秒延时函数(设置执行几次中断,时间为nTime×中断时间);
在SysTick中断SysTick_Handler()中按照配置的减小计数值并检测是否清零;
static __IO uint32_t TimingDelay; void Delay(__IO uint32_t nTime); /** * @brief Main program * @param None * @retval None */ int main(void) { /*!< At this stage the microcontroller clock setting is already configured, this is done through SystemInit() function which is called from startup file (startup_stm32f0xx.s) before to branch to application main. To reconfigure the default setting of SystemInit() function, refer to system_stm32f0xx.c file */ /* Setup SysTick Timer for 1 msec interrupts. ------------------------------------------ 1. The SysTick_Config() function is a CMSIS function which configure: - The SysTick Reload register with value passed as function parameter. - Configure the SysTick IRQ priority to the lowest value (0x0F). - Reset the SysTick Counter register. - Configure the SysTick Counter clock source to be Core Clock Source (HCLK). - Enable the SysTick Interrupt. - Start the SysTick Counter. 2. You can change the SysTick Clock source to be HCLK_Div8 by calling the SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8) just after the SysTick_Config() function call. The SysTick_CLKSourceConfig() is defined inside the misc.c file. 3. You can change the SysTick IRQ priority by calling the NVIC_SetPriority(SysTick_IRQn,...) just after the SysTick_Config() function call. The NVIC_SetPriority() is defined inside the core_cm0.h file. 4. To adjust the SysTick time base, use the following formula: Reload Value = SysTick Counter Clock (Hz) x Desired Time base (s) - Reload Value is the parameter to be passed for SysTick_Config() function - Reload Value should not exceed 0xFFFFFF */ if (SysTick_Config(SystemCoreClock / 1000)) { /* Capture error */ while (1); } while (1) { /* Insert 100 ms delay */ Delay(100); } } /** * @brief Inserts a delay time. * @param nTime: specifies the delay time length, in milliseconds. * @retval None */ void Delay(__IO uint32_t nTime) { TimingDelay = nTime; while(TimingDelay != 0); } /** * @brief Decrements the TimingDelay variable. * @param None * @retval None */ void TimingDelay_Decrement(void) { if (TimingDelay != 0x00) { TimingDelay--; } }
由于官方的例子中默认配置好了时钟,因此给出的SystemCoreClock是48000000;
设置好重装值后,就可以确定发生一次中断需要的时间;
uint32_t SystemCoreClock = 48000000; /** * @brief This function handles SysTick Handler. * @param None * @retval None */ void SysTick_Handler(void) { TimingDelay_Decrement(); }
从SysTick_Config()中可以看出配置SysTick的步骤:
设置重装载寄存器LOAD的值;(设置的是中断时间)
设置中断优先级;
清楚当前数值寄存器VAL的值;
配置控制与状态寄存器CTRL,包括时钟源、系统定时器中断、使能定时器;
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) { if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = ticks - 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Systick Interrupt */ SysTick->VAL = 0; /* 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 (0); /* Function successful */ }
SysTick中断时间的计算:
假设fAHB=180MHz,则计数值±1的时间为t=1/fAHB,中断一次的时间=LOAD×t;
根据计算,中断1us的时间= LOAD×1/fAHB=180×(1/180M)=1us;
若需要中断1ms的时间=180000×(1/180M)=1ms;
因此,设置SystemCoreClock=180M/1000=180000=LOAD值;
这样取LOAD值为180000就可以得到一次中断需要的时间为1ms;
由于库例子是默认设置好了时钟,因此这里涉及到一个时钟的配置
在SystemInit中重要任务是清除RCC中的时钟配置:
将HSI作为默认时钟;
且SYSCLK不分频、HCLK不分频、ADC时钟不分频、MCO时钟输出不分频;
关闭HSE、CSS、PLL时钟;
将外部HSE时钟输入旁路;
清空PLL系数;
清空USART1、I2C、CEC和ADC外设的时钟源;
关闭HSI14时钟;
关闭所有中断Int;
void SystemInit (void) { /* Set HSION bit */ RCC->CR |= (uint32_t)0x00000001; #if defined(STM32F051) /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE and MCOSEL[2:0] bits */ RCC->CFGR &= (uint32_t)0xF8FFB80C; #else /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE, MCOSEL[2:0], MCOPRE[2:0] and PLLNODIV bits */ RCC->CFGR &= (uint32_t)0x08FFB80C; #endif /* STM32F051 */ /* Reset HSEON, CSSON and PLLON bits */ RCC->CR &= (uint32_t)0xFEF6FFFF; /* Reset HSEBYP bit */ RCC->CR &= (uint32_t)0xFFFBFFFF; /* Reset PLLSRC, PLLXTPRE and PLLMUL[3:0] bits */ RCC->CFGR &= (uint32_t)0xFFC0FFFF; /* Reset PREDIV1[3:0] bits */ RCC->CFGR2 &= (uint32_t)0xFFFFFFF0; /* Reset USARTSW[1:0], I2CSW, CECSW and ADCSW bits */ RCC->CFGR3 &= (uint32_t)0xFFFFFEAC; /* Reset HSI14 bit */ RCC->CR2 &= (uint32_t)0xFFFFFFFE; /* Disable all interrupts */ RCC->CIR = 0x00000000; /* Configure the System clock frequency, AHB/APBx prescalers and Flash settings */ SetSysClock(); }
时钟配置的重点在函数SetSysClock()中;
使能HSE并等待HSE初始化完成;
若HSE初始化完成后用HSE配置PLL时钟,并将PLL输出时钟作为系统时钟SYSCLK;
/** * @brief Configures the System clock frequency, AHB/APBx prescalers and Flash * settings. * @note This function should be called only once the RCC clock configuration * is reset to the default reset state (done in SystemInit() function). * @param None * @retval None */ static void SetSysClock(void) { __IO uint32_t StartUpCounter = 0, HSEStatus = 0; /******************************************************************************/ /* PLL (clocked by HSE) used as System clock source */ /******************************************************************************/ /* SYSCLK, HCLK, PCLK configuration ----------------------------------------*/ /* Enable HSE */ RCC->CR |= ((uint32_t)RCC_CR_HSEON); /* Wait till HSE is ready and if Time out is reached exit */ do { HSEStatus = RCC->CR & RCC_CR_HSERDY; StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; } if (HSEStatus == (uint32_t)0x01) { /* Enable Prefetch Buffer and set Flash Latency */ FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY; /* HCLK = SYSCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; /* PCLK = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1; /* PLL configuration */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL)); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL6); /* Enable PLL */ RCC->CR |= RCC_CR_PLLON; /* Wait till PLL is ready */ while((RCC->CR & RCC_CR_PLLRDY) == 0) { } /* Select PLL as system clock source */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; /* Wait till PLL is used as system clock source */ while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL) { } } else { /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */ } }
而在核心时钟HCLK发生变化时,需要调用SystemCoreClockUpdate()函数来更新SystemCoreClock值,以便调整使用了该参数的配置;
如果SYSCLK源为HSI,则SystemCoreClock=HSI_VALUE;(HSI_VALUE默认为8MHz,实际值会随电压和温度变化而变化)
如果SYSCLK源为HSE,则SystemCoreClock=HSE_VALUE;(HSE_VALUE需要确保和实际所使用的外接晶振频率相同,否则此功能可能会失效)
如果SYSCLK源为PLL,则SystemCoreClock= HSE/HSI_VALUE与PLL因子的计算;
如果SYSCLK源不确定,默认为HSI时钟源;
最后考虑HCLK的分频系数;
void SystemCoreClockUpdate (void) { uint32_t tmp = 0, pllmull = 0, pllsource = 0, prediv1factor = 0; /* Get SYSCLK source -------------------------------------------------------*/ tmp = RCC->CFGR & RCC_CFGR_SWS; switch (tmp) { case 0x00: /* HSI used as system clock */ SystemCoreClock = HSI_VALUE; break; case 0x04: /* HSE used as system clock */ SystemCoreClock = HSE_VALUE; break; case 0x08: /* PLL used as system clock */ /* Get PLL clock source and multiplication factor ----------------------*/ pllmull = RCC->CFGR & RCC_CFGR_PLLMULL; pllsource = RCC->CFGR & RCC_CFGR_PLLSRC; pllmull = ( pllmull >> 18) + 2; if (pllsource == 0x00) { /* HSI oscillator clock divided by 2 selected as PLL clock entry */ SystemCoreClock = (HSI_VALUE >> 1) * pllmull; } else { prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1; /* HSE oscillator clock selected as PREDIV1 clock entry */ SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull; } break; default: /* HSI used as system clock */ SystemCoreClock = HSI_VALUE; break; } /* Compute HCLK clock frequency ----------------*/ /* Get HCLK prescaler */ tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)]; /* HCLK clock frequency */ SystemCoreClock >>= tmp; }
另一中延时函数的配置方法,是不利用中断来实现的
首先初始化SysTick时钟
这里直接将SysTick时钟源设置为HCLK的8分频;
由于这里SYSCLK=SystemCoreClock=168MHz,因此SysTick时钟为168MHz/8=21MHz;
因此要延时1us=10^(-6)s=21/21M,此时的重装值为21=SYSCLK/8=fac_us;
要延时1ms=10^(-3)=21k/21M,此时的重装值为21k=1000*fac_us= fac_ms;
void delay_init(u8 SYSCLK) { SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); fac_us=SYSCLK/8; fac_ms=(u16)fac_us*1000; }
延时1us到延时1ms的函数
流程大致如下:将x*fac_us加载入重装寄存器LOAD;
清空计数寄存器VAL;
通过CTRL寄存器,开启SysTick时钟;
等待计数完成,关闭SysTick时钟,清空计数寄存器VAL;
void delay_us(u32 nus) { u32 temp; SysTick->LOAD=nus*fac_us; SysTick->VAL=0x00; SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; do { temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; SysTick->VAL =0X00; } void delay_xms(u16 nms) { u32 temp; SysTick->LOAD=(u32)nms*fac_ms; SysTick->VAL =0x00; SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; do { temp=SysTick->CTRL; }while((temp&0x01)&&!(temp&(1<<16))); SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; SysTick->VAL =0X00; } void delay_ms(u16 nms) { u8 repeat=nms/540; u16 remain=nms%540; while(repeat) { delay_xms(540); repeat--; } if(remain)delay_xms(remain); }
2019-7-18更新
从STM32Cube生成的代码已经默认包含了延时功能,但是仅用于实现1ms的延时,其实现是通过系统自带的配置:
/** * @brief This function handles System tick timer. */ void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ /* USER CODE END SysTick_IRQn 1 */ }
然后延时的功能采用库函数实现HAL_Delay()来实现:
可以实现延时1ms的功能;
/** * @brief This function provides accurate delay (in milliseconds) based * on variable incremented. * @note In the default implementation , SysTick timer is the source of time base. * It is used to generate interrupts at regular time intervals where uwTick * is incremented. * @note ThiS function is declared as __weak to be overwritten in case of other * implementations in user file. * @param Delay specifies the delay time length, in milliseconds. * @retval None */ __weak void HAL_Delay(__IO uint32_t Delay) { uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay; /* Add a period to guarantee minimum wait */ if (wait < HAL_MAX_DELAY) { wait++; } while((HAL_GetTick() - tickstart) < wait) { } }