STM32F103——时钟配置
目录
1、认识时钟树
1.1 什么是时钟树
1.2 时钟系统解析
1.2.1 时钟源
1.2.2 锁相环PLL
1.2.3 系统时钟SYSCLK
1.2.4 时钟信号输出MCO
2、如何修改主频
2.1 STM32F1时钟系统配置
2.2 STM32F1 时钟使能和配置
下列进行举例的开发板是原子哥的战舰开发板STM32F103ZET6
1、认识时钟树
1.1 什么是时钟树
在学习时钟之前,我们得先要了解什么是时钟?
简单来说,时钟是具有周期性的脉冲信号,最常用的是占空比50%的方波。如下图:
时钟是单片机的脉搏,搞懂时钟走向及关系,对单片机使用至关重要。
时钟树概念:STM32F103的时钟树是指该微控制器的整体时钟系统架构。它由一系列时钟源、时钟分频器和时钟分配器组成,用于提供不同模块的时钟信号,并确保它们以正确的速率和时间关系运行。
为什么是时钟树而不是时钟呢?一个 MCU 越复杂,时钟系统也会相应地变得复杂,如 STM32F1 的时钟系统比较复杂,不像简单的 51 单片机一个系统时钟就可以解决一切。对于 STM32F1 系列的芯片,正常工作的主频可以达到 72Mhz,但并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及 RTC 只需要几十 kHZ 的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。由一系列时钟源通过时钟配置器提供各个模块时钟信号,犹如一颗树。如下图:
1.2 时钟系统解析
STM32F1 时钟系统图如下:
图中已经把我们主要关注几处标注出来。A 部分表示其它电路需要的输入源时钟信号;B 为一个特殊的振荡电路“PLL”,由几个部分构成;C 为我们重点需要关注的 MCU 内的主时钟“SYSCLK”;AHB 预分频器将 SYSCLK 分频或不分频后分发给其它外设进行处理,包括到 F 部分的 Cortex-M 内核系统的时钟。D、E 部分别为定时器等外设的时钟源APB1/APB2。G 是 STM32 的时钟输出功能。
1.2.1 时钟源
对于 STM32F1,输入时钟源(Input Clock)主要包括 HSI,HSE,LSI,LSE。其中,从时钟频率来分可以分为高速时钟源和低速时钟源,其中 HSI、HSE 高速时钟,LSI 和 LSE 是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE 和 LSE 是外部时钟源;其他是内部时钟源,芯片上电即可产生,不需要借助外部电路。
下面我们看看 STM32 的时钟源。
(1)2个外部时钟源:
高速外部振荡器 HSE (High Speed External Clock signal):外接石英/陶瓷谐振器,频率为4MHz~16MHz。正点原子STM32F103战舰开发板使用的是8MHz。
低速外部振荡器 LSE (Low Speed External Clock signal):外接 32.768kHz 石英晶体,主要作用于 RTC 的时钟源。(2)2个内部时钟源:高速内部振荡器 HSI(High Speed Internal Clock signal):由内部 RC 振荡器产生,频率为 8MHz。低速内部振荡器 LSI(Low Speed Internal Clock signal) :由内部 RC 振荡器产生,频率为 40kHz,可作为独立看门狗的时钟源。
芯片上电时默认由内部的 HSI 时钟启动,如果用户进行了硬件和软件的配置,芯片才会根据用户配置调试尝试切换到对应的外部时钟源,所以同时了解这几个时钟源信号还是很有必要的。如何设置时钟的方法会在后续提到。
1.2.2 锁相环PLL
锁相环是自动控制系统中常用的一个反馈电路,在 STM32 主控中,锁相环的作用主要有两个部分:输入时钟净化和倍频。前者是利用锁相环电路的反馈机制实现,后者我们用于使芯片在更高且频率稳定的时钟下工作。
在 STM32 中,锁相环的输出也可以作为芯片系统的时钟源。使用锁相环时只需要进行三个部分的配置。PLL 作为系统时钟源的配置部分图如下:
(1)PLLXTPRE:HSE 分频器作为 PLL 输入 (HSE divider for PLL entry)
在标注为①的地方,它专门用于 HSE,ST 设计它有两种方式,并把它的控制功能放在 RCC_CFGR 寄存器中。
PLLXTPRE:HSE分频器作为PLL输入(HSE divider for PLL entry)由软件置“1”或清“0”来分频HSE后作为PLL输入时钟。只能在关闭PLL时才能写入此位。
0:HSE不分频(1分频)
1:HSE 2分频
经过 HSE 分频器处理后的输出振荡时钟信号比直接输入的时钟信号更稳定。
(2)PLLSRC:PLL 输入时钟源 (PLL entry clock source)
图中②表示的是 PLL 时钟源的选择器。
PLLSRC:PLL输入时钟源(PLL entry clock source)由软件置“1”或清“0”来选择PLL输入时钟源。只能在关闭PLL时才能写入此位。
0:HSI振荡器时钟经2分频后作为PLL输入时钟
1:HSE时钟作为PLL输入时钟。
它有两种可选择的输入源:设计为 HSI 的二分频时钟,另一个是 A 处的 PLLXTPRE 处理后的 HSE 信号。
(3)PLLMUL:PLL 倍频系数 (PLL multiplication factor)
图中③所表示的配置锁相环倍频系数,同样地可以查到在 STM32F1 系列中,ST 设置它的有效倍频范围为 2~16 倍。
要实现 72MHz 的主频率,我们通过选择 HSE 不分频作为 PLL 输入的时钟信号,即输入 8Mhz,通过标号③选择倍频因子,可选择 2-16 倍频,我们选择 9 倍频,这样可以得到时钟信号为 8*9=72MHz。
1.2.3 系统时钟SYSCLK
STM32 的系统时钟 SYSCLK 为整个芯片提供了时序信号。我们已经大致知道 STM32 主控是时序电路链接起来的。对于相同的稳定运行的电路,时钟频率越高,指令的执行速度越快,单位时间能处理的功能越多。STM32 的系统时钟是可配置的,在 STM32F1 系列中,它可以为HSI、PLLCLK、HSE 中的一个,通过 CFGR 的位 SW[1:0]设置。
讲解 PLL 作为系统时钟时,根据我们开发板的资源,可以把主频通过 PLL 设置为 72MHz。仍使用 PLL 作为系统时钟源,如果使用 HSI/2,那么可以得到最高主频 8MHz/2*16=64MHz,显然达不到72MHz。
AHB、APB1、APB2、内核时钟等时钟通过系统时钟分频得到。根据得到的这个系统时钟,下面我们结合外设来看一看各个外设时钟源。如下STM32F103 系统时钟图:
上图,标号 C 为系统时钟输入选择,可选时钟信号有外部高速时钟 HSE(8M)、内部高速时钟 HSI(8M)和经过倍频的 PLL CLK(72M),选择 PLL CLK 作为系统时钟,此时系统时钟的频率为 72MHz。系统时钟来到标号 D 的 AHB 预分频器,其中可选择的分频系数为 1,2,4,8,16,32,64,128,256,我们选择不分频,所以 AHB 总线时钟达到最大的 72MHz。
下面介绍一下由 AHB 总线时钟得到的时钟:
APB1 总线时钟,由 HCLK 经过标号 E 的低速 APB1 预分频器得到,分频因子可以选择 1,2,4,8,16,这里我们选择的是 2 分频,所以 APB1 总线时钟为 36M。由于 APB1 是低速总线时钟,所以 APB1 总线最高频率为 36MHz,片上低速的外设就挂载在该总线上,例如有看门狗定时器、定时器 2/3/4/5/6/7、RTC 时钟、USART2/3/4/5、SPI2(I2S2)与 SPI3(I2S3)、I2C1 与 I2C2、CAN、USB 设备和 2 个 DAC。
APB2 总线时钟,由 HCLK 经过标号 F 的高速 APB2 预分频器得到,分频因子可以选择 1,2,4,8,16,这里我们选择的是 1 即不分频,所以 APB2 总线时钟频率为 72M。与 APB2 高速总线链接的外设有外部中断与唤醒控制、7 个通用目的输入/输出口(PA、PB、PC、PD、PE、PF和 PG)、定时器 1、定时器 8、SPI1、USART1、3 个 ADC 和内部温度传感器。其中标号 G 是ADC 的预分频器在后面 ADC 实验中详细说明。
此外,AHB 总线时钟直接作为 SDIO、FSMC、AHB 总线、Cortex 内核、存储器和 DMA 的HCLK 时钟,并作为 Cortex 内核自由运行时钟 FCLK。
USB、RTC、MCO 相关时钟,如下图:
标号 H 是 USBCLK,是一个通用串行接口时钟,时钟来源于 PLLCLK。STM32F103 内置全速功能的 USB 外设,其串行接口引擎需要一个频率为 48MHz 的时钟源。该时钟源只能从PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB 模块时,PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz。
标号 I 是 MCO 输出内部时钟,STM32 的一个时钟输出 IO(PA8),它可以选择一个时钟信号输出,可以选择为 PLL 输出的 2 分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
标号 J 是 RTC 定时器,其时钟源为 HSE/128、LSE 或 LSI。
1.2.4 时钟信号输出MCO
STM32 允许通过设置,通过 MCO 引脚输出一个稳定的时钟信号。在下图中标注为“G”的部分。
以下四个时钟信号可被选作 MCO 时钟:
(1)SYSCLK
(2)HSI
(3)HSE
(4)除 2 的 PLL 时钟
时钟的选择由时钟配置寄存器(RCC_CFGR)中的 MCO[2:0]位控制。
我们可以通过 MCO 引脚来输出时钟信号,测试输出时钟的频率,或作为其它需要时钟信号的外部电路的时钟。
2、如何修改主频
STM32F103 默认的情况下(比如:串口 IAP 时或者是未初始化时钟时),使用的是内部 8M的 HSI 作为时钟源,所以不需要外部晶振也可以下载和运行代码的。
下面我们来学习如何让 STM32F103 芯片在 72MHZ 的频率下工作,72MHz 是官方推荐使用的最高的稳定时钟频率。
2.1 STM32F1时钟系统配置
第 1 步:配置 HSE_VALUE
打开 STM32F1xx_hal_conf.h 文件,我们知道需要宏定义 HSE_VALUE 匹配我们实际硬件的高速晶振频率(这里是 8MHZ),代码中通过使用宏定义的方式来选择 HSE_VALUE 的值是 25M 或者 8M,这里我们不去定义 USE_STM3210C_EVAL 这个宏或者全局变量即可,选择定义 HSE_VALUE 的值为 8M。找到这一段代码,进行配置即可,不过一般官方已经配置好的。如下图:
第 2 步:调用 SystemInit 函数
我们介绍启动文件的时候就知道,在系统启动之后,程序会先执行 SystemInit 函数,进行系统一些初始化配置。启动代码调用 SystemInit 函数如下:
下面我们来看看 system_stm32f1xx.c 文件下定义的 SystemInit 程序。
从上面代码可以看出,SystemInit 主要做了如下两个方面工作:
(1)外部存储器配置
(2)中断向量表地址配置
然而我们的代码中实际并没有定义 DATA_IN_ExtSRAM 和 USER_VECT_TAB_ADDRESS这两个宏,实际上 SystemInit 对于原子哥的例程并没有起作用,但原子哥保留了这个接口。从而避免了去修改启动文件。另外,是可以把一些重要的初始化放到 SystemInit 这里,在 main 函数运行前就把重要的一些初始化配置好(如 ST 这里是在运行 main 函数前先把外部的 SRAM 初始化),这个我们一般用不到,直接到 main 函数中处理即可,但也有厂商(如 RT-Thread)就采取了这样的做法,使得 main 函数更加简单,但对于初学者,我们暂时不建议这种用法。
HAL 库的 SystemInit 函数并没有任何时钟相关配置,所以后续的初始化步骤,我们还必须编写自己的时钟配置函数。
第 3 步:在 main 函数里调用用户编写的时钟设置函数
这里我们随便打开原子哥 HAL 库例程实验 1 跑马灯实验,看看在工程目录 Drivers\SYSTEM 分组下面定义的 sys.c 文件中的时钟设置函数 sys_stm32_clock_init 的内容:
函数 sys_stm32_clock_init 就是用户的时钟系统配置函数,除了配置 PLL 相关参数确定SYSCLK 值之外,还配置了 AHB、APB1 和 APB2 的分频系数,也就是确定了 HCLK,PCLK1和 PCLK2 的时钟值。
首先来看看使用 HAL 库配置 STM32F1 时钟系统的一般步骤:
(1)配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
(2)配置系统时钟源以及 SYSCLK、AHB、APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。下面我们详细讲解这个 2 个步骤。
步骤 1:配置时钟源相关参数,使能并选择 HSE 作为 PLL 时钟源,配置 PLL1,我们调用的函数为 HAL_RCC_OscConfig(),该函数在 HAL 库头文件 STM32F1xx_hal_rcc.h 中声明,在文件 STM32F1xx_hal_rcc.c 中定义。首先我们来看看该函数声明:HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);
该函数只有一个形参,就是结构体 RCC_OscInitTypeDef 类型指针。接下来我们看看结构体RCC_OscInitTypeDef 的定义:
typedef struct {uint32_t OscillatorType; /* 需要选择配置的振荡器类型 */uint32_t HSEState; /* HSE 状态 */uint32_t HSEPredivValue; /* HSE 预分频值 */uint32_t LSEState; /* LSE 状态 */uint32_t HSIState; /* HIS 状态 */uint32_t HSICalibrationValue; /* HIS 校准值 */uint32_t LSIState; /* LSI 状态 */RCC_PLLInitTypeDef PLL; /* PLL 配置 */ }RCC_OscInitTypeDef;
该结构体前面几个参数主要是用来选择配置的振荡器类型。比如我们要开启 HSE,那么我们会设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为RCC_HSE_ON 开启 HSE。对于其他时钟源:HIS、LSI、LSE,配置方法类似。
RCC_OscInitTypeDef 这个结构体还有一个很重要的成员变量是 PLL,它是结构体RCC_PLLInitTypeDef 类型。它的作用是配置 PLL 相关参数,我们来看看它的定义:
typedef struct {uint32_t PLLState; /* PLL 状态 */uint32_t PLLSource; /* PLL 时钟源 */uint32_t PLLMUL; /* PLL 倍频系数 M */ }RCC_PLLInitTypeDef;
从 RCC_PLLInitTypeDef;结构体的定义很容易看出该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。这个结构体的定义的相关内容请结合时钟树中红色框的内容一起理解。
接下来我们看看我们的时钟初始化函数 sys_stm32_clock_init 中的配置内容:
/* 使能 HSE,并选择 HSE 作为 PLL 时钟源,配置 PLLMUL */ RCC_OscInitTypeDef rcc_osc_init = {0}; rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; /* 使能 HSE */ rcc_osc_init.HSEState = RCC_HSE_ON; /* 打开 HSE */ rcc_osc_init.HSEPredivValue = RCC_HSE_PREDIV_DIV1; /* HSE 预分频 */ rcc_osc_init.PLL.PLLState = RCC_PLL_ON; /* 打开 PLL */ rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; /* PLL 时钟源为 HSE */ rcc_osc_init.PLL.PLLMUL = plln; /* 主 PLL 倍频因子 */ ret = HAL_RCC_OscConfig(&rcc_osc_init); /* 初始化 */
通过函数的该段程序,我们开启了 HSE 时钟源,同时选择 PLL 时钟源为 HSE,然后把sys_stm32_clock_init 的形参直接设置作为 PLL 的参数 M 的值,这样就达到了设置 PLL 时钟源相关参数的目的。设置好 PLL 时钟源参数之后,也就是确定了 PLL 的时钟频率。
步骤 2:配置系统时钟源,以及 SYSCLK、AHB、APB1 和 APB2 相关参数,用函数HAL_RCC_ClockConfig(),声明如下:
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);
该函数有两个形参,第一个形参 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef 类型指针变量,用于设置 SYSCLK 时钟源以及 SYSCLK、AHB、APB1 和 APB2 的分频系数。第二个形参 FLatency 用于设置 FLASH 延迟。
RCC_ClkInitTypeDef 结构体类型定义比较简单,我们来看看其定义:
typedef struct {uint32_t ClockType; /* 要配置的时钟 */uint32_t SYSCLKSource; /* 系统时钟源 */uint32_t AHBCLKDivider; /* AHB 分频系数 */uint32_t APB1CLKDivider; /* APB1 分频系数 */uint32_t APB2CLKDivider; /* APB2 分频系数 */ }RCC_ClkInitTypeDef;
在 sys_stm32_clock_init 函数中的实际应用配置内容如下:
/****************** 具体配置*************************/ /*选中 PLL 作为系统时钟源并且配置 HCLK,PCLK1 和 PCLK2*/ /*设置系统时钟时钟源为 PLL*/ /*AHB 分频系数为 1*/ /*APB1 分频系数为 2*/ /*APB2 分频系数为 1*/ /*同时设置 FLASH 延时周期为 2WS,也就是 3 个 CPU 周期。*/ /***************************************************/ rcc_clk_init.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV2; rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV1; ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_2);
sys_stm32_clock_init 函数中的 RCC_ClkInitTypeDef 结构体配置内容:
第一个参数 ClockType 配置表示我们要配置的是 SYSCLK、HCLK、PCLK1 和 PCLK 四个时钟。
第二个参数 SYSCLKSource 配置选择系统时钟源为 PLL。
第三个参数 AHBCLKDivider 配置 AHB 分频系数为 1。
第四个参数 APB1CLKDivider 配置 APB1 分频系数为 2。
第五个参数 APB2CLKDivider 配置 APB2 分频系数为 1。根据我们在 mian 函数中调用 sys_stm32_clock_init(RCC_PLL_MUL9)时设置的形参数值,我们可以计算出,PLL 时钟为 PLLCLK = HSE * 9 = 8MHz * 9 = 72MHz。
同时我们选择系统时钟源为 PLL,所以系统时钟 SYSCLK=72MHz。AHB 分频系数为 1,故频率为 HCLK=SYSCLK/1=72MHz。APB1 分频系数为 2,故其频率为 PCLK1=HCLK/2=36MHz。APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=72MHz。我们总结一下通过调用函数sys_stm32_clock_init(RCC_PLL_MUL9)之后的关键时钟频率值:
SYSCLK(系统时钟) =72MHz PLL 主时钟 =72MHz AHB 总线时钟(HCLK=SYSCLK/1) =72MHz APB1 总线时钟(PCLK1=HCLK/2) =36MHz APB2 总线时钟(PCLK2=HCLK/1) =72MHz
最后我们来看看函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency 的含义,为了使FLASH 读写正确(因为 72Mhz 的时钟比 Flash 的操作速度 24Mhz 要快得多,操作速度不匹配容易导致 Flash 操作失败),所以需要设置延时时间。对于 STM32F1 系列,FLASH 延迟配置参数值是通过下表来确定的。
从上可以看出,我们设置值为 FLASH_LATENCY_2,也就是 2WS,也就是 3 个 CPU 周期,为什么呢?因为经过上面的配置之后,系统时钟频率达到了最高的 72MHz,对应的就是两个等待状态,所以选择 FLASH_LATENCY_2。
2.2 STM32F1 时钟使能和配置
在配置好时钟系统之后,如果我们要使用某些外设,例如 GPIO,ADC 等,我们还要使能这些外设时钟。这里大家必须注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。因为 RCC 相关寄存器非常多,有兴趣的可以直接打开原子哥《STM32F10xxx 参考手册_V10(中文版).pdf》6.3 小节查看所有 RCC 相关寄存器的配置。
接下来我们来讲解通过 STM32F1 的 HAL 库使能外设时钟的方法。
在 STM32F1 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件STM32F1xx_hal_rcc.h 定义的。大家打开 STM32F1xx_hal_rcc.h 头文件可以看到文件中除了少数几个函数声明之外大部分都是宏定义标识符。外设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。首先,我们来看看 GPIOA 的外设时钟使能宏定义标识符:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \__IO uint32_t tmpreg; \SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\UNUSED(tmpreg); \} while(0U)
这段代码主要是定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),它的核心操作是通过下面这行代码实现的:
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);
这行代码的作用是,设置寄存器 RCC->APB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_APB2ENR_IOPAEN 的值决定的,而它的值为:
#define RCC_APB2ENR_IOPAEN_Pos (0U)
#define RCC_APB2ENR_IOPAEN_Msk (0x1UL << RCC_APB2ENR_IOPAEN_Pos)
#define RCC_APB2ENR_IOPAEN RCC_APB2ENR_IOPAEN_Msk
上面三行代码很容易计算出来 RCC_APB2ENR_IOPAEN= (0x00000001<<2),因此上面代码的作用是设置寄存器 RCC->APB2ENR 寄存器的位 2 为 1。我们可以从 STM32F1 的参考手册中搜索 APB2ENR 寄存器定义,位 2 的作用是用来使用 GPIOA 时钟。APB2ENR 寄存器的位 2 描述如下:
位 0 IOPAEN:IO 端 A 时钟使能(I/O port A clock enable)
由软件置‘1’或清‘0’
0:IO 端口 A 时钟关闭
1:IO 端口 A 时钟开启
那么我们只需要在我们的用户程序中调用宏定义标识符就可以实现 GPIOA 时钟使能。使用方法为:
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能 GPIOA 时钟 */
对于其他外设,同样都是在 STM32F1xx_hal_rcc.h 头文件中定义,大家只需要找到相关宏定义标识符即可,这里我们列出几个常用使能外设时钟的宏定义标识符使用方法:
__HAL_RCC_DMA1_CLK_ENABLE(); /* 使能 DMA1 时钟 */
__HAL_RCC_USART2_CLK_ENABLE(); /* 使能串口 2 时钟 */
__HAL_RCC_TIM1_CLK_ENABLE(); /* 使能 TIM1 时钟 */
我们使用外设的时候需要使能外设时钟,如果我们不需要使用某个外设,同样我们可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。我们同样以 GPIOA 为例,宏定义标识符为:
#define __HAL_RCC_GPIOA_CLK_DISABLE() (RCC->APB2ENR) &= ~ (RCC_APB2ENR_GPIOAEN)
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE()的作用是设置 RCC->APB2ENR寄存器的位 2 为 0,也就是禁止 GPIOA 时钟。具体使用方法我们这里就不做过多讲解,我们这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
__HAL_RCC_DMA1_CLK_DISABLE(); /* 禁止 DMA1 时钟 */
__HAL_RCC_USART2_CLK_DISABLE(); /* 禁止串口 2 时钟 */
__HAL_RCC_TIM1_CLK_DISABLE(); /* 禁止 TIM1 时钟 */
相关文章:

