1.时钟系统
基本概念
时钟是嵌入式系统的脉搏,(在电子系统中,时钟信号可以被理解为一个周期性的信号,他确定了系统的节奏和时间基准)处理器内核在时钟的驱动下完成各项工作。比如:串口通信(异步通信,每一个设备都有自己单独的时钟系统)、AD转换、定时器计数...
因此时钟对于计算机系统是非常重要的,如果时钟系统出现问题往往也是致命的,比如振荡器不起振,震荡不稳、停振等。时钟信号推动单片机内各个部分执行相应的指令,时钟就像人的心跳一样。
时钟系统的作用:通信同步,时序控制,计时功能
时钟系统的组成
振荡器,唤醒定时器,倍频器,分频器
振荡器:产生时钟信号的源头
唤醒定时器:使能,关闭
倍频器:放大时钟频率
分频器:减小时钟频率
常见的振荡器
振荡器主要分为:晶体振荡器,RC振荡器,LC振荡器
晶体振荡器
晶体振荡器是一种电子元件,用于在电路中提供准确的时钟信号。它由一个晶体振荡器和相应的驱动电路组成,通常用于数字电路、微控制器、计算机等系统中。
晶体振荡器使用石英晶体作为震荡元件。石英晶体具有压电效应,当施加电厂时,会以固定频率产生机械振动。晶体振荡器利用石英晶体的这种特性,通过电子电路驱动晶体振荡的频率稳定性非常高。
优点:晶体振荡器信号的稳定性和精度较高,连接方式简单
缺点:价格高,需要较长的起振时间。
RC振荡器
RC振荡器使用电阻和电容构成一个简单的振荡回路。当电容充放电达到某个阈值时,会产生周期性的震荡信号。RC振荡器通常用于低频、中频等信号的产生和调制。
优点:成本低,由电容和电阻组成,起振时间短
缺点:精度低,不稳定,容易受到环境因素的影响
LC振荡器
LC振荡器使用电感容器构成一个震荡回路。在LC震荡回路中,电感和电容器通过能量交换产生周期性的震荡信号,通常同于射频、高频等电路信号产生。
LC振荡器的频率稳定性较好,但相比较RC振荡器,其结构复杂,尺寸较大。
总结:
RC振荡器和LC振荡器是简单的振荡器,适用于一些低要求的应用场景,而晶体振荡器则具有更高的频率稳定性和精度,适用于对时钟信号要求较高的电子系统
倍频器
cpu需要更高的频率,但是晶体振荡器的制作成本较高(而且频率越高,自身的频率也不稳定),不适合直接生产高振荡器,所以需要用到倍频器对现有的时钟频率进行放大。
分频器
外设需要不同的频率,为了降低功耗,进行分频,以保证不同的时钟信号
STM32U5的时钟源
在MX中如何开启外部时钟
时钟树
时钟树的作用:时钟树(Clock Tree)是数字电路(尤其是集成电路和系统级芯片,SoC)中的关键组成部分,其核心作用是为芯片内部各个模块提供稳定、同步的时钟信号,确保数据在正确的时间被处理和传输。
MX中配置时钟树
2.SysTick定时器
概念
SysTick又称滴答定时器。是一个定时设备,位于Cortex-M33内核中,和NVIC捆绑(可以产生中断信号),产生SysTick异常,可以对输入的时钟进行计数,系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。
频率(f):单位:HZ 代表每秒钟振荡的次数1MHZ = 1000KHZ = 1000 000HZ
周期(T):单位s 代表振荡一次需要的时间
1s = 1000ms = 1000 000us
二者的关系: T = 1/f
1KHZ代表每秒钟振荡1000下,振荡一次需要 1/1000 = 1ms
工作原理
滴答定时器本质上就是一个24位递减计数器,也就是最大计数是2^24 - 1(0xFFFFFF)。 在给Systick设定初值后,每当到来一个时钟信号,计数值则减1,直到计数值减到0时,触发一次异常事件,处理异常服务程序,处理完成后计数器再自动重装初值并继续减一,依次循环。
比如现在输入的时钟频率为1MHZ,定时1ms如何配置自动重载数值寄存器?
1MHZ-》每秒钟振荡1000 000 次-》震荡一次需要1us-》1ms-》振荡1000次,我们只需要让计数器计数1000个数-》自动重载数值寄存器1000-1;
时钟频率:16MHZ 计时1ms16MHZ = 16000000次-》震荡一次1/16us-》1us16次
1ms = 16000次 计数 16000-1
重载数值寄存器:16000-1
32MHZ 200ms
32MZ-》32000000次 -》1次1/32us-》1us-》32次
1ms-》32000次 200ms-》6400000 计数 6400000-1
重载数值寄存器:6400000-1
滴答定时器的源码分析
滴答定时器是系统默认开启的
1.打开keil5初始化函数接口
2.配置滴答定时器
3.配置滴答定时器的中断
4.时钟频率:4MHZ 定时时间1ms
4MHZ-》1/4us 1us-》4次 1ms 4000次 重载数值寄存器 4000-1
5.分析验证
先找出参数中各个变量的值
SystemCoreClock = 4000000U
第二个变量
uwTickFreq= 1U
SystemCoreClock / (1000UL / (uint32_t)uwTickFreq
4000000 / 1000 / 1 = 4000;
这个参数现在我们算出来为4000;
6.接下来我们找函数
经过我们的验证发现,结果是成立的。
到此我们又出现了一个新的问题,每间隔1ms产生一次中断,中断服务程序中干了什么呢?
7.查找中断向量表,找到滴答定时器服务程序的入口
8.入口函数的中断回调
9.目的
我们找到中断的目的去是每间隔1ms让uWTick自加1。
uwTick:在滴答定时器(SysTick)中,uwTick 是一个关键的全局变量(通常定义为 volatile uint32_t),其核心作用是记录系统启动后的毫秒级时间累计值,为嵌入式系统提供精确的时间基准。
利用HAL_GetTick实现1S的延时
3.定时器
滴答定时器在内核里面(ARM公司设计)定时器(TIM)(ST芯片厂商设计)
基本概念
定时器类似于我们生活中的闹钟,可以设定一个时间来提醒我们。定时器可以作为基本的定时器/计数器,用于生成周期性的定时中断或作为计数器来计数外部事件的脉冲数。例如1秒钟进入定时器中断干一件事,定时器可以配置为产生脉冲宽度调制(PWM)信号,用于控制电机速度、LED亮度调节,风扇风速调节,等应用。
定时器是存在于STM32U5单片机中的一个外设,总共有11个定时器,分别是2个高级定时器(TIM1,TIM8)7个通用定时器(TIM2,TIM3,TIM4,TIM5,TIM15,TIM16,TIM17)和2个基本定时器(TIM6,TIM7)
定时功能的基本框图
时基单元
三大件:PSC(预分频器),ARR(自动重装载寄存器)CNT(计数器)
预分频器
自动重装载寄存器
计数器
向上计数模式:计数器从0开始计数,当达到自动装载寄存器(TIMx_ARR)里的值时,自动清零且产生一个溢出事件,然后再从0开始向上计数。
向下计数模式:计数器从自动装载寄存器(TIMx_ARR)里的值开始递减计数,当计数值达到0时产生一个定时器溢出事件,并重装初值,继续向下计数。
中央对齐模式:又称为向上/向下计数,计数器从0开始递增达到ARR的值,产生一个定时器溢出事件,再从ARR的值递减到0,产生一个定时器溢出事件。
定时时间的配置
定时时间(S) = 预分频系数+1 * 自动重载寄存器+1 / 时钟频率(HZ)
时钟频率:32MHZ 计时 10ms 应该如何进行配置?
0.01S = ? * ? / 32000000
? * ? = 320000
PSC:1000 -1
ARR:320 -1
练习
72MHZ 计时 500ms
ARR:36000-1
PSC:1000-1
4MHZ 计时 200ms
ARR:800-1
PSC:1000-1
16MHZ 计时 700ms
ARR:11200-1
PSC:1000-1
定时器实验
实验要求
每间隔1S切换一次蜂鸣器的状态
1.寻找想要使用的寄存器🔗的总线
2.观察时钟频率
定时时间 :1S 时钟频率:4MHZ PSC:1000-1 ARR:4000-1
MX配置
1.配置定时时间
2.开启中断
3.开启蜂鸣器
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* Prevent unused argument(s) compilation warning */ UNUSED(htim); /* NOTE : This function should not be modified, when the callback is needed, the HAL_TIM_PeriodElapsedCallback could be implemented in the user file */ }1.开启定时器
2.回调函数
4.PWM
定义
PWM,全称为脉冲宽度调制(Pulse Width Modulation),是一种调节信号的方法。简单来说,PWM 就是通过改变信号的高电平和低电平的时间比例来控制输出的平均电压或功率。
想象一下,我们有一个周期性的信号,其中高电平表示“开”状态,低电平表示“关”状态。PWM 就是在一个固定的时间周期内,通过改变高电平和低电平的持续时间来控制信号的特性。例如,如果我们将高电平持续时间设为较长,低电平持续时间设为较短,那么平均电压或功率就会比较高。相反,如果高电平持续时间设为较短,低电平持续时间设为较长,平均电压或功率就会比较低。
通过不断地调整高电平和低电平的持续时间比例,我们可以得到不同的输出效果。这种方法常用于控制电机的速度、调光灯的亮度、音频设备的音量等。
PWM参数
周期:高低电平变化所需要的时间 T=1/f
频率:在1秒内,信号变化(高电平到低电平再回到高电平)的次数,也就是PWM在1秒钟有多少周期,Hz.
占空比: 在一个脉冲周期内,高电平的时间占整个周期时间的比例,单位是% (0%-100%)。
输出的平均电压 = 输入的额定电压 X 占空比
PWM生成的原理
- 输入滤波器和边沿检测器:平滑时钟曲线,改变信号的质量,精准的控制时间,准确的检测时钟的边沿,确保PWM波形的周期和占空比的正确性。
- 预分频器:对过滤好的时钟信号进行分频,生成计数时钟,各分频系数由PSC寄存器来决定,可以调整计数频率。
- 捕获比较寄存器:定时器中有多个捕获比较寄存器,用于存储比较的值,当计数器的值与捕获比较寄存器的值相匹配的时候就会触发相应的输出控制。
- 输出控制:每一个捕获比较寄存器都会对应输出通道,输出控制模块根据计数器和捕获比较寄存器的值以及设置好的输出设置,来生成PWM信号。
常见的PWM生成模式包括
下面我们以PWM模式1的向上计数进行理解
输出过程
- 当0-t1这段时间,计数器寄存器的CNT的值是小于CCR,输出高电平。
- 当t1-t2这段时间,计数器寄存器的CNT的值是大于CCR且小于ARR的,输出低电平。
PWM模式2
PWM实验
实验要求
使用按键控制风扇的转速(PWM)
MX配置
1.按键的引脚(PC9)配置为外部中断
2.配置风扇PWM模式
代码编写
练习:控制一下马达,蜂鸣器,加上数码管。
定时炸弹,使用定时器,每间隔1S数码管-1,初值为10,减到0,蜂鸣器滴滴响。
/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2026 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "spi.h" #include "tim.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void SystemPower_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ int count = 11; int shiwei,gewei; //定义一个保存了所有数字显示状态的数组 const uint8_t number[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //数字显示 //数码管显示函数 void led_dispaly(uint8_t wei,uint8_t num) { uint8_t which_where_data[2] = {0x00, 0x00}; //创建一个用于保存段选与位选的数组 which_where_data[0] = wei; which_where_data[1] = number[num]; HAL_SPI_Transmit(&hspi2,which_where_data,2,10); //进行锁存操作,等效于写入一高一低电平 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET); } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ 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(); /* Configure the System Power */ SystemPower_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI2_Init(); MX_TIM16_Init(); MX_TIM17_Init(); /* USER CODE BEGIN 2 */ //开启定时器 HAL_TIM_Base_Start_IT(&htim17); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { shiwei = count / 10; gewei = count % 10; led_dispaly(0x01,shiwei); led_dispaly(0x02,gewei); /* 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 = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE4) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI; RCC_OscInitStruct.MSIState = RCC_MSI_ON; RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT; RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; 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_CLOCKTYPE_PCLK3; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) { Error_Handler(); } } /** * @brief Power Configuration * @retval None */ static void SystemPower_Config(void) { /* * Disable the internal Pull-Up in Dead Battery pins of UCPD peripheral */ HAL_PWREx_DisableUCPDDeadBattery(); /* USER CODE BEGIN PWR */ /* USER CODE END PWR */ } /* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim17) { HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_15); count--; if(count == 0) { HAL_TIM_Base_Stop(&htim17); HAL_TIM_Base_Start_IT(&htim16); } } if(htim == &htim16) { HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_15); } } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); 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, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */