第4课【STM32的时钟】时钟 时钟源 内外部时钟 高低速时钟

基本知识框架

在这里插入图片描述

课堂笔记

时钟

什么是时钟?

时钟是单片机内部最核心的器件,它可以向所有元器件提供时钟信号。元器件要依赖于时钟信号,才能有条不紊地工作。所以时钟对于单片机正如人的脉搏一样重要

时钟的作用

同步状态:在提供时钟信号时,元器件才会工作。所以一致且统一的时钟信号,可以让元器件相互配合、步调统一地工作
降低功耗:既然元器件的工作依赖于时钟信号,所以可以根据不同的的元器件匹配不同的时钟频率,不需工作的元器件不提供时钟信号,达到降低功耗的目的

时钟源

能够主动发出时钟信号的元器件,可以用作时钟源。STM32中有四个时钟源,还有一个辅助时钟源生成倍频时钟信号的器件锁相环

HSE 外部高速时钟

时钟信号由外部晶振提供,时钟频率一般在4-16MHz,是经常会用到的时钟源

这里的外部晶振可以是无源晶振,也可以是有源晶振。无源晶振严格来讲就是晶体,需要额外的时钟电路,才能起振从而提供时钟信号,有源晶振已经将晶体和时钟电路封装好,使用时无需额外操作

HSI 内部高速时钟

时钟信号由内部RC振荡电路提供,时钟频率为8MHz,但是这个时钟频率会随着温度产生漂移,很不稳定,所以一般不使用此时钟信号

LSE 外部低速时钟

时钟信号由外部晶振提供,时钟频率一般为32.768KHz,这个信号一般用于RTC实时时钟

LSI 内部低速时钟

时钟信号由内部RC振荡电路提供,时钟频率一般为40KHz,这个信号一般用于独立看门狗时钟

PLL锁相环

PLL锁相环是辅助产生时钟信号的器件。将时钟信号输入锁相环,锁相环可以将这个时钟信号的频率按照指定倍率提高(倍频)之后,再输出

与锁相环具有相反作用的是分频器,分频器可以将输入时钟信号分率按照指定倍率降低之后,再输出

主要时钟和其他时钟

通过锁相环和分频器,时钟信号可以有更多的变化,根据不同的元器件提供不同的时钟信号。可以通过时钟树直观的看到,原始时钟信号经过处理,最终生成了元器件所需的时钟信号

以下是RCC的框图(RCC:Reset Clock Control 复位时钟控制),里面包含了主要时钟(1-7)和其他时钟(A-E)
在这里插入图片描述

主要时钟

单独分析和了解时钟的过程,是很枯燥的。结合某条特定时钟树去讲解时钟会更易于理解,也使得这个认知的过程更有趣

时钟树是一个用于时钟信号传递的网状结构,通过时钟树,可以将时钟源的时钟信号传递到所需要的外设处。现在通过最基础的时钟树(路径如上图中1-6)去验证之前的解析,基础时钟树的最终目标是配置好系统时钟SYSCLK(72MHz)以及外设时钟PCLK1(32MHz),PCLK2(72MHz)

HSE 与 PLLXTPRE

HSE是外部高速时钟,时钟信号由外部晶振提供。HSE时钟信号将会传递给PLLXTPRE,这个过程可以选择将HSE时钟信号进行1分频(不分频)或者2分频处理,之后PLLXTPRE会将选择的时钟信号再传递给PLLSRC。基础时钟树中,HSE提供最常用的8MHz时钟信号,传递到PLLXTPRE的时钟信号选择1分频,依旧是8MHz

PLLSRC

PLLSRC的输入时钟信号可以选择PLLXTPRE(其实也就是HSE)或者是进行2分频后的HSI时钟信号。由于HSI的时钟信号容易漂移,基础时钟树中,输入时钟信号选择HSE,输出时钟信号直接传递给PLLMUL

PLLMUL / PLLCLK

PLLMUL其实就是PLL锁相环,可以对时钟信号进行倍频处理,倍频后得到的信号就是PLL时钟信号PLLCLK,基础时钟树中选择8倍频输出,也就是倍频到8MHz*9=72MHz后,输出PLLCLK时钟信号传递给系统时钟SYSCLK

