文章目录
这是我学习32的心理路程
我会尽量把内容写的仔细
对于一些简单模块的使用,我不打算深究其原理,我只要会用它就欧克。所以看见我的32博客的同志们请注意这一点。如果同志您觉得内容有改进的地方,那就请多多指教啦~~~本人一定认真听取。
我这里使用的光敏传感器是4针的。
| AO | 模拟输出:光敏传感器将采集的光线变成一个连续的模拟信号从AO引脚输出 |
|---|---|
| DO | 数字输出:大于光线阈值,DO引脚输出1(高电平);反之输出0。关于光线阈值,应该是调节模块上面那个十字架旋钮 |
| GND | 接地 |
| VCC | 3·3v或者5v |
注意:这是我第一次使用这个模块,所以语言描述可能有bug。
3针的光敏传感器就好像没有DO引脚。其他的是一样的。
这个小实验很简单哈。
lightsensor.c
#include "stm32f10x.h" // Device header
#define LED_OFF() GPIO_SetBits(GPIOB, GPIO_Pin_1 )
#define LED_NO() GPIO_ResetBits(GPIOB, GPIO_Pin_1 )
/**
* @brief 光敏传感器DO引脚所连接32的PC14引脚的初始化,
PC14设置为输入模式,读取DO传过来的电平。
* @param 无
* @retval 无
*/
void Light_Sensor_GPIOinit(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef Light_Sensor_GPIO_InitStruct;
Light_Sensor_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ;
Light_Sensor_GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
Light_Sensor_GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&Light_Sensor_GPIO_InitStruct);
}
/**
* @brief 光敏传感器所控制的led的初始化,这里我是自己外接的led
* @param 无
* @retval 无
*/
void Test_Sensor_LEDinit(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef Test_Sensor_LEDinit;
Test_Sensor_LEDinit.GPIO_Mode = GPIO_Mode_Out_PP ;
Test_Sensor_LEDinit.GPIO_Pin = GPIO_Pin_1;
Test_Sensor_LEDinit.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&Test_Sensor_LEDinit);
LED_OFF(); //high--->led off
}
/**
* @brief 功能函数,读取DO电平,来控制外接LED
* @param
* @retval
*/
void Sensor_Contral_LED(void)
{
//uint8_t value = 0;
if( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_14) == 0 )
{
LED_OFF();
}
else
LED_NO();
}
main.c
#include "stm32f10x.h" // Device header
#include "lightsensor.h"
int main (void)
{
Light_Sensor_GPIOinit();
Test_Sensor_LEDinit();
while(1)
{
//GPIO_ResetBits(GPIOB, GPIO_Pin_1 );// 测试外接led单独能不能亮
Sensor_Contral_LED();
}
}
注意:lightsensor.h就是将lightsensor.c中的函数名复制一下就欧克了,这里就不多展示 了哈!
对于这个实验的总结:
开始测试外接led能不能单独亮的时候,led死活不亮,我还以为是引脚复用的毛病,但是我差了一下资料发现并不是这个问题,结果回去仔细看了一下引脚初始化才发现led要推挽输出(),以及引脚初始化结构体的问题。我麻了。后来修正后就成功点亮了,虽然这个问题是小小的不起眼的问题,至少还是需要总结一下,避免以后再犯~~~
4种输出模式:
(1)推挽输出:使用推挽输出目的是增大电流,即提高输出引脚的驱动能力,提高电路负载能力。【驱动led】
(2)开漏输出:由于只有下拉MOS管没有上拉MOS管,所以开漏输出模式下,IO引脚只能输出低电平。如果要输出高电平,则需要外接上拉电阻。
(3)复用推挽,复用开漏输出:IO引脚做复用功能时,可以选则复用推挽或者复用开漏输出模式,在选择复用开漏输出模式时,需要外接上拉电阻。
4种输入模式:
(1)上拉输入模式:引脚内部有一个上拉电阻,通过开关连接到电源VDD,当IO引脚无输入信号时,默认输入高电平。【外接按键】
(2)下拉输入模式:与上拉输入模式相反。当IO引脚无输入信号时,默认输入低电平。
(3)浮空输入:引脚内部即不接上拉也不接下拉电阻。浮空输入模式下的引脚电平是不确定的,外部信号是上面电平,MCU引脚就输入什么电平。【USART,IIC等通信协议】
(4)模拟输入模式:引脚内部即不接上拉也不接下拉电阻。【A/D模拟输入实现对外部信号的采集】
(1)输出速度并不是输出信号的的速度,而是IO口驱动电路的响应速度。“我简称为:反应能力”。
(2)32 的输出速度有3种: 2MHz, 10MHz, 50MHz,对应的就是低速反应能力,中速反应能力,高速反应能力。
当输出配置为高速时,噪声大,功耗高,电磁干扰强;当输出为低速时,噪声小,功耗低,电磁干扰弱。当输出较高频率的信号时,应该选用较高频率响应速度的驱动模块,否则容易出现信号失真现象。
一般常用的外设(例如led,蜂鸣器等)建议采用2MHz的输出速度,而作为IIC,SPI等复用功能的输出引脚时,尽量选择高响应速度,如10mhz,50hmz。
GPIO引脚做输入模式时,不需要配置引脚的输出速度。
稍微提升一下小实验1的难度。
这里我就不贴OLED的代码了哈。
这个实验就是在实验1的lightsensor.c中加一个函数就行。
void SensorContral_LED_OLED(void)
{
//uint8_t value = 0;
if( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_14) == 0 )
{
LED_OFF();
OLED_Clear();
OLED_ShowString(2,2,"light hight");
}
else
{
LED_NO();
OLED_Clear();
OLED_ShowString(2,2,"light low");
}
}
main.c
int main (void)
{
Light_Sensor_GPIOinit();
Test_Sensor_LEDinit();
OLED_Init();
while(1)
{
SensorContral_LED_OLED();
}
}
实验总结:这个实验和实验1其实本质上上没有什么大差别,只是将led换成了OLED而已,我就想着慢慢来使用OLED屏幕,后面会慢慢提升到32用ADC采样采取光敏传感器的AO的模拟输出信号,用OLED来显示更加精确的亮度值。
对于实验2 ,我遇到一个问题:OLED出现闪屏现象。我猜测应该是主函数中的while(1)在一直循环SensorContral_LED_OLED这个函数,这个函数内部对OLED进行了清屏操作,所以就出现了闪屏现象。现在正在解决,若是解决了就放修正后代码。
修正代码的思路:(1)由于我们OLED 是静态显示,所以就不需要清屏刷新.因为在光敏传感器里面如果调用OLED的清屏函数,加上主函数while循环里面调用这个功能函数,就相当于while循环会一直有OLED清屏,所以会出现闪屏现象。(2)light hight 与 light low 长度不一样,在显示的时候会出现light lowht , 所以在light low 后面补充两个空格符,方便静态显示的时候不会出现交叠。
这刚好是OLED的静态显示所以不需要清屏刷新,但是遇到动态实时显示,需要清屏刷新怎么办捏????不需要
接线:光敏的AO接32的PA0(PA0是ADC1通道0,所以已经在adc模块里面初始化了,不需要再初始化)
.


上面函数:获取串口状态标志。其中第二个传参就是下面这张图。

无法打开串口的情况:串口调试助手没有关闭,导致串口被占用,烧录不了。

STM32F103C8T6 的引脚资源表。

myusart.c
#include "myusart.h"
void MyUsart_init(void)
{
GPIO_InitTypeDef MyUsart_GPIO_InitStruct ;
USART_InitTypeDef MyUSART_InitStruct;
// 这两个结构体的定义一定要放在时钟的之前,否者编译器警告
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 ,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA,ENABLE);
// usart1 Tx PA9
MyUsart_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP ;
MyUsart_GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
MyUsart_GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, & MyUsart_GPIO_InitStruct);
//usart1 Rx PA10
MyUsart_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
MyUsart_GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, & MyUsart_GPIO_InitStruct);
// 初始化串口2
MyUSART_InitStruct.USART_BaudRate = 115200 ;// 波特率
MyUSART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
MyUSART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 收和发模式
MyUSART_InitStruct.USART_Parity = USART_Parity_No;// 无奇偶校验位
MyUSART_InitStruct.USART_StopBits = USART_StopBits_1;// 一位停止位
MyUSART_InitStruct.USART_WordLength = USART_WordLength_8b;// 字长为8位的数据模式
USART_Init(USART1, &MyUSART_InitStruct);
USART_Cmd(USART1, ENABLE);// 使能串口2
}
// 发送一个字符
void Usart_send_byte(USART_TypeDef* USARTx,uint16_t Data)
{
USART_SendData(USARTx,Data);
while( USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET );
}
// 发送字符串,遇到字符串结尾标志‘\0’结束
void Usart_send_string(USART_TypeDef* USARTx,char *arr)
{
uint16_t i = 0;
do
{
Usart_send_byte(USARTx,*(arr + i));
i++;
}while(*(arr + i) != '\0');
while( USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET );
}
//重定向 printf
int fputc(int ch,FILE *f)
{
USART_SendData(USART1,(uint8_t)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
return (ch);
}
//重定向 输入
int fgetc(FILE *f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
adc.c
#include "stm32f10x.h" // Device header
void ADC_init(void)
{
GPIO_InitTypeDef ADC_GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStruct;// 结构体的定义需要先于RCC的开启,否者报警告
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO ,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
// 由于ADC的最大时钟不超过14MHz,所以需要把时钟降下来。
// ADC时钟分频器;六分频;时钟从APB2总线来,最大为72MHz,所以需要六分频后12MHz。
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 使用的是 ADC1 初始化 PA0 用于采集光敏传感器的模拟输出
ADC_GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入
ADC_GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 由于是输入模式,所以不需要配置Speed.
GPIO_Init(GPIOA,&ADC_GPIO_InitStructure);
// 初始化ADC1
ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描多通道还是单通道?单通道。
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;// 连续转换还是单次转换?连续。
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;// 数据对齐方式。右对齐。
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//定义用于启动模拟的外部触发器到常规频道的数字转换。
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//将ADC配置为独立还是多模?
ADC_InitStruct.ADC_NbrOfChannel = 1;//通道数量
ADC_Init(ADC1, &ADC_InitStruct);
// ADC规则组配置
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
// ADC1,通道0,需要转换数是1,采样周期是55.5。
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1);//对ADC进行复位
while(ADC_GetResetCalibrationStatus(ADC1));//等待ADC复位完成
ADC_StartCalibration(ADC1);//校验ADC
while(ADC_GetCalibrationStatus(ADC1));//等待ADC校验完成
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC的软件触发
}
float ADC_GetValue(void)
{
uint32_t Value = 0;
float ret = 0.0;
for(uint8_t i = 0;i < 30;i++)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//转换结束标志, 转换完成该标志就清零
Value += ADC_GetConversionValue(ADC1);
}
Value /= 30; // 这里求的是平均值
ret = 30.303 * (3.3 - Value *(3.3/4095));
// 整体上表示模数转换后的光度;4095表示12为的ADC的精度(不能变);
// 3.3表示这个引脚最大承受电压
return ret;
}
/*这里的 ret的运算就小小的运用了一下小算法,
如果PA0不接光敏传感器,PA0引脚还是有电压的,
(光敏传感器就是一个光敏电阻收到光线的影响来改变了电阻的阻值,
从而改变了电压,所以知道PA0的电压就可以类比出此刻的光线的强度)
我们以100作为光线最强的情况,0作为光线最弱的情况。
PA0引脚所承受的最大电压是3.3V,所以我们需要将3.3V 与 100的光强进行一个类比。
所以100 除以 3.3 等于 30.303。所以3.3V里面的一个单位1对应的就是100里面的30.303
这也是为什么要乘以一个30.303的原因。
*/
main.c
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include "lightsensor.h"
#include "myusart.h"
#include "Delay.h"
#include "adc.h"
int main (void)
{
Light_Sensor_GPIOinit();
MyUsart_init();
ADC_init();
while(1)
{
printf("ADC = %.3f \n",ADC_GetValue());
Delay_ms(500);
}
}
这里延时半秒是:避免串口调试助手疯狂答应导致卡死。
实验现象:

