时钟树
在之前的所有代码编程的过程中,似乎每次都绕不开一个叫做时钟使能的东西,当时我们是在数据手册上直接看其挂接在那条时钟线上的,那么STM32内部的时钟到底是怎么一个构型呢,本文来对此做一个介绍。
老规矩,一个新的名词出现,首先需要搞清楚它是个啥,下图中对时钟树给出了定义,STM32的时钟树是由多个时钟源和时钟分频组成的,为STM32芯片提供各种时钟信号。也就是说,在使用STM32的时候,所有的频率和时钟都是通过时钟树产生的。

在之前的嵌入式学习笔记——STM32硬件基础知识中的晶振电路部分提到过一嘴,STM32的基础时钟源有4个HSI振荡器时钟 、HSE振荡器时钟 、LSI振荡器时钟 、LSE振荡器时钟。这是最底层的时钟来源。
然后是上图中提到的PLL锁相环和SYSCLK系统时钟,前面提到过,此系列使用的STM32F407VE的主频是168Mhz,这个主频对应的就是SYSCLK系统时钟,而PLL锁相环就是这个系统时钟的直接来源。
在硬件篇介绍的时候,我们知道,外部晶振(也就是外部高速时钟HSE)的频率是25MHZ,内部振荡电路(对应内部高速时钟HSI)的频率是16Mhz,显然这两者都不能直接提供168MHZ的主频,那么系统时钟的168Mhz是怎么来的呢?
其实就是将HSI或者HSE接入PLL,通过PLL倍频产生的,具体的产生过程在后面的结构框图和寄存器介绍中描述。
系统时钟的来源:HSI振荡器时钟 HSE振荡器时钟 PLL锁相环
两个次级时钟源:LSI振荡器时钟 LSE振荡器时钟
H/L:高速/低速 SI/SE:内部/外部
大致了解了时钟树的构成后,就可以来研究它的框图了,这是一张很复杂的图,还是按照之前的思路,对其进行一个拆分,把暂时不用的先排除,此图的下方红框里面的是一个I2S的音频处理专用的时钟,独立于其他的所有时钟,所以暂时不看它,绿框里面的是一个以太网的时钟,也是独立于其他所有模块,所以暂时也先不看,最下面的橙色框里的USB2.0时钟也是,这三个暂时都不需要看。


简化下半部分后,如下图所示,橙色框内的MCO1和MCO2使用来做测试脚的,使用示波器进行测量,测量频率是否正常的,所以这部分暂时也可以不看,进一步简化。

我们先来看上半部分的内部低速时钟LSI和外部低速时钟LSE;根据框图,可以发现紫色框的内部低速时钟频率为32KHZ,有两个分支,一个是为独立看门狗提供时钟,另一个是为RTC提供时钟。
而绿色的框是外部低速时钟,外接引脚OSC32_IN和OSC32_OUT就是接外部晶振的引脚,进来的频率是32.768KHZ进来后只有一个走向就是为RTC提供时钟。
然后需要注意的是蓝色框的四选一数据选择器,一共有三个输入,前两个是上面的LSI和LSE,还有一个来自下方的HSE,中间经过了一个/2到31的分频器,也就是说看门狗智能由内部低速时钟提供时钟,RTC可以由LSI、LSE、或者是HSE经过分频后来提供时钟,具体使用哪一个需要我们在代码里面配置。

丢掉了下面,看完了上面,删除了测试接口,接下来就到了本文的重头戏了,百分之90的片上外设的时钟都与这部分有关系。
首先,看图中的系统时钟(红色椭圆框),可以放发现,系统时钟也是通过一个选择器来选择的,输入源有三个,分别是来自橙色框的HSI(内部高速时钟),绿色框的HSE(外部高速时钟)以及蓝色框的PLL锁相环,也就是说,系统时钟可以有三个选择。
这里有一个问题,上面也提到过,系统时钟的直接来演应该选择哪一个输入?
由于内部高速时钟和外部高速时钟都无法提供168Mhz的频率,所以系统时钟的直接来源应该是PLLCLK,也就是说在配置的时候需要将系统时钟选择到锁相环上。

