简介:STM32F1xx_HAL_Driver.rar 是一套面向 STM32F1XX 系列微控制器的 HAL 库驱动合集,基于意法半导体官方 HAL 框架整理,专为 Keil MDK 5 环境设计,帮助开发者在无需启动 STM32CubeMX 的情况下完成 HAL 层工程搭建。压缩包共 137 个文件,其中 60 个 .c 源文件提供外设驱动实现,76 个 .h 头文件声明接口与寄存器映射,覆盖 GPIO、定时器、ADC、SPI、I2C、UART/USART、SD/MMC 等常见外设,另有 1 个启动文件;整体仅 789KB,导入轻量。目前已有 2300 人学习使用,成为不少 STM32 开发者构建基础工程的参考。通过这套驱动,读者可以快速获得标准化的初始化、中断与 DMA 操作接口,减少重复编码和对可视化配置工具的依赖;配合 MDK5 的路径与宏定义设置,即可将 HAL 层无缝整合进自己的项目,适合入门 STM32 HAL 开发或希望精简工程流程的嵌入式工程师。 先从一个常见画面说起:你在网上找到一个叫STM32F1xx_HAL_Driver.rar的压缩包,下载完解压进一堆.c/.h文件,然后懵了——怎么用?稍微搜一下,跳出来的还全是显卡驱动、打印机驱动,跟单片机没有半点关系。这个压缩包里的东西,其实是STM32F1 系列芯片的官方 HAL 驱动库,也就是芯片厂家提供的外设驱动层。它把 GPIO、UART、I2C、ADC、DMA、定时器这些外设的底层寄存器操作,封装成一套统一的函数接口,让你不必每次翻参考手册抠寄存器位。
这篇文章不会跟你念 HAL 库的 API 手册,而是按我实际做项目的顺序来聊:拿到这个 rar 之后先干什么、工程怎么搭、F103C8T6 上那些高频外设(模拟 IIC 读 MT6701、DHT11、OLED、ADC DMA 采样、串口中断)分别怎么落地,遇到“串口中断只收一次”这种经典问题怎么一步步查。做嵌入式这些年,我最大的感受是:HAL 库的最大价值不是“帮你写代码”,而是“让你少查几百页 datasheet”,但它也有自己的脾气,踩过坑的人才知道怎么用好它。
1. 这个 RAR 里装的到底是什么:HAL 库的定位与价值
1.1 别被 Driver 带偏:这是单片机的驱动层,不是电脑驱动
搜索引擎里输入STM32F1xx_HAL_Driver时,很容易混进来一堆电脑硬件的“Driver”,什么显卡驱动、打印机驱动、驱动更新工具,这些跟 STM32 完全不沾边。这个压缩包里的内容是ST 官方为 STM32F1 系列维护的 HAL(Hardware Abstraction Layer,硬件抽象层)驱动源码,它属于 STM32CubeF1 固件包的核心部分。
打开这个 rar 后你通常会看到类似这样的目录结构:
Drivers/ ├── CMSIS/ // 内核头文件、启动文件、系统初始化 │ ├── Device/ST/STM32F1xx/Include/ │ ├── Device/ST/STM32F1xx/Source/Templates/gcc/ │ └── Include/ └── STM32F1xx_HAL_Driver/ ├── Inc/ // 头文件,比如 stm32f1xx_hal.h └── Src/ // 源码,比如 stm32f1xx_hal_uart.cDrivers/STM32F1xx_HAL_Driver就是我们常说的 HAL 驱动层。src下面每个.c文件对应一种外设,Inc下面的头文件是它的接口声明。在它下面,还有一层 CMSIS,负责把 ARM Cortex-M3 内核和 ST 芯片本身的寄存器定义包起来。你在main.c里写业务代码,调HAL_UART_Transmit()、HAL_ADC_Start_DMA(),中间经过 HAL 驱动层,最终落到寄存器操作,这就是整条链路。
1.2 HAL 库和标准外设库,我为什么站 HAL
STM32F1 是老芯片,早年大家用标准外设库(Standard Peripheral Library)比较多,后来 ST 主推 HAL 库,Stm32CubeMX 一键生成工程。两者最大的差异在下面这张表:
| 对比项 | 标准外设库 | HAL 库 |
|---|---|---|
| 代码风格 | 直接操作外设寄存器封装 | 基于句柄对象、回调机制 |
| 初始化方式 | 手动写结构体配置 | CubeMX 图形化生成 |
| 外设状态管理 | 相对简单,自己做 | HAL 内部维护State、Lock |
| 跨芯片迁移 | 换型号基本重写 | F1/F4/H7 的 API 高度一致 |
| 学习曲线 | 需要懂寄存器多一点 | 上手快,但黑盒感强 |
| 中断处理 | 自己写 IRQHandler | 库统一接管,回调到用户代码 |
实际项目里我更推荐 HAL 库,原因很实际:第一,CubeMX 能把时钟树、引脚复用、DMA 映射这些“最容易出错且肉眼检查很累”的部分自动配好;第二,以后从 F103 换到 F407,UART、I2C、ADC 的调用方式几乎一样,迁移成本低;第三,ST 官方例程、社区资料、各种开源板卡代码现在基本都是 HAL,你跟着学不会卡在“没有配套代码”上。
缺点是 HAL 库代码量大、中间层多、执行效率比寄存器操作低一些,但对我们绝大多数人来说,几十 KB 的 Flash 占用、几微秒的延时差距,换来的开发效率和可维护性完全值得。
2. 拿到压缩包之后:版本、目录和工程搭建
2.1 先确认版本和来源,别拿错芯片系列的库
我见过有人把STM32F4xx_HAL_Driver硬塞进 F103 工程里,编译报几百个错误还不知道为什么。拿到 rar 后的第一件事不是解压,而是确认它是F1 系列专用,以及对应的 CubeF1 版本号。F1 的 HAL 驱动现在一般跟随 STM32CubeF1 发布,常见版本是1.8.x。版本不同,API 会有细微差异,比如老版本可能没有HAL_UARTEx_ReceiveToIdle()这种扩展接口,新版本把部分 bug 修了。建议从 ST 官网或 GitHub 的STMicroelectronics/STM32CubeF1仓库取,尽量别用网上来路不明的二手压缩包。
解压之后,除了Drivers目录,官方固件包里通常还有Projects和Examples。我强烈建议你把Projects下的某个官方例程(比如STM32F1xx-Nucleo/Examples/UART/UART_TwoBoards)先编译一遍。官方例程是“验证过的配置合集”,时钟、外设、链接脚本都是对的,拿它当参照系,比自己从零搭稳定得多。
2.2 CubeMX 生成工程 vs 手动移植,两条路怎么选
搭建 HAL 工程有两条主流路径:
路径一:CubeMX 图形化生成(首选)。打开 CubeMX,选芯片型号STM32F103C8T6,在 Pinout 页配置引脚,在 Clock 页配置 72MHz 主频,然后 Project Manager 里选 Toolchain(Keil MDK-ARM / STM32CubeIDE / GCC),生成工程。CubeMX 会自动把需要的 HAL 源文件拷进Drivers目录,并生成main.c、stm32f1xx_hal_msp.c、stm32f1xx_hal_conf.h。这条路的容错率最高,新手几乎不会因为“漏配宏”而失败。
路径二:手动移植。如果你拿到的是像我标题里那种单独的STM32F1xx_HAL_Driver.rar,没带 CubeMX 工程,那就需要自己搭:把 CMSIS 和 HAL 驱动目录拷进工程,把stm32f1xx_hal_conf_template.h改名为stm32f1xx_hal_conf.h,把启动文件(startup_stm32f103xb.s)加进工程,再添加stm32f1xx_hal.c、stm32f1xx_hal_gpio.c等用到的外设源文件,最后在预定义宏里加USE_HAL_DRIVER和STM32F103xB。手动移植的好处是你对工程结构彻底清楚,但前几次很容易踩漏文件、漏宏的坑。
我的建议:不管最后项目用不用 CubeMX,第一版工程先用 CubeMX 生成,它生成的那套stm32f1xx_hal_conf.h和stm32f1xx_hal_msp.c是你最好的参考。
2.3 裁剪文件:别让整个 HAL 库全部编译
HAL 库十几个外设源文件,加起来不小,如果全部编译,Keil 里光编译时间就够喝杯茶,Flash 也会被没用的中间代码占掉。裁剪主要靠stm32f1xx_hal_conf.h这个头文件,里面每个外设都有一段类似这样的宏开关:
#define HAL_UART_MODULE_ENABLED #define HAL_I2C_MODULE_ENABLED #define HAL_ADC_MODULE_ENABLED // #define HAL_CAN_MODULE_ENABLED用哪个外设就保留哪个#define,不用的注释掉。同时,在工程里只添加对应的.c源文件,比如只用串口和 GPIO,就只加stm32f1xx_hal.c、stm32f1xx_hal_uart.c、stm32f1xx_hal_gpio.c、stm32f1xx_hal_cortex.c和stm32f1xx_hal_dma.c。注意stm32f1xx_hal.c里的HAL_Init()和时基(SysTick)是整个库的地基,永远不能裁掉。
3. F103C8T6 上几个高频场景的 HAL 实战拆解
3.1 模拟 IIC 读 MT6701 磁编码器:滤波与校准
MT6701 是一款磁编码器芯片,I2C 接口读出 14-bit 角度数据,对应 0~16383,再映射到 0~360°。很多项目里用 F103C8T6 的普通 GPIO 模拟 IIC 来读它,主要原因是省一路硬件 I2C,也免去和 I2C 总线外设扯皮的麻烦。
软件 IIC 的关键要点就两个:引脚模式切换和时序准确。初始化时把 SCL、SDA 都配成开漏输出并外接上拉电阻,读 SDA 时把引脚临时切成输入模式,写完再切回输出。下面是一段常见的软件 IIC 读 MT6701 的骨架:
#define IIC_SCL_PIN GPIO_PIN_6 #define IIC_SDA_PIN GPIO_PIN_7 #define IIC_GPIO_PORT GPIOB void IIC_Start(void) { IIC_SDA_HIGH(); IIC_SCL_HIGH(); delay_us(5); IIC_SDA_LOW(); delay_us(5); IIC_SCL_LOW(); } uint8_t IIC_ReadByte(void) { uint8_t i, dat = 0; IIC_SDA_IN(); // 切输入 for (i = 0; i < 8; i++) { IIC_SCL_HIGH(); delay_us(2); dat = (dat << 1) | IIC_SDA_READ(); IIC_SCL_LOW(); delay_us(2); } IIC_SDA_OUT(); return dat; }新手最容易犯的错是“没加延时”。F103 主频 72MHz,一条 IO 翻转指令只有十几纳秒,I2C 协议要求时钟低电平和高电平都有最小宽度,不加延时的话时序完全不对,读回来的数据乱跳。实测下来,SCL 高低电平各保持 2~5us,读 MT6701 很稳。
MT6701 读原始角度只是第一步,真正麻烦的是滤波和校准。滤波最直接的做法是连续采样 N 次求平均,但角度平均有一个非常经典的坑:角度是环形的,359.5° 和 0.5° 的真实平均值应该接近 0°,直接加起来除 2 却会得到 180°。所以要先“解包”,判断相邻两次采样有没有跨越 0/360 边界,有的话把后一个值加 360 再做平均,最后结果再取模 360。
校准的核心是处理安装偏心带来的零点偏移和增益误差。最简单的零点校准:把机械结构转到你定义的“0° 位置”,读当前原始角度raw_offset,之后每次读数用((raw - raw_offset + 16384) % 16384) * 360 / 16384换算成角度。如果要求更高,可以在 0° 和 90° 分别采样,计算出 scale 系数做两点校正。经验是:先做零点修正,再看线性度够不够,不够才做增益修正,别一上来就四参数拟合,反而把简单问题复杂化。
3.2 DHT11 单总线:HAL_Delay 解决不了的 us 级延时
DHT11 是单总线温湿度传感器,协议全部靠时序,最坑的一点是它的时序单位是微秒级,而HAL_Delay()只支持毫秒级延时。所以用 HAL 库驱动 DHT11,第一件事是写一个可靠的delay_us()。
我推荐用 DWT(Data Watchpoint and Trace)实现微秒延时,而不是用 SysTick。SysTick 已经被 HAL 库拿去作为系统时基了,你再用它做延时容易打架。DWT 是内核调试单元里的一个计数器,操作简单:
void delay_us(uint32_t us) { DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; while (DWT->CYCCNT < us * 72); // F103 主频 72MHz }注意72这个系数依赖系统时钟,如果你把主频改成 48MHz 或 64MHz,要相应调整。
DHT11 的读取流程是:主机把总线拉低至少 18ms,释放后等待 DHT11 拉低响应 80us,再拉高 80us,随后 40bit 数据以“50us 低电平 + 变长高电平”的形式输出,高电平 26~28us 代表“0”,约 70us 代表“1”。在 HAL 工程里我建议读取期间用临界区保护(__disable_irq()或__set_PRIMASK(1)),因为串口中断、定时器中断一旦插入,us 级时序就崩了。读完再恢复中断。
另外,DHT11 对电源纹波敏感,最好在 VCC 和 GND 之间加一个 100nF 陶瓷电容,数据线上拉电阻 4.7k~10k。如果你发现读出的湿度永远是 0xFF 或温度乱跳,先查上拉电阻和供电,再怀疑时序。
3.3 用 HAL I2C 驱动 SSD1306 OLED
OLED 显示是嵌入式调试利器。SSD1306 大部分模块是 I2C 接口,用 HAL 库的硬件 I2C 驱动时,核心就是一条HAL_I2C_Mem_Write()函数:
void OLED_WriteCmd(uint8_t cmd) { HAL_I2C_Mem_Write(&hi2c1, 0x3C << 1, 0x00, 1, &cmd, 1, 100); } void OLED_WriteData(uint8_t dat) { HAL_I2C_Mem_Write(&hi2c1, 0x3C << 1, 0x40, 1, &dat, 1, 100); }这里0x3C是模块地址(有的模块是0x3D,看背面板子丝印),左移一位变成 7 位地址格式。第二个参数0x00是控制字节里的命令标志,0x40是数据标志。HAL 库的HAL_I2C_Mem_Write已经帮你处理了起始、地址、控制字节、数据、停止位的完整时序,你不用关心底层。
SSD1306 初始化序列网上很多,但要留意几个关键配置:0x8D, 0x14打开电荷泵(否则屏幕不亮),0xA1段重映射,0xC8COM 扫描方向,这三个最容易导致“点亮但显示镜像/不亮”。如果显示出来是镜像的,把0xA1改回0xA0或者把0xC8改成0xC0。
我自己的习惯是 OLED 初始化放在HAL_I2C_Init()之后,如果初始化失败,多半是 I2C 引脚没配对上拉。STM32F1 的硬件 I2C 比较“娇气”,如果板子硬件 I2C 不稳定,直接用软件 IIC 驱动 OLED 反而省心,代码量也不大。
3.4 ADC 单通道 DMA 多次采样,均值滤波怎么做
很多传感器输出的是模拟电压,F103C8T6 内部 ADC 是 12 位。如果你直接把 ADC 读到的一次转换结果拿来用,往往会发现数值跳得厉害,尤其当电源噪声较大或者信号源内阻偏大的时候。简单可靠的办法就是ADC + DMA 多次采样,在软件里做均值滤波。
CubeMX 里的配置思路:ADC1 开一个通道(比如PA1,ADC_IN1),开启扫描模式和连续转换,DMA 选择 Circular 循环模式,数据宽度 Half Word。缓存数组开大一点,比如:
#define ADC_SAMPLES 32 uint16_t adc_buf[ADC_SAMPLES]; volatile uint32_t adc_value = 0; HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, ADC_SAMPLES);然后主循环里每轮把adc_buf[0]到adc_buf[ADC_SAMPLES-1]累加平均,得到adc_value。因为 DMA 是循环模式,它会不停地把新转换结果填进数组,主循环读数组时可能会撞上 DMA 正在更新某个元素,所以严谨一点的做法是在 DMA 半满中断里处理前半段数据,全满中断里处理后半段数据,保证当前处理的数据不受写入影响。
配置 ADC 时还有两个细节容易被忽略:
- 采样时间不要太短。F103 ADC 最快 1.5 周期的采样时间,但信号源内阻高时,内部采样电容充不满,转换结果会偏低。做传感器采集建议把采样时间设到
239.5周期,精度优先,速度完全够用。 - 参考电压要稳定。如果
VREF+接的是板载 3.3V,而这路 3.3V 还有 MOSI、RGB 灯在拉电流,ADC 结果会跟着抖。可以打开内部 VREFINT 通道做基准校准,或者至少保证 VREF 电源干净。
4. “串口中断只收一次”的完整排查链路
4.1 现象与排查起点:先排除硬件,再看代码
“串口中断只收一次”是 HAL 库新手最常撞见的问题,如果你在 F103C8T6 上用HAL_UART_Receive_IT()接收,大概率会踩到。现象是:上电后第一次往单片机发数据,能正常进中断回调,也能解析出数据;但发第二帧、第三帧,完全没反应。
我的排查顺序永远是先硬件、后软件。第一步,用串口助手连续发数据,同时用示波器或逻辑分析仪看 MCU 的 RX 引脚,确认波形确实到了引脚;第二步,确认共地可靠,很多“第一帧能收、之后收不到”其实是波特率误差累积或地线接触不良导致的偶发错帧;第三步,硬件确认没问题,再看代码。
这种排查方式本身就很值得养成习惯:不要一上来就改代码,先证明信号是好的。信号有问题的话,你在软件上折腾一整天也白搭。
4.2 根因:HAL_UART_Receive_IT 的设计机制
代码层面,这个问题几乎都出在同一个地方:HAL_UART_Receive_IT()的设计不是“常驻接收”,而是“接收指定字节数后自动停止”。很多人的写法是这样的:
HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_data, 1); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { // 处理 rx_data } }第一次调用HAL_UART_Receive_IT(&huart1, &rx_data, 1)之后,UART 确实使能了接收中断,收到一个字节后进入中断,HAL 库内部把 1 字节存进rx_data,然后调用HAL_UART_RxCpltCallback()。但回调执行完,接收并没有重新使能,因为 HAL 库认为“你要的 1 个字节我已经收到了,任务完成”。所以你只收到一次。
解决办法是在回调里重新调用一次:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { process_byte(rx_data); HAL_UART_Receive_IT(&huart1, (uint8_t *)&rx_data, 1); // 必须重新开启 } }还有一个隐藏坑:如果接收期间发生了过载错误(ORE,Overrun Error),HAL 库默认不会自动恢复接收。原因是 MCU 收到第二个字节时,前一个字节还没被取走,硬件就会置 ORE 标志。不加处理的话,此后接收中断不再触发,表现为“只收一次后再也收不到”。所以在回调里除了重新开启接收,最好检查一下__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE),有标志就清掉。CubeMX 新版生成的 HAL 代码里其实有这个处理,但手动移植的工程经常缺。
4.3 进阶方案:IDLE 空闲中断 + DMA 不定长接收
如果只是想“收到一个字节处理一个”,上面的回调里重新开启接收就够了。但项目里更常见的是“接收不定长的一帧数据,帧间靠空闲间隔分割”,这时就该上用 IDLE 空闲中断 + DMA 的方式。
IDLE 中断是 UART 硬件自带的“总线空闲”检测:当一帧数据传完之后,总线上出现一个字节时间的空闲电平,就会触发 IDLE 标志。配合 DMA 循环接收,数据自动进内存,不需要一个字节一个字节地进中断,CPU 负担低很多。
在 F1 的 HAL 库中,你可以直接用扩展接口:
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);这个函数在 HAL 库 1.8.x 里基本都有,没有的话就自己处理 IDLE 标志。核心思路是:
- DMA 开启循环接收,数据不断写进
rx_buffer; - 在 UART 中断服务函数里检查
UART_FLAG_IDLE,置位说明一帧结束; - 用 DMA 当前计数算出这一帧的长度,把数据拷走,然后重新准备下一帧。
用上这个方案之后,“只收一次”这类问题基本和你无缘了,因为 DMA 一直在跑,中断只负责“一帧收完了”的通知,而不是“收一个字节通知一次”。
5. HAL 工程问题排查方法论与避坑清单
5.1 拿到报错先分类:配置问题、时钟问题和信号问题
搞嵌入式遇到报错太正常了,关键是要快速归类。我一般把 HAL 工程里的爆红分成三类:
第一类,编译报错。大多是工程配置问题:漏了宏定义、少了源文件、芯片型号选错。典型的是error: unknown type name 'UART_HandleTypeDef',十有八九是没加#include "stm32f1xx_hal_uart.h"或者USE_HAL_DRIVER没定义。这类问题按错误提示找文件、宏、头文件路径就好。
第二类,运行卡死。典型是HAL_Delay()卡在while (uwTick < tickstart + Delay)里,说明 SysTick 中断没正常运行或者中断优先级配置有问题。还有一种情况是你在中断里调了HAL_Delay(),SysTick 中断优先级比当前中断低,导致永远等不到 tick 增加,这在 H7 上尤其常见。F1 上解决办法是保证HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)被调用,并把 SysTick 中断优先级设得足够高。
第三类,硬件信号问题。软件看起来没问题,但就是读不到数据。这种就要回到示波器、逻辑分析仪,看波形、看时序、看电平。模拟 IIC 读不到数据、DHT11 全零、OLED 白屏,绝大多数情况是上拉电阻、接线、供电的锅。
5.2 stm32f1xx_hal_conf.h 这个开关文件,值得你多看两遍
stm32f1xx_hal_conf.h是 HAL 库的“总开关”,里面除了外设使能宏,还有几个极其重要的配置:
#define HSE_VALUE 8000000U // 外部晶振频率,必须和实际板子一致 #define HSI_VALUE 8000000U // 内部 RC,一般不用改 #define TICK_INT_PRIORITY 0U // SysTick 中断优先级HSE_VALUE如果和板载晶振不一致,CubeMX 生成的时钟树配置楼上的 PLL 锁相环就锁不到 72MHz,串口波特率会飘到完全无法通信。很多人查了半天串口乱码,最后发现是板子晶振是 12MHz 而配置里写的是 8MHz。拿到一块新板子,第一件事就是确认晶振频率,再改HSE_VALUE。
另外stm32f1xx_hal_conf.h里的HAL_MAX_DELAY、HAL_CORTEX_MODULE_ENABLED这些宏,建议保持默认,不要为了“瘦身”随便注释掉HAL_CORTEX_MODULE_ENABLED,因为 NVIC 配置、SysTick 初始化都在里面,缺失会导致整个 HAL 库跑不起来。
5.3 我自己反复踩过的坑和现在的习惯
最后分享几个拿真金白银换来的经验。第一个坑发生在刚用 HAL 库时:我把整个STM32F1xx_HAL_Driver/Src目录直接加进 Keil,以为“全编译”最省事,结果编译慢不说,还因为芯片型号没选对,启动文件用错,代码死在HardFault_Handler里。后来我养成了习惯:用 CubeMX 生成基础工程,再进 Keil 裁剪文件,几分钟搞定,不会漏也不会多。
第二个坑是调试串口时,用了HAL_UART_Transmit(&huart1, "OK", 2, 1000)这种写法,第二个参数的类型是uint8_t*,直接丢字符串常量其实有编译警告,更容易在函数内部读取非法指针导致 HardFault。正确做法是定义一个字节数组,把字符串内容拷进去再发。这个细节看起来小,但在 F103 这种 Flash 空间不富裕的芯片上,省内存、避异常,一举两得。
第三个习惯是关于 HAL 库版本。我手头会固定保留一两套“验证过没问题”的 HAL 库版本,比如 CubeF1 1.8.4,而不是每次从官网拉最新。因为你项目做一半,网络上下载的新版 HAL 库如果改了 API 细节,重编之后可能冒出来一堆兼容性问题。在嵌入式里,稳定大于“追新”,版本锁死了,问题才好查,你写博客、出教程,别人复现的难度也会小很多。
上面这些内容,从压缩包解压到串口排查,基本覆盖了我在 F103C8T6 上用 HAL 库做项目的完整路径。如果你也刚下载了STM32F1xx_HAL_Driver.rar,我的建议是不要急着往工程里塞代码,先把hal_conf.h里每个开关看一遍,再跑一个官方例程,有了这两步地基,后面写外设驱动就会顺手很多,踩坑的概率能降下一大半。
本文还有配套的精品资源,点击获取