STM32F103——时钟配置
目录 1、认识时钟树 1.1 什么是时钟树 1.2 时钟系统解析 1.2.1 时钟源 1.2.2 锁相环PLL 1.2.3 系统时钟SYSCLK 1.2.4 时钟信号输出MCO 2、如何修改主频 2.1 STM32F1时钟系统配置 2.2 STM32F1 时钟使能和配置 下列进行举例的开发板是原子哥的战舰开发板STM32F103ZET…...
【Linux】信号捕捉
目录 信号捕捉1.用户态与内核态1.1关于内核空间与内核态:1.2关于用户态与内核态的表征: 2.信号捕捉过程 信号捕捉 1.用户态与内核态 用户态:执行用户代码时,进程的状态 内核态:执行OS代码时,进程的状态 …...

超详情的开源知识库管理系统- mm-wiki的安装和使用
背景:最近公司需要一款可以记录公司内部文档信息,一些只是累计等,通过之前的经验积累,立马想到了 mm-wiki,然后就给公司搭建了一套,分享一下安装和使用说明: 当前市场上众多的优秀的文档系统百…...

安卓:UDP通信
目录 一、介绍 网络通信的三要素: (1)、IP地址: IPv4: IPv6: IP地址形式: IP常用命令: IP地址操作类: (2)、端口: (3)、协议: UDP协…...
clickhouse安装
clickhouse安装 在线安装和离线安装 一、环境准备: 1.检查系统是否支持clickhouse安装 (向量化支持) grep -q sse4_2 /proc/cpuinfo && echo “SSE 4.2 supported” || echo “SSE 4.2 not supported.” 2.下载对应的clickhouse包 复制运行之后,就会将对应的包加入…...