那么问题来了,PLLCLK又是怎么产生168Mhz的频率的呢,我们放大这部分电路来做个解析,
如下图所示:
首先内部时钟HSI和外部时钟 HSE会来到绿色框的选择器,由我们编程控制具体选择HSI还是HSE;经过选择器选择后,会有一个分频器 “/M”一般这个M就是对应的多少M,
举个栗子:我们选择外部高速时钟作为锁相环的输入,此时的外部时钟频率是8MHZ,那么此处的分配就会采用8分频,将频率分频为1Mhz输入,方便后面的计算。
同样的,如果板子上没有外接晶振,只能选择内部高速时钟作为PLL的输入,此时内部高速时钟的频率是16Mhz,那么此处的分频系数就要配置为16,总之就是将对应输入的频率在此处转化为1Mhz,当然也可以是其他的值,只是1Mhz方便计算而已。

再经过选择器和分频器后,就正式进入了PLL锁相环了,这里我们结合寄存器的描述来解析:
首先1的位置就是VCO也就是经过分频后的1Mh频率,在其正下方有一个xN,这个xN是倍频的意思,此处的xN是需要我们编程时进行控制的,它的具体位置就是RCC PLL 配置寄存器 (RCC_PLLCFGR)的第6到14位,但是其写入值不是随意的,注意看下方的描述,我们写入的数据必须是192-432之间的值;

经过倍频后,会来到3的位置,这里有一个/P,根据上面的经验,这个肯定也是一个分频器,那么它的配置有没有要求呢,还是来看看寄存器的相关描述:它是由RCC PLL 配置寄存器 (RCC_PLLCFGR)的第16和17位控制的,注意最低分频也是2分频。

经过/P后就输出到SYSCLK的选择器了,至于下面的4和5暂时我们没有用上,所以这里不做介绍,想要了解的自己去中文编程手册查看。
好了,到这里已经知道了系统频率是怎么产生的了,举两个例子吧,
第一种,选择外部时钟作为PLL的输入,外部时钟是8MHZ,主频是168MHZ,那么有关PLL的配置流程应该是怎样的?
//此处伪代码主要是走一下计算流程实际流程会比这个多几个步骤
{
1.绿框选择器选择HSE作为输入;
2.橙框的分频系数设置为8分频,输出1Mhz的信号到VCO;
3.配置2号位置的倍频系数,系数要求是(192-433),由于后面还有个最低的2分频,所以此处要配置为336(168*2);
4.配置3号位置分频器为00,二分频,这样就实现了一个168MHZ的系统频率
}
第二种,没有外部接晶振电路,或者说板子的外接晶振电路挂逼了,此时应该如何配置系统的时钟为200MHZ(对于F407来说,200MHZ的频率算超频,如非特殊需求必要,不建议这么操作,会拉高功耗而且造成不稳定)
//此处伪代码主要是走一下计算流程实际流程会比这个多几个步骤
{
1.绿框选择器选择HSI作为输入;
2.橙框的分频系数设置为16分频,输出1Mhz的信号到VCO;(内部高速时做的频率为16MHZ)
3.配置2号位置的倍频系数,系数要求是(192-433),由于后面还有个最低的2分频,所以此处要配置为400(200*2);
4.配置3号位置分频器为00,二分频,这样就实现了一个200MHZ的系统频率
}
上面一段主要是介绍了系统时钟的具体生成过程,这个时钟框图还剩下最后一块,就是我们前面配置时经常使用到的AHB、APB1、APB2这些。

如上图所示,经左侧的选择器选择后,最终的SYSCLK的频率是168MHZ,紧接着往右就分成了两条路线一条向上直接是给到了以太网时钟,这个暂时用不上,可以暂时性忽略;另一路则是来到了AHB,下面有一个分频器,意思是可以配置AHB分频为1,2,4,8,16分频,这个具体的我们也看一眼,对应的寄存器描述,其具体的控制是由RCC 时钟配置寄存器 (RCC_CFGR)的第4到第7位来实现的。

然后再往后走,又分成了两路,这次先看下面的这一路,可以看见首先来到了2位置的APBx的分频控制,关于它的具体配置也是有对应的寄存器位的,如下图:其中APB1占RCC 时钟配置寄存器 (RCC_CFGR)的10-12位
APB2占RCC 时钟配置寄存器 (RCC_CFGR)的13-15位
然后还有对应的要求,APB1不能超过42MHZ,APB2不能超过84Mhz。

通过这个就可以为大多是外设提供所需的时钟了,需要特别注意的是下面还有个框3,里面是一个判断语句,如果APBx是1分频,则×1给到定时器,如果APBx不是1分频,则需要×2后再给到定时器。

最后,还有一个遗留的就是经过AHB后向上去的,那个最后是给内核提供时钟了,还有个(框4位置)是经过8分频后位系统时钟提供时钟,也就是下一篇会介绍的系统滴答(SYS_Tick),这个到下一篇再说。
整个时钟相关的寄存器都在中文编程手册的第6.3章,主要包括三大类:1.时钟配置寄存器部分 ;2.外设复位寄存器部分 3.外设使能寄存器部分;

这里重点是如何配置时钟,所以需要使用到的是第一大类,时钟配置相关的寄存器。

写法:RCC->CR
功能: 1.打开对应的时钟源 HIS HSE PLL;2.用以判断对应的时钟是否准备好。
配置过程中需要使用到的具体位:
位 0 HSION:内部高速时钟使能 (Internal high-speed clock enable)
置1:打开HSI时钟
置0:关闭HSI时钟
写操作,时钟并不是立刻打开的
位 1 HSIRDY:内部高速时钟就绪标志 (Internal high-speed clock ready flag)
为1:表示HSI时钟开启成功
为0:表示HSI时钟还没有开启成功
读操作
位 16 HSEON:HSE 时钟使能 (HSE clock enable)
置1:打开HSE时钟
置0: 关闭HSE时钟
位 17 HSERDY:HSE 时钟就绪标志 (HSE clock ready flag)
为1:表示HSE时钟开启成功
为0:表示HSE时钟还没有开启成功
位 24 PLLON:主 PLL (PLL) 使能 (Main PLL (PLL) enable)
置1:打开锁相环
置0:关闭锁相环
位 25 PLLRDY:主 PLL (PLL) 时钟就绪标志 (Main PLL (PLL) clock ready flag)
为1:表示PLL开启成功
为0:表示PLL还没有开启成功
PLL的锁相环具备写保护,在PLL锁相环运行状态的时候,不能进行相关配置
其他位的相关介绍请参考手册。
这个寄存器在上面介绍PLL的流程的时候已经大致看过了,其作用就是配置PLL的各项参数,产生系统所需的168MHZ的频率。
写法:RCC->PLLCFGR
功能:配置PLL锁相环,注意:配置过程中需要先操作上一个寄存器关闭锁相环
位 5:0 PLLM:主 PLL (PLL) 和音频 PLL (PLLI2S) 输入时钟的分频系数 (Division factor for the main PL(PLL) and audio PLL (PLLI2S) input clock)
可以配置相应的分频
写入值:2~63 可写入的值有限制
位 14:6 PLLN:适用于 VCO 的主 PLL (PLL) 倍频系数 (Main PLL (PLL) multiplication factor for VCO)
可以配置相应的倍频
写入值:192~432 可写入的值有限制
位 17:16 PLLP:适用于主系统时钟的主 PLL (PLL) 分频系数 (Main PLL (PLL) division factor for main system clock)
可以配置相应的分频,对应的分频有自己的参数
00:PLLP = 2
01:PLLP = 4
10:PLLP = 6
11:PLLP = 8
位 22 PLLSRC: 主 PLL(PLL) 和音频 PLL (PLLI2S) 输入时钟源 (Main PLL(PLL) and audio PLL
(PLLI2S) entry clock source)
由软件置 1 和清零,用于选择 PLL 和 PLLI2S 时钟源。此位只有在 PLL 和 PLLI2S 已禁止时
才可写入。
0:选择 HSI 时钟作为 PLL 和 PLLI2S 时钟输入
1:选择 HSE 振荡器时钟作为 PLL 和 PLLI2S 时钟输
此位就是上面框图中的绿色选择框,选择HSI或者是HSE作为PLL的输入。
此寄存器的主要作用就是对AHB、APB1、APB2等进行时钟配置,也就是上面框图讲解的最后一部分的控制内容。

