最近一直想用系统滴答定时器来做一个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库函数定义的默认值为 168MHZfac_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时钟依然运行着。

HAL库之SYSTICK时钟频率探究-F407相关推荐

  1. 正点原子STM32(基于HAL库)0

    目录 开发环境搭建与使用 常用开发工具简介 MDK 安装 仿真器驱动安装 CH340 USB 虚拟串口驱动安装 使用MDK5 编译例程 使用串口下载程序 使用DAP 下载与调试程序 使用DAP 下载程 ...

  2. 【STM32】HAL库-系统滴答定时器SysTick

    SysTick定时器被捆绑在NVIC中,是一个简单的定时器,对于CM3.CM4内核芯片,都有Systick定时器.Systick定时器常用来做延时,或者实时系统的心跳时钟.这样可以节省MCU资源,不用 ...

  3. [学习笔记]STM32F1 SYSTICK 滴答定时器(寄存器、标准库、HAL库)

    目录 0. 博主理解: 1. 实验内容及步骤: 2. 硬件说明 3. 寄存器说明 3.1 SysTick的时钟和使能: 3.2 SysTick重装载数值寄存器: 3.3 SysTick的中断优先级: ...

  4. 关于HAL库中系统嘀嗒时钟的简单探究

    在利用HAL库建立STM32工程时,系统嘀嗒时钟的初始化是在函数HAL_Init()中实现的: HAL_StatusTypeDef HAL_Init(void) {/* Configure Flash ...

  5. STM32F4 HAL库开发 -- STM32CubeMX

    一.STM32CubeMX 简介 STM32CubeMX 是 ST 意法半导体近几年来大力推荐的 STM32 芯片图形化配置工具, 允许用户使用图形化向导生成 C 初始化代码,可以大大减轻开发工作,时 ...

  6. hal库选择滴答时钟函数_stm32h7“理解hal库框架”

    1.按照初始化流程调用的hal库文件 完成初始化工作需要调用到的hal库文件,如下表 序号功能调用hal库文件 1Module Selection(模块选择) Oscillator Values ad ...

  7. STM32H7时钟树RCC分析--- HAL库配置(二)

    上一讲我们说了H7时钟树的一些基本概念,现在的话我们来用HAL库和CubeMx配置一下 再次说明,本文耗时较久,如果您想搞懂RCC初始化流程,请认真阅读,刚开始可能看不懂,但是仔细阅读之后绝对会有收获 ...

  8. STM32【H7】理论——综述、HAL库简述

    文章目录 1. STM32H7芯片简介 1.1 STM32H7与STM32F1.F4系列芯片的区别 1.2 硬件框图 1.3 STM32H7各型号对比 1.4 总线框图和时钟 1.5 AXI总线 1. ...

  9. STM32F103C8T6基础开发教程(HAL库)—点亮第一颗LED灯

    STM32F103C8T6基础开发教程目录 STM32F103C8T6基础开发教程(HAL库)-开发环境配置 STM32F103C8T6基础开发教程(HAL库)-Keil添加注释的快捷键 STM32F ...

最新文章

  1. pandas 中的函数—— .reset_index()
  2. 为什么孙悟空能大闹天宫,却打不过路上的妖怪?
  3. 无需另配定时器在STM32 HAL下实现微秒级延时(兼容FreeRTOS)
  4. (最短路 Floyd diskstra prim)Frogger --POJ--2253
  5. android实例教程_Android共享首选项示例教程
  6. redis srandmember_Redis五大数据类型使用场景
  7. VScode单步跟踪Nginx(虚拟机中搭建Nginx)源码
  8. mysql查询最接近的记录
  9. 2021-05-31驱动总裁万能网卡版
  10. 关于磁力计和加速度计的融合以及坐标系的对准
  11. 图片查看器-Python-tkinter
  12. 浏览器书签有效性验证
  13. shal()函数绕过和session验证绕过
  14. GIS开发:Contour(轮廓线)
  15. 非专业python学多久_非的解释|非的意思|汉典“非”字的基本解释
  16. TASKCTL4.1不同版本下载
  17. 松下PLC FP-XHC60T 程序 两个PLC通信控制11个轴 程序稳定已批量生产 注释完整 带威纶通触摸屏程序
  18. html转图片 workflow,用 Workflow + Day one 给未来的自己做时间履历 | Matrix 精选
  19. mysql 8 expire_logs_days 废弃 启用binlog_expire_logs_seconds设置binlog自动清除日志时间 阿里云RDS暂时不支持
  20. PostgreSQL的查询技巧: 零除, GENERATED STORED, COUNT DISTINCT, JOIN和数组LIKE

热门文章

  1. service_cmn
  2. Java进阶之--------集合2
  3. 短信发送平台-阿里大于
  4. python井字棋ai,python 井字棋(Tic Tac Toe)
  5. 阿丹学理财之P2P投资
  6. cocos 合成大西瓜思路
  7. html中数字效果,使用css实现电子数字效果
  8. 旅途——Python基本的“生存技能”
  9. windows引导过程以及多系统引导原理
  10. MySQL: 垂直分片