Cpp学习——string(2)
目录 编辑 容器string中的一些函数 1.capacity() 2.reserve() 3.resize() 4.push_back()与append() 5.find系列函数 容器string中的一些函数 1.capacity() capacity是string当中表示容量大小的函数。但是string开空间时是如何开的呢?现在就来看一下。先写…...
python进阶编程
lambda匿名函数 python使用lambda表达式来创建匿名函数 语法 // lambda 参数们:对参数的处理 lambda x : 2 * x // x 是参数, 2*x 是返回值 //使用lambda实现求和 sum lambda arg1, arg2 : agr1 arg2 print(sum(10,20)) // 将匿名函数封装在一…...

算法练习--leetcode 链表
文章目录 合并两个有序链表删除排序链表中的重复元素 1删除排序链表中的重复元素 2环形链表1环形链表2相交链表反转链表 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。 新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入&…...

Android性能优化—Apk瘦身优化
随着业务迭代,apk体积逐渐变大。项目中积累的无用资源,未压缩的图片资源等,都为apk带来了不必要的体积 增加。而APK 的大小会影响应用加载速度、使用的内存量以及消耗的电量。在讨论如何缩减应用的大小之前,有必要了解下应用 APK …...

前端主题切换方案——CSS变量
前言 主题切换是前端开发中老生常谈的问题,本文将介绍主流的前端主题切换实现方案——CSS变量 CSS变量 简介 编写CSS样式时,为了避免代码冗余,降低维护成本,一些CSS预编译工具(Sass/Less/Stylus)等都支…...

