HAL库之SYSTICK时钟频率探究-F407

最近一直想用系统滴答定时器来做一个us级别的延时,用正点原子和其他的一些函数库都试了一下,最终的结果都不尽人意,要不然就是延时进不来,要不然就是频率错误,自己在示波器的帮助下,搞定了一部分内容,目前能够正常使用,产生1us的延时。

至于直接在函数库里修改系统配置文件,有以下担心,所以一直没有操作。

HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);//此配置为HAL库自带配置,延时为1MS;

HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000000);//此配置为硬石HAL教程推荐的方法,延时为1US。

这样虽然调用HAL_DELAY() 函数能够正常产生1US的延时,但是毕竟修改了系统内置的配置,系统在初始化和调用一些外设的时候还是会自己用到这个延时函数,导致延时函数变化,可能会产生不可排查的BUG(虽然目前还没发现那个地方调用了此延时函数会发生BUG,总归还是小心点的好)。因此排除此方法。

自己在查看正点原子的教程的时候发现,正点原子的教程里面介绍SYSTICK的时钟为调用外部高速时钟(HSE)的时候时钟为HCLK的1/8,或者调用内置高速时钟(HSI)的时候为FCLK(这里面自己没理解好正点原子的意思,具体原因下面有讲解)

代码如下:

//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{

 	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //配置systick的时钟源
	fac_us=SYSCLK/8;						//不论是否使用OS,fac_us都需要使用

	fac_ms=(u16)fac_us*1000;				//非OS下,代表每个ms需要的systick时钟数   

}								    

以上代码删除了支持OS的部分,这样看起来比较简洁。

再来看配置时钟源的函数

/**
  * @brief  Configures the SysTick clock source.
  * @param  SysTick_CLKSource: specifies the SysTick clock source.
  *   This parameter can be one of the following values:
  *     @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.//当函数选择SysTick_CLKSource_HCLK_Div8的时候,systick时钟=AHPCLOCK(也就是HCLK)/8
  *     @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.
        //当函数选择SysTick_CLKSource_HCLK的时候,systick时钟=AHBclock(也就是FCLK)
  * @retval None
  */
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}
再来看 SysTick_CLKSource_HCLK_Div8的定义
#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
也就是说8分频的时候,将0xFFFFFFFB赋值(进行与运算)到SysTick->CTRL
再来看SysTick_CLKSource_HCLK 的定义
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
也就是将0x00000004赋值(或运算)到)SysTick->CTRL

8分频的结果就是将CTRL的2位段置0,从而选择称为外部时钟源。

不分频 的结果就是将CTRL的2位段置1,从而选择称为内部时钟源。

注:这个内部和外部时钟源不是HSE 和HSI,而是针对系统时钟HCLK而言的。

此图来源于STM32F4XX中文参考手册第107页,正确的标识了两种时钟的区别

下面解释一下这个所谓的内部和外部时钟

系统时钟SYSCLK最大频率为168MHz,它是供STM32中绝大部分部件工作的时钟源。系统时钟可由PLL、HSI或者HSE提供输出,并且它通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用:

①、送给AHB总线、内核、内存和DMA使用的HCLK时钟。
②、分频后送给STM32芯片的系统定时器时钟(Systick=Sysclk/8=9Mhz)
③、直接送给Cortex的自由运行时钟(free running clock)FCLK。【ARMJISHU注:FCLK 为处理器的自由振荡的处理器时钟,用来采样中断和为调试模块计时。在处理器休眠时,通过FCLK 保证可以采样到中断和跟踪休眠事件。 Cortex-M3内核的“自由运行时钟(free running clock)”FCLK。“自由”表现在它不来自系统时钟HCLK,因此在系统时钟停止时FCLK 也继续运行。FCLK和HCLK 互相同步。FCLK 是一个自由振荡的HCLK。FCLK 和HCLK 应该互相平衡,保证进入Cortex-M3 时的延迟相同。】

④、送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4使用。
⑤、送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器1使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。

注意:STMCUBEMX软件中的时钟图有一个地方容易误导大众,

因此这时候终于搞明白这两个所谓的内部和外部时钟,

当使用HAL库默认的情况时,  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

此时systick采用的时钟为FCLK,频率为168MHZ,因此配置us定时器的LOAD值应该为168*nus,

//延时nus
//nus为要延时的us数.	
//注意:nus的值,不要大于2^24/168=99864us
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*168; //时间加载	  		 
	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;       //清空计数器	 
}

当需要修改systick的时钟源为HCLK的1/8时,HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8);

此时systick的时钟源为1/8的HCLK,频率为168/8=21MHZ,因此定时器LOAD值应为21*nus