因为ADC采集的数据会出现小数点,所以我需要一个可以显示浮点型数据的OLED功能函数。
代码如下:
OLED.c :部分代码
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
//OLED 显示浮点型数据
// Line : 第几行
// Colum: 第几列
// Number:需要显示的浮点数(需要把浮点数据扩大为它的整型)
void OLED_FloatNum1(uint8_t Line, uint8_t Column, uint32_t Number)
{
OLED_ShowChar(Line,Column + 0,(Number/10000) + '0');// 最高位
OLED_ShowChar(Line,Column + 1,(Number/1000%10) + '0');
OLED_ShowChar(Line,Column + 2,'.');// 固定显示
OLED_ShowChar(Line,Column + 3,(Number/100%10) + '0');
OLED_ShowChar(Line,Column + 4,(Number/10%10) + '0');
OLED_ShowChar(Line,Column + 5,(Number%10) + '0');
}
就是用这两个函数来显示浮点型数据。
ADC.c:采样功能代码,实验3这个部分和这里大致没有差别,这个代针对OLED,所以返回值会有一个小小的处理。
float ADC_GetValue(void)
{
uint32_t Value = 0;
float ret = 0.0;
for(uint8_t i = 0;i < 30;i++)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));
//转换结束标志, 转换完成该标志就清零
Value += ADC_GetConversionValue(ADC1);
}
Value /= 30; // 这里求的是平均值
ret = 30.303 * (3.3 - Value *(3.3/4095));
//整体上表示模数转换后的光度;4095表示12为的ADC的精度;100就表示最大光度。
return ret*1000;
// 先把浮点型数据给扩大为它的整型,有利于OLED的功能函数处理显示这个数据
}
总结:主要是OLED显示浮点型数据那一块费了一些些时间。
处理浮点型数据的思路:(1)刚开始我就是简单的以为把浮点型数据分为整数部分和小数部分,整数部分就经常调用显示十进制函数,小数部分就是先把小数扩大成整数,然后固定显示小数点,把扩大后的小数以整数的形式显示在小数点后面,事实证明,只能显示写死的浮点型数据,不能显示总是刷新的数据,小数部分显示不出来。
(2)在我大哥的指点下,用了他的思路:先把浮点型数据转换为整型的,然后直接传这个整型的数据,再一位一位的显示(就是取最高位到最低位,挨着取出来显示,调用显示一个字符的函数。)。
欧克,结束了,学到很多。。。。加油!!
文章目录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