Java8 list多属性去重
大家好,我是三叔,很高兴这期又和大家见面了,一个奋斗在互联网的打工人。 在 Java 开发中,我们经常会面临对 List 中的对象属性去重的需求。然而,当需要根据多个属性来进行去重时,情况会稍微复杂一些。本篇…...

kafka-保证数据不重复-生产者开启幂等性和事务的作用?
1. 生产者开启幂等性为什么能去重? 1.1 场景 适用于消息在写入到服务器日志后,由于网络故障,生产者没有及时收到服务端的ACK消息,生产者误以为消息没有持久化到服务端,导致生产者重复发送该消息,造成了消…...
[AI in security]-214 网络安全威胁情报的建设
文章目录 1.什么是威胁情报2. 威胁情报3. 智能威胁情报3.1 智能威胁情报的组成3.2 整合威胁情报3.3 最佳实践4. 威胁情报的作用5.威胁情报模型6.反杀链模型7.基于TI的局部优势模型参考文献相关的研究1.什么是威胁情报 威胁情报是循证知识,包括环境、机制、指标、意义和可行性…...

Javaweb学习(2)
Javaweb学习 一、Maven1.1 Maven概述1.2 Maven简介1.3、Maven基本使用1.4、IDEA配置Maven1.6、依赖管理&依赖范围 二、MyBatis2.1 MyBatis简介2.2 Mybatis快速入门2.3、解决SQL映射文件的警告提示2.4、Mapper代理开发 三、MyBaits核心配置文件四、 配置文件的增删改查4.1 M…...
leetcode410. 分割数组的最大值 动态规划
hard:https://leetcode.cn/problems/split-array-largest-sum/ 给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。 设计一个算法使得这 m 个子数组各自和的最大值最小。 示例 1:输入:nums [7,2,5,1…...
C函数指针与类型定义
#include <stdio.h> #define PI 3.14 typedef int uint32_t; /* pfun is a pointer and its type is void (*)(void) */ void (*pfun)(void); /* afer typedef like this we can use “pfun1” as a data type to a function that has form like: / -------…...