写法:RCC->CFGR
功能:配置相应时钟总线时钟,选择系统时钟的来源
位 1:0 SW:系统时钟切换 (System clock switch)
00:选择HSI作为系统时钟时钟源
01:选择HSE作为系统时钟时钟源
10:选择PLL作为系统时钟时钟源
位 3:2 SWS:系统时钟切换状态 (System clock switch status)
为00:表示HIS选择成功
为01:表示HSE选择成功
为10:表示PLL选择成功
具体写法:
while(!(PLL->CFGR & (1<<3)));
//等待系统时钟切换成HIS
while ( RLL->CFGR & (3<<2) );
位 7:4 HPRE:AHB 预分频器 (AHB prescaler)
可以修改AHB总线的频率
0xxx:系统时钟不分频
1000:系统时钟 2 分频
1001:系统时钟 4 分频
1010:系统时钟 8 分频
1011:系统时钟 16 分频
1100:系统时钟 64 分频
1101:系统时钟 128 分频
1110:系统时钟 256 分频
1111:系统时钟 512 分频
位 15:10 PPRE1:APBx分频器
0xx:AHB 时钟不分频
100:AHB 时钟 2 分频
101:AHB 时钟 4 分频
110:AHB 时钟 8 分频
111:AHB 时钟 16 分频
功能:在上面各个配置过程中,各类时钟准备就绪就会进入中断执行操作,默认不操作,在这里一般不需要执行中断。所以这个寄存器基本上不操作的。
在搞清楚了框图以及对应的寄存器后,就可以尝试配置一个时钟了,这里实现这样一个需求,使用HSI作为基础时钟源配置系统时钟200Mhz,APB2为50MHZ;APB1为25MHZ。
这类需求一般不常用,但是一旦拿到的板子没有外接晶振或者说自己画的板子外接晶振挂了,不工作,就可以采用类似的操作,将PLL时钟源选择为HSI,进而为整个系统提供时钟源。
还是老规矩,先写一个伪代码来理清思路:
ST的默认时钟文件------HSE
HSI系统时钟配置的初始化函数
{
打开HSI的时钟振荡器
等待HSI准备就绪
先切换系统时钟到HSI//由于系统运行过程中不能没有时钟,而配置PLL又需要先将PLL关闭,所以需要执行此操作,现将系统时钟暂时切换到HSI。
等待系统时钟切换成功
关闭锁相环
选择锁相环需要的时钟源
配置M分频
配置N倍频
配置P分频
打开PLL锁相环
等待PLL锁相环锁定
系统时钟切换回PLL锁相环
等待系统时钟准备就绪
}
还是一样的需要新建文件然后导入工程,这里就不再一步一步的演示了
//配置代码:
#include "Hsi.h"
/*******************************************
*函数名 :HSI_Init
*函数功能 :初始化HSI函为系统时钟
*函数参数 :无
*函数返回值:无
*函数描述 :
将系统时钟调整为200MHZ
APB2为50MHZ
APB1为25MHZ
*********************************************/
void HSI_Init(void)
{
RCC->CR |=(1<<0);//打开HSI的适中振荡器
while(!(RCC->CR & (1<<1)));//等待HSI就位
RCC->CFGR &= ~(3<<0);//将系统时钟暂时切换至HSI
while(RCC->CFGR & (3<<2));//等待时钟切换完成
RCC->CR &=~(1<<24);//关闭锁相环
RCC->PLLCFGR &=~(1<<22);//PLL选择HSI为输入时钟源
RCC->PLLCFGR &=~(0X3F<<0);//清零
RCC->PLLCFGR |=(16<<0); //M16分频
RCC->PLLCFGR &=~(0X1FF<<6);//清零
RCC->PLLCFGR |=(400<<6); //N倍频 400
RCC->PLLCFGR &=~(3<<16);//P分频 2
RCC->CR |=(1<<24);//打开锁相环
while(!(RCC->PLLCFGR & (1<<25)));//等待锁相环锁定
//切换回PLL作为系统时钟
RCC->CFGR |= (1<<1);//将系统时钟暂时切换回PLL
while(!(RCC->CFGR & (1<<3)));//等待时钟切换完成
RCC->CFGR &=~(0xf<<4);//清零,将AHB时钟设置为不分频200MHZ
RCC->CFGR &=~(0x7<<10);//清零
RCC->CFGR |=(0x6<<10);//将APB1的时钟设置为25MHZ8分频
RCC->CFGR &=~(0x7<<13);//清零
RCC->CFGR |=(0x5<<13);//将APB2的时钟设置为50MHZ//4分频
}
//.H:
#ifndef _HSI_H
#define _HSI_H
#include "stm32f4xx.h"
void HSI_Init(void);
#endif
效果:由于APB2的时钟变成了50MHZ所以一种检验方式如下图,修改BRR内对应的频率为50MHZ然后编译下载,串口助手可以正常打印就说明正常。