//延时nus
//nus为要延时的us数.	
//注意:nus的值,不要大于2^24/21=798915us
void delay_us(u32 nus)
{		
	u32 temp;	    	 
	SysTick->LOAD=nus*21; //时间加载	  		 
	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;       //清空计数器	 
}

下面贴一下,在正常HAL库操作生成的代码中,如何编写延时函数,达到延时1us的目的

主函数如下

#include "main.h"
#include "stm32f4xx_hal.h"
#include "adc.h"
#include "i2c.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"

/* USER CODE BEGIN Includes */
#include "delay.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR = (uint8_t) ch;      
	return ch;
}
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  *
  * @retval None
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
	delay_init();               //在系统设置玩时钟之后,初始化一下自己写的延时函数
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_USART1_UART_Init();
  MX_RTC_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	
	delay_us(50000);  //延时50ms,也就是一个周期为100ms,z指示灯翻转1次
	HAL_GPIO_TogglePin(GPIOA, LED2_Pin);
	  
	
	  //HAL_Delay(1000);
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
	  

  }
  /* USER CODE END 3 */

}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;

    /**Configure the main internal regulator output voltage 
    */
  __HAL_RCC_PWR_CLK_ENABLE();

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
  PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Enables the Clock Security System 
    */
  HAL_RCC_EnableCSS();

    /**Configure the Systick interrupt time 
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick 
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @param  file: The file name as string.
  * @param  line: The line in file as a number.
  * @retval None
  */
void _Error_Handler(char *file, int line)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  while(1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{ 
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/**
  * @}
  */

/**
  * @}
  */

延时函数如下:

#include "delay.h"
#include "sys.h"
////////////////////////////////////////////////////////////////////////////////// 	 

static u8  fac_us=0;//us延时倍乘数			   
static u16 fac_ms=0;//ms延时倍乘数,
			   
//初始化延迟函数
//SYSTICK的时钟等于FCLK时钟=168MHZ
//SYSCLK:系统时钟
void delay_init(void)
{
	
	fac_us=168;		//因为HAL库函数定义的默认值为 168MHZ
	
	fac_ms=(u16)fac_us*1000;//   

}								    


//延时nus
//nus为要延时的us数.	
//注意:nus的值,不要大于99864us
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;       //清空计数器	 
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=2^24/168/1000=99ms 
void delay_xms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
	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;       //清空计数器	  	    
} 
//延时nms 
//nms:0~65535
void delay_ms(u16 nms)
{	 	 
	u8 repeat=nms/99;	//这里用99,是考虑到大部分人不会超频使用,如果超频使用,请自己自行修改
						//比如超频到248M的时候,delay_xms最大只能延时67ms左右了
	u16 remain=nms%99;
	while(repeat)
	{
		delay_xms(99);
		repeat--;
	}
	if(remain)delay_xms(remain);
	
} 

			 

以上配置是CUBEMX默认的配置,大家在使用的过程中注意us的使用限制范围就行了0-99864us

假如想使用8分频的HCLK,作为信号源,一下是主程序和延时程序的代码,进攻参考

主程序只需要修改
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8);
 // HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

延时字程序如下

#include "delay.h"
#include "sys.h"
////////////////////////////////////////////////////////////////////////////////// 	 
//////////////////////////////////////////////////////////////////////////////////  
////////////////////////////////////////////////////////////////////////////////// 
 
static u8  fac_us=0;//us延时倍乘数			   
static u16 fac_ms=0;//ms延时倍乘数


			   
//初始化延迟函数
//SYSTICK的时钟等于HCLK的1/8时钟=168/8=21MHZ
//SYSCLK:系统时钟
void delay_init(void)
{
	
 	
	fac_us=21;		         
 
	fac_ms=(u16)fac_us*1000;

}								    



//延时nus
//nus为要延时的us数.	
//注意:nus的值,不要大于798915us
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;       //清空计数器	 
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对168M条件下,nms<=798ms 
void delay_xms(u16 nms)
{	 		  	  
	u32 temp;		   
	SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
	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;       //清空计数器	  	    
} 
//延时nms 
//nms:0~65535
void delay_ms(u16 nms)
{	 	 
	u8 repeat=nms/540;	//这里用540,是考虑到某些客户可能超频使用,
						//比如超频到248M的时候,delay_xms最大只能延时541ms左右了
	u16 remain=nms%540;
	while(repeat)
	{
		delay_xms(540);
		repeat--;
	}
	if(remain)delay_xms(remain);
	
} 


现在对比完毕了,两种方式各有优缺点,

1.使用HCLK的1/8作为时钟源的话,在us延时函数里面,延时范围是798915us,远大于用FCLK的99864us。

2.使用FCLK作为时钟源的话,好处是在系统时钟停止时FCLK 也继续运行,此时systick时钟依然运行着。

猜你喜欢

转载自blog.csdn.net/DINGDING_GO/article/details/88169264