最新2024届【海康威视】内推码【GTK3B6】
最新2024届【海康威视】内推码【GTK3B6】 【内推码使用方法】 1.请学弟学妹们登录校招官网,选择岗位投递简历; 2.投递过程中填写内推码完成内推步骤,即可获得内推特权。 内推码:GTK3B6 内推码:GTK3B6 内推码&…...

边写代码边学习之LSTM
1. 什么是LSTM 长短期记忆网络 LSTM(long short-term memory)是 RNN 的一种变体,其核心概念在于细胞状态以及“门”结构。细胞状态相当于信息传输的路径,让信息能在序列连中传递下去。你可以将其看作网络的“记忆”。理论上讲&a…...
Elasticsearch8.8.0 SpringBoot实战操作各种案例(索引操作、聚合、复杂查询、嵌套等)
Elasticsearch8.8.0 全网最新版教程 从入门到精通 通俗易懂 配置项目 引入依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version></dependency><dependency>&l…...

《MySQL高级篇》十五、其他数据库日志
文章目录 1. MySQL支持的日志1.1 日志类型1.2 日志的弊端 2. 慢查询日志(slow query log)3. 通用查询日志3.1 问题场景3.2 查看当前状态3.3 启动日志3.4 查看日志3.5 停止日志3.6 删除\刷新日志 4. 错误日志(error log)4.1 启动日志4.2 查看日志4.3 删除\刷新日志4.4 MySQL8.0新…...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...