另外一种检验方式就是添加库函数,调用对应库函数即可查看。
未修改之前的主频是168Mhz,如下图右下的框图所示。

修改后的主频是200MHZ,APB1的频率是25MHZ,APB2的频率是50MHZ。

1.需要使用到库函数,要找一个库函数的代码,复制对应的
“stm32f4xx_rcc.h” 和对应的"stm32f4xx_rcc.c"到自己工程目录下并添加进工程;使用库函数的同学可以直接在自己的库里面找,将其添加进工程即可。
2.使用寄存器的小伙伴记得打开stm32f4xx_rcc.h,
添加一个宏定义“ #define assert_param(expr) ((void)0)”,使用库函数的同学直接跳转至第3步。

3.在main.h包含头文件“stm32f4xx_rcc.h”,然后再main.c声明一个结构体变量
RCC_ClocksTypeDef Get_Rcc_Clock; //声明一个结构体变量,获取系统时钟状态
4.在单次运行代码的位置,添加:
RCC_GetClocksFreq(&Get_Rcc_Clock); //仿真的时候就可以在结构体get_rcc_clock中看见各个外设的时钟了

5.编译通过后,点击Debug按钮,来到如下界面,在右下角底部找到“Call Stack + Locals”,选中;然后点击2位置的全速运行,等待3秒,然后点击叛变的停止运行,4对应的框会出现对应的频率。

此时频率还是16进制的数,为了方便查看,随便选择一个变量选中,然后右键弹出2位置的16进制显示,把这个复选框取消即可。


STM32F4获取并查看当前系统时钟频率——http://t.csdn.cn/E9ATa
STM32获取当前系统时钟——http://t.csdn.cn/uPwvO
此法是仿真的频率并不是实际采集的频率,只是理论值,实际最好还是采用上一种串口修改频率不乱码的方案来解决。
本文主要是介绍了STM32的是时钟树,捋了一下时钟的配置流程,文中如有不足还请指出。
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
如何学习ruby的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/
深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG
文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk=Var(yt)Cov(yt,yt−k)其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞
写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c
LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L