SYSCLK

可以从图中看到,系统时钟SYSCLK可以有很多的输入时钟信号(通过Switcher进行选择),包括HSI,HSE或是PLLMULL。基础时钟树中,选择PLLCLK作为输入时钟信号,到这里,系统时钟SYSCLK就被配置为72MHz(最高频率),基础时钟树的任务完成了1/3,之后SYSCLK时钟信号会被传递给AHB预分频器

AHB / HCLK

系统时钟SYSCLK经过AHB分频器进行分频操作,得到总线时钟HCLK。基础时钟树中,经过AHB分频器1分频后,得到总线时钟HCLK,时钟频率依旧为72MHz,HCLK时钟信号会传递给APB1,APB2预分频器

APB1 / PCLK1

因为APB1总线上挂载的都是低速外设,所以HCLK时钟信号还经过APB1预分频器要进行2分频,得到低速外设时钟信号PCLK1,时钟频率为36MHz

APB2 / PCLK2

因为APB1总线上挂载的都是高速外设,所以HCLK时钟信号还经过APB2预分频器进行1分频后,得到高速外设时钟信号PCLK2,时钟频率为72MHz

至此,基础时钟树的任务圆满完成,STM32内核开始正常运行,同时外设总线上的外设也开始工作

其他时钟

通过时钟树已经能对主要时钟以及主要时钟之间的联系有了一定的理解。主要时钟以外的其他时钟,较为重要的,可以一一列举出来讲解:

USB时钟

USB时钟信号是通过PLLCLK时钟信号经过USB预分频器得到,分频因数可以是1或者1.5,USB时钟信号的频率为48MHz,这就意味着PLLCLK时钟信号的频率只能48MHz或者72MHz。由于USB时钟信号对频率的要求较高,所以只能由HSE经过PLL倍频后时钟信号提供,不能由HSI提供

Cortex系统时钟

Cortex系统时钟信号由HCLK时钟信号经过8分频处理后得到,也就是72MHz / 8 = 9MHz,这个时钟信号提供给系统内核SysTick定时器,SysTick定时器一般用于操作系统的时钟节拍,也可以用于普通定时器

ADC时钟

ADC时钟信号是由PCLK2时钟信号经过ADC预分频器处理后的得到,分频因数可以是2,4,6,8。ADC时钟信号频率最高为14MHz,反推PCLK2时钟信号的频率只能是28MHz,56MHz,因为PCLK2时钟信号的频率最高为72MHz

RTC时钟 / 独立看门狗时钟

RTC时钟信号可以由HSE或者HSI提供(HSE提供RTC时钟信号的话需要经过128分频处理),也可以由LSE时钟信号提供,时钟频率为32.768KHz。而独立看门狗时钟信号只能由LSI提供,LSI时钟信号是内部低速时钟信号,频率在30~60KHz之间不等,一般取40KHz

MCO时钟输出

MCO(MicroController Clock Output微控制器时钟输出)时钟输出,可以在STM32 F1的PA8引脚,通过复用,对外输出时钟信号,作为一个有源晶振来使用,对外输出的时钟信号可以来自PLLCLK/2、HSI、HSE或者SYSCLK。此外通过输出的时钟信号,来观察与判断内部时钟是否配置正确

配置系统时钟实验

在介绍时钟信号传递流程时,硬件上通过时钟树进行描述;软件上通过启动文件中的SystemInit()函数进行配置,SystemInit函数的定义在system_stm32f10x.c中。通过修改此文件相关内容,可以修改系统时钟,但为了保持库文件的完整性,一般不去做这样的修改,而是另外通过自定义函数来实现

自定义的RCC系统时钟配置函数流程大致为

  1. 定义晶振结构体,时钟结构体
  2. 初始化晶振结构体,选择HSE,并设置分频因子
  3. 设置PLL
  4. 等待晶振初始化
  5. 初始化时钟结构体,设置SYSCLK,HCLK,PCLK1,PCLK2
  6. 等待时钟初始化完成

时钟驱动文件共有两个,bsp_clkconfig.h和bsp_clkconfig.c
bsp_clkconfig.c代码主体部分实现

/*
 * bsp_clkconfig.c
 *
 *  Created on: 2021年10月17日
 *      Author: 67566
 */

#ifndef BSP_CLKCONFIG_C_
#define BSP_CLKCONFIG_C_

#include "bsp_clkconfig.h"

extern void Error_Handler(void);

void HSE_SetSysClock(void)
{
	// Define RCC OscInitTypeDef & ClkInitTypeDef Structure
	RCC_OscInitTypeDef RCC_OscInitStruct = {0};
	RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

	// Enabled HSE & PLL
	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
	RCC_OscInitStruct.HSEState       = RCC_HSE_ON;
	RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
	RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_ON;

	// Ensure HSE Work Properly
	if (HAL_OK != HAL_RCC_OscConfig(&RCC_OscInitStruct))
	{
		// Enable Clock
		RCC_ClkInitStruct.ClockType      = RCC_CLOCKTYPE_SYSCLK |
									  	   RCC_CLOCKTYPE_HCLK |
										   RCC_CLOCKTYPE_PCLK1 |
										   RCC_CLOCKTYPE_PCLK2;
		RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_HSE;
		RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;
		RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
		RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV16;
	}
	// // Ensure Clock Work Properly
	if (HAL_OK != HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0))
	{
		Error_Handler();
	}
}

void Delay(long long count)
{
	for (; count<0; count--)
	{}
}

#endif /* BSP_CLKCONFIG_C_ */

基本知识框架Xmind文件下载

链接:资源下载

#include "main.h" #include "stdio.h" #include "string.h" #include "time.h" UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); while (1) { time_t now = time(NULL); struct tm *timeinfo = localtime(&now); char time_str[9]; sprintf(time_str, "%02d:%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec); HAL_UART_Transmit(&huart1, (uint8_t *)time_str, strlen(time_str), HAL_MAX_DELAY); HAL_Delay(1000); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses 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_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET); /*Configure GPIO pin : PA9 */ GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } 在以上代码的基础上,编写代码以实现计算发送 hh:mm:ss到单片机,修改单片机时间
05-30
可以使用串口接收单片机发送过来的时间数据,然后通过C语言中的time()函数来设置单片机的时间。下面是示例代码: ``` #include "main.h" #include "stdio.h" #include "string.h" #include "time.h" UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 用于接收单片机发送过来的时间数据 char time_input[9]; while (1) { // 接收串口数据 HAL_UART_Receive(&huart1, (uint8_t *)time_input, 9, HAL_MAX_DELAY); // 将接收到的时间数据转换成struct tm格式 struct tm timeinfo = {0}; sscanf(time_input, "%d:%d:%d", &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec); // 设置单片机时间 time_t now = mktime(&timeinfo); if(now != -1) { if(stm32_settime(now) == 0) { printf("Time set successfully!\r\n"); } else { printf("Failed to set time.\r\n"); } } else { printf("Invalid time data.\r\n"); } } } // 设置单片机时间 int stm32_settime(time_t t) { struct tm *timeinfo = localtime(&t); RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; sTime.Hours = timeinfo->tm_hour; sTime.Minutes = timeinfo->tm_min; sTime.Seconds = timeinfo->tm_sec; sDate.WeekDay = timeinfo->tm_wday; sDate.Month = timeinfo->tm_mon + 1; sDate.Date = timeinfo->tm_mday; sDate.Year = timeinfo->tm_year - 100; if(HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) { return -1; } if(HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK) { return -1; } return 0; } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses 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_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_9, GPIO_PIN_RESET); /*Configure GPIO pin : PA9 */ GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 同时,需要在单片机上添加RTC实时时钟模块,并在CubeMX中进行配置。需要注意的是,使用RTC模块前需要先初始化时钟,可以使用RCC_OscConfig和RCC_ClkConfig函数来进行初始化。在设置时间时,需要将时间数据转换成RTC模块所需的格式,并调用HAL_RTC_SetTime和HAL_RTC_SetDate函数来设置时间和日期。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值