1.STM32F103ZET6
F 产品系列:F1 主流基础系列(经典 Cortex-M3 内核)
103 :子型号:增强型,主频最高 72MHz,外设齐全
Z :引脚数代码:Z = 144 引脚
E:Flash 容量代码:E = 512 KB 程序闪存
T封装类型:T = LQFP 薄型四方扁平封装
6:温度等级:6 = 工业温度范围 -40℃ ~ +85℃常用同系列代码对照(方便区分)
引脚位:C=48 脚、R=64 脚、V=100 脚、Z=144 脚
Flash 位:4=16KB、6=32KB、8=64KB、B=128KB、C=256KB、E=512KB
温度位:0=0~70℃、6=-40~85℃
STM32单片机能工作的最小外围电路就叫最小系统。
最小系统通常包括:STM32芯片、电源、时钟、下载调试和复位5部分组成。


- MDK 安装

不用注册 :(豆包)
方案一:国内高速直链(不用注册,优先用这个)
MDK5.43a(最新版,872MB)
直接复制到浏览器地址栏:
plaintext
https://www.keil.com/fid/armkeil/files/eval/mdk543a.exe


特别说明:
芯片支持包在线安装先关闭,国内的网速太慢,在线安装需要的时间太长。最好是从官网下载和芯片匹配的芯片支持包之后,离线安装。
Keil MDK也需要注册,否则很多功能无法使用,具体注册方式参考前面学习C51时的操作。
注册机
MDK5 (Keil5)注册机破解通俗易懂-腾讯云开发者社区-腾讯云
离线安装芯片支持包
]Keil MDK 与前面学过的Keil C51不一样,并没有内置STM的MCU,所以需要手动安装。
下载芯片支持包(Keil提供):Arm Keil | Devices 根据自己使用的芯片型号下载对应的芯片支持包。



下载完成之后,双击安装即可。成功之后,再创建项目,就可以找到我们想要的芯片了。
配置
2. 点亮LED灯案例(寄存器)
LED1-LED3是普通LED灯,LED4为电源指示灯。PA0引脚输出低电平就可以点亮LED1。


启动文件
标准外设库下载地址:https://www.st.com/zh/embedded-software/stm32-standard-peripheral-libraries/products.html



启动文件选择标准:

创建一个目录:Start 拷贝下面的库文件


Copy完之后:

Keil MDK 创建工程




添加两个Project Group方便管理代码文件

先删除默认的Source Group 1,再添加两个:Start(启动相关的文件),User(我们自己写的代码)。


点击一次add 就行,否则会重复添加。



创建main.c 文件



目前最新的Keil ARM用的是 Compiler version 6,与前面的core_cm3.c不兼容,所以需要提前准备好Compiler version 5。 keil5更新5.36版本及以上版本无法编译问题解决-腾讯云开发者社区-腾讯云



安装目录下默认的第六版
下载地址:https://developer.arm.com/downloads/view/ACOMP5
解压之后,把解压的后文件夹放入到Keil MDK 的安装目录下:





keil 输入中文显示问号



5.43 不支持 编译器5 只能换5.36 试试
免费视频及资料下载地址:https://pan.baidu.com/s/11NmNj1PIjK-VkpjXUccrlA?pwd=yyds 提取码: yyds












在STM32中,让IO口工作,必须先开启对应的时钟。所以需要先查找到开启时钟的寄存器,然后通过该寄存器操作时钟的开启或关闭。我们要打开的是GPIOA的时钟。
RCC 寄存器




我们需要知道RCC_APB2ENR这个寄存器的地址。如何查找呢?先知道RCC这个外设的基地址,然后加上这个寄存器的偏移地址就行了





GPIOA 对应总线 ---APB2
从上面可以看出来,RCC的基地址是0x4002 1000,APB2ENR的偏移量是0x18,所以APB2ENR 的地址值是0x4002 1000 + 0x18
有了地址,在这个地址写入一个数据,这个数据的二进制第2是1就行了。其他位暂时不管。我们写入4。这样就开启了GPIOA的时钟。
在代码中,我们需要把地址强转成指针才能给这个地址赋值。
*(uint32_t *)(0x40021000 + 0x18) = 4;
给IO口设置输出模式
在STM32中,如果要让IO口输出低电平或高电平,必须给要使用的IO设置为输出模式。
根据前面的思路,需要先找到GPIOA的基地址,再根据偏移地址找到要使用的寄存器的地址。GPIOA的基地址是0x4001 0800。
配置PA0口的输出模式的寄存器是GPIOA_CRL。

只需要让这个寄存器的最后4位是 0011,就是最大速度的推挽输出。
*(uint32_t *)(0x40010800 + 0x00) = 3;
给IO口设置输出模式
让IO口输出低电平或高电平,必须给要使用的IO设置为输出模式。
找到GPIOA的基地址,再根据偏移地址找到要使用的寄存器的地址。GPIOA的基地址是0x4001 0800
给PA0口输出0
给指定PA0口输出0就可以点亮LED1了。用到的寄存器是ODR数据输出寄存器。

ODR寄存器的地址是 0x40010800 + 0x0c 。给这个地址的第0位写0,其他位写1。
*(uint32_t *)((0x40010800 + 0x0c)) = 0xfffe;
#include "stdint.h"
int main(void)
/* 开启GPIOA的时钟 */
*(uint32_t *)(0x40021000 + 0x18) = 4;//RCC的基地址是0x4002 1000,APB2ENR的偏移量是0x18
{
/* 给PA0设置为通用推挽输出 */
*(uint32_t *)(0x40010800 + 0x00) = 3;
/* 给输出寄存器赋值 */
*(uint32_t *)(0x40010800 + 0x0c) = 0xfffe;
while (1)
{
}
}


安装ST-LINK驱动
使用ST-LINK仿真器下载程序。

Keil的安装目录下自带了ST-LINK的USB驱动,双击安装即可

升级STLink固件






在Keil软件中,对仿真器做一些必要的配置。




操作寄存器方式优化
在操作寄存器的时候,如果每次都查手册计算地址,是相当麻烦且无聊。ST公司早就考虑到了这个问题,已经提前把每个外设寄存器的地址提前给我们用宏定义的方式给算好了,我只需要直接使用即可。比如下面是定义的RCC各个寄存器地址。(stm32f10x.h中定义)
#define PERIPH_BASE ((uint32_t)0x40000000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define RCC ((RCC_TypeDef *) RCC_BASE)
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
} RCC_TypeDef;
这里还巧妙的运用了结构体中各个成员地址是连续的特征。CR寄存器是RCC第0个32位寄存器,所以它相对于基地址的偏移是0。CFGR相对于基地址的偏移是4,...
APB2ENR的相对于基地址的偏移是6*4=24=0x18,和我们前面查找手册的结果是一致的。
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR = 4;
GPIOA->CRL = 3;
GPIOA->ODR = 0xfffe;
while (1)
{
}
}
优化2
在上面的代码中还有一些问题。在STM32中一个寄存器是32位的,我们在编写代码的时候只是需要给某位或某几位赋值。由于STM32不支持位寻址,所以在前面的操作中,我们其实是修改了所有位。这是非常不合理的,也许其他位在其他地方有赋值,我们重新赋值势必会覆盖了其他值,带来的后果也是很严重的。
如何只修改特定的位的值,而不影响其他位呢?我们需要先回顾下一些常见的位操作,再来继续进化上面的代码。
常用的一些位操作回顾
#include "stdio.h"
#include "stdlib.h"
char buffer[100] = {0};
void printfBinary(unsigned char *op, unsigned char result)
{
itoa(result, buffer, 2); // 把result转成2进制字符串
printf("%s = %s\r\n", op, buffer);
}
int main()
{
/* 左移 8<<1 = 1000<<1 = 10000*/
printfBinary("8 << 1", 8 << 1);
/* 右移 8>>1 = 1000>>1 = 100*/
printfBinary("8 >> 1", 8 >> 1);
/* 按位或 8|7 = 1000|0111 = 1111 */
printfBinary("8 | 7", 8 | 7);
/* 按位与 8&7 = 1000&0111 = 0000 */
printfBinary("8 & 7", 8 & 7);
/* 按位取反 ~8 = ~1000 = 0111 */
printfBinary("~8", ~8);
/*
把某位置 1 (0 位 1位 ...)
比如把 mum 的第 2 位置 1
1. 得到一个数第 2 位是 1 其他都为 0
a = 0000 0100 是由 1<<2 得到
2. 让 num | a
*/
printfBinary("8置第 2 位为 1 ", 8 | (1 << 2));
/*
把连续的多位同时置 1 (0 位 1位 ...)
比如把 mum 的第 1和2 位置 1
1 a = 3 << 1
2. num | a
*/
printfBinary("8置第 1和2 位为 1 ", 8 | (3 << 1));
/*
把某位置 0 (0位 1位 ...)
比如把 mum 的第 2 位置 0
1. 得到一个数第 2 位是 0 其他都为 1
a = 1111 0100 是由 ~(1<<2) 得到
2. 让 num & a
*/
printfBinary("7置第 2 位为 0 ", 7 & ~(1 << 2));
/*
把连续多位同时置 0 (0位 1位 ...)
比如把 mum 的第 1和2 位置 0
1. a = ~(3<<1)
2. 让 num & a
*/
printfBinary("7置第 1和2 位为 0 ", 7 & ~(3 << 1));
/*
把连续的多位同时置位 101 (二进制)
比如把 mum 的第 1,2,3 位置为 101
1. num的 1,2,3位置为0
num &= ~(7<<1)
2. num |= (5 << 1); (5 = 101)
*/
unsigned char num = 13;
num &= ~(7 << 1);
num |= 5 << 1;
printfBinary("13", 13);
printfBinary("10的123位置为101 ", num);
}
itoa 函数兼容性问题
itoa 是非标准库函数,标准 C / 多数编译器不自带,跨平台、Keil/GCC 都会报错,建议手动实现二进制转字符串。
#include "stdio.h"
#include "string.h"
char buffer[100] = {0};
// 手动实现:无依赖二进制转字符串(替代非标准itoa)
void uchar_to_bin(unsigned char num, char *buf)
{
int i = 7;
int idx = 0;
memset(buf, 0, 100);
// 从最高位(bit7)到最低位(bit0)遍历
for (; i >= 0; i--)
{
buf[idx++] = (num & (1 << i)) ? '1' : '0';
}
}
// 打印表达式 + 二进制结果
void printBinary(char *op, unsigned char result)
{
uchar_to_bin(result, buffer);
printf("%s = %s\r\n", op, buffer);
}
int main(void)
{
// 8 << 1 左移运算
printBinary("8 << 1", 8 << 1);
return 0;
}

左移原理
- 二进制左移 1 位 = 数值 × 2
00001000(8) 左移 1 位 →00010000(16)
右移 :
/* 右移 8>>1 = 1000>>1 = 100*/
printfBinary("8 >> 1", 8 >> 1);
二进制左移 1 位 = 数值 / 2
00001000(8) 右移 1 位 → 00010000(4)
代码
#include "stm32f10x.h"
int main(void)
{
/* 开启GPIOA的时钟 第2位置1*/
RCC->APB2ENR |= 0x1 << 2; // 100
/* GPIOA_CRL的最后4位置 0011 */
GPIOA->CRL &= ~(0x1 << 3); //把第3位清0,其余位保持不变
GPIOA->CRL &= ~(0x1 << 2); //把第2位清0,其余位保持不变
GPIOA->CRL |= 0x1 << 1;
GPIOA->CRL |= 0x1 << 0;
/* GPIOA_ODR的第0位置0 */
GPIOA->ODR &= ~(0x1 << 0);
while (1)
{
}
}
///////////////////////////////////////////////////////////////////
// 把第3位清0,其余位保持不变
val = val & ~(1 << 3);
0x1 << 3 表示 1<<3 --> 1000 (8) ---> 按位取反 ~ : 0111 (7)
32 位下:\(1 \ll 3 = 00000000\ 00000000\ 00000000\ 00001000\)
按位取反:\(\sim (\dots00001000)
= 11111111\ 11111111\ 11111111\ 11110111\)
无符号解读(uint32_t):\(2^{32}-1 - 8 = \boldsymbol{4294967287}\)
有符号解读(int32_t):最高位为 1,是负数,对应十进制 \(\boldsymbol{-9}\)
清 bit3 清 bit2 为什么
GPIOA->CRL 寄存器规则(重点)
CRL 是端口低配置寄存器,控制 PA0 ~ PA7。
每 4 个位 管理 1 个引脚:
PA0:占用 bit3、bit2、bit1、bit0
PA1:占用 bit7~bit4
PA2:占用 bit11~bit8
...... 以此类推
对单个引脚(PA0):
bit3、bit2 → CNF 位:配置引脚模式 通用推挽输出模式 00
bit1、bit0 → MODE 位:配置输出速度 1 1 50MHZ
bit3、bit2 决定引脚功能(输入 / 输出 / 推挽 / 开漏)你要用推挽输出,所以必须手动把这两位清 0。
bit1、bit0 决定输出速度你要用 10MHz,所以把这两位置 1。
这也是 STM32 寄存器操作标准套路:先清位,再赋值,避免旧配置干扰
优化3
在上次的进化中,我们是给寄存器"或等"和"与等"了一些值,这些值都是通过相应的"移位"操作得到的。比如要操作第2位,就需要把0x1左移2位得到。我们需要查找手册才能知道要移位几。也是很不方便。
其实ST公司也把我们需要的移位后的值给提前计算好 了,用宏定义的方式供我们使用。
比如前面的开启时钟,已经定义了好了这个值。正好就是1<<2
#define RCC_APB2ENR_IOPAEN ((uint32_t)0x00000004)
利用ST公司提前预定义的这些值,可以进一步进化代码为下面的形式。
#include "stm32f10x.h"
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL &= ~GPIO_CRL_CNF0_1;
GPIOA->CRL &= ~GPIO_CRL_CNF0_0;
GPIOA->CRL |= GPIO_CRL_MODE0_1;
GPIOA->CRL |= GPIO_CRL_MODE0_0;
GPIOA->ODR &= ~GPIO_ODR_ODR0;
while (1)
{
}
}

GPIO概述
GPIO(General-purpose input/output),通用型输入输出。简单理解就是我们可以控制输入输出的STM32引脚,统称为GPIO。
GPIO存在的意义就是用程序控制或读取 他们的输出或输入。
STM32有多组GPIO,比如我们使用的芯片:STM32F103ZET6共有7组GPIO端口,他们分别是GPIOx(x从A-G),每组控制 16 个引脚, 共有112个GPIO引脚。具体一个其他STM32芯片有多少组GPIO,可以去查看他们的对应的数据手册 。
每个引脚的电平是0-3.3V,部分引脚最高可以兼容到5V。

GPIO的主要特点
(1)不同型号,IO口的数量可能不一样。
(2)快速翻转。最快可以达到每2个时钟周期翻转一次。(STM32F1系列最快可以达到50MHz的翻转速度)。
(3)每个IO都可以作为外部中断。
(4)支持8种工作模式。
GPIO 8种工作模式
GPIO端口的每个位(引脚)可以由软件分别配置成8种模式,当然对同一个引脚同一时间只能处于某一种模式中。
(1)输入浮空(Input floating)
(2)输入上拉(Input pull-up)
(3)输入下拉(Input-pull-down)
(4)模拟输入(Analog)
(5)通用开漏输出(Output open-drain)
(6)通用推挽式输出(Output push-pull)
(7)推挽式复用功能(Alternate function push-pull)
(8)开漏复用功能(Alternate function open-drain)
每个I/O端口位可以自由编程,然而I/0端口寄存器必须按32位字被访问。
输出模式下可以控制端口输出高电平低电平,用于驱动LED,蜂鸣器等,如果是大功率器件(比如电机),还需要加上驱动器(小电流控制大电流)。
输入模式下可以读取端口的高低电平,用于读取外接按键,外接模拟信号的输入,ADC电压采集,模拟通信协议接受数据等。

电路结构
(1)输出缓冲器被激活。
(2)推挽模式:输出寄存器上的 1 将激活P-MOS,输出高电平。0 将激活N-MOS,输出低电平。
(3)开漏模式:PMOS永远关闭。 输出寄存器上的 0 激活N-MOS,而输出寄存器上的1 将端口置于高阻状态,所以外部必须要接上拉电阻。
(4)施密特触发输入被激活。
(5)弱上拉和下拉电阻被禁止。
(6)出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器。
(7)在开漏模式时,对输入数据寄存器的读访问可得到I/O状态。
(8)在推挽模式时,对输出数据寄存器的读访问得到最后一次写的值。
输出模式
推挽输出





开漏输出




复用输出模式

(1)在开漏或推挽式配置中,输出缓冲器被打开。
(2)内置外设的信号驱动输出缓冲器(复用功能输出)。
(3)施密特触发输入被激活。
(4)弱上拉和下拉电阻被禁止。
(5)在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器。
(6)开漏模式时,读输入数据寄存器时可得到I/O口状态。
(7)在推挽模式时,读输出数据寄存器时可得到最后一次写的值。
输入模式


(1)2个保护二极管的作用是保护我们的芯片不会由于电压过高或过低而烧毁。
VDD是接电源(3.3V),VSS接地(0V)。如果IO引脚的输入电压高于VDD的值到一定程度,上方保护二极管导通,则引脚电压被拉低到VDD。如果IO引脚的输入电压(负电压)低于VSS到一定程度,则下方保护二极管导通,电压被拉高到VSS。
(2)2个开关控制引脚没有输入的时候是上拉,下拉还是浮空。当上面的开关闭合的时候,输入被拉高到高电平。当下面的开关闭合的时候,输入被拉低到低电平。如果两个都不闭合,输入就是悬空状态。两个同时闭合,就是费电了,不会这么做的。

施密特(图中翻译成肖特基触发器应该是翻译错误,英文版手册是TTL Schmitt trigger)触发器是包含正反馈的比较器电路。可以对信号进行波形整形。


从施密特触发起出来的数据,进入到输入数据寄存器中,我们就可以从中读取数据了。
模拟输入模式


与GPIO相关的7个寄存器(重要)
每个GPI/O端口有7个相关的:
2个32位配置寄存器(GPIOx_CRL,GPIOx_CRH)。
2个32位数据寄存器(GPIOx_IDR和GPIOx_ODR)。
1个32位置位/复位寄存器(GPIOx_BSRR)。
1个16位复位寄存器(GPIOx_BRR)。
1个32位锁定寄存器(GPIOx_LCKR)。
GPIOx_CRL(端口配置低寄存器)
GPIOx_CRL(Port configuration register low),x可以是A-G。

该寄存器配置的每个GPIO的 0-7 这个8个位,所以叫低寄存器。
1)MODE:每个端口有2个MODE位进行控制。
00:输入模式(复位后的状态)
01:输出模式,最大速度10MHz
10:输出模式,最大速度2MHz
11:输出模式,最大速度50MHz
2)CNF:每个端口有2个CNF位进行控制。
(1)当MODE是00 (输入模式)
00:模拟输入模式
01:浮空输入模式(复位后的状态)
10:上拉/下拉输入模式
11:保留
(2)当MODE>00(输出模式)
00:通用推挽输出模式
01:通用开漏输出模式
10:复用功能推挽输出模式
11:复用功能开漏输出模式
GPIOx_CRH(端口配置高寄存器)
GPIOx_CRH(Port configuration register high)。
该寄存器配置的是每个端口的 8-15引脚,配置方式和低位寄存器完全一样。
GPIOx_IDR(端口输入数据寄存器)

保留位始终读为0。剩下的分别对应每个引脚的输入值。
GPIOx_ODR(端口输出数据寄存器)
保留位始终读为0。剩下的分别对应每个引脚的输出值。

GPIOx_BSRR(端口位设置/清除寄存器)
Port bit set/reset register

(1)高16位是用清除对应的数据输出寄存器的位(0-15)的值:设置为0不影响,设置为1会清除ODR对应的位的值(置为0)。
(2)低16位是用设置对应的数据输出寄存器的位(0-15)的值:设置为0不影响,设置为1会设置ODR对应的位的值(置为1)。
GPIOx_BRR(端口位清除寄存器)
这个寄存器具有了GPIOx_BSRR 一半的功能:清除。

GPIOx_LCKR(端口配置锁定寄存器)
Port configuration lock register

该寄存器用来锁定端口位的配置。位[15:0]用于锁定GPIO端口的配置。在规定的写入操作期间,不能改变LCKP[15:0]。当对相应的端口位执行了LOCK序列后,在下次系统复位之前将不能再更改端口位的配置。
每个锁定位锁定控制寄存器(CRL,CRH)中相应的4个位(CNF2位和MODE2位)。
第16位用来激活锁定寄存器,必须按照规定的时序来操作才行: 写1 -> 写0 -> 写1 -> 读0 -> 读1。
对0-15位:
0:不锁定对应端口的配置。
1:锁定对应端口的配置。
Keil+VSCode优化开发体验
Keil的开发调试能力很强,但是代码编辑体验很差。很多开发者喜欢用Keil编译调试下载,用VsCode编辑代码。
1)安装VsCode
从官网下载之后,直接安装即可.
https://code.visualstudio.com
2)安装插件
需要安装2个插件。
(1)c/c++插件

务必选择下载量最大的那个。其他的都是山寨。

3)配置Keil 助手插件
我们在VsCode中编写代码,编译下载还是使用的Keil,所以要告诉VsCode你的Keil的位置。



D:\software\keil\u5\UV4\UV4.exe

使用方式1
想用VsCode进行编辑代码,必须所有的工作先在Keil完成。比如创建工程,配置环境等。





使用方式2
打开Keil工程所在的目录,空白处点击右键,然后选择通过 Code打开。


编译下载

其实调用的仍然是Keil的编译和下载。
注意
如果需要新增源文件和项目结构更改,这时仍然需要打开Keil,在Keil中新增和更改项目结构,并做相应的配置。然后把Keil关闭,VsCode才能读取到新增的文件和项目结构的更改。
用VsCode主要是为了方便代码的编写。但是仍然离不开Keil MDK。
vscode 显示中文
安装官方中文语言包(必做)
认准 Microsoft 官方 的:Chinese (Simplified) Language Pack(简体中文)

二、切换显示语言为中文
- 按快捷键:Ctrl + Shift + P
- 输入:
configure display language - 选择:zh-cn(简体中文)

右下角会提示重启,点 Restart。
重启后,整个 VSCode 菜单、设置、提示都会变成中文。
三、如果还是英文(兜底方法)
- 按 Ctrl+Shift+P → 输入
settings→ 打开 Settings (JSON) - 加入这一行:
json
"locale": "zh-cn"
- 保存,重启 VSCode。
乱码原因
Keil 默认的中文编码是 GB2312/GBK ,而 VSCode 默认用的是 UTF-8,编码不匹配就会导致中文注释变成问号 / 方块。

解决步骤(按顺序试,很快就能好)
方法 1:直接在右下角切换编码(最快)
- 点击 VSCode 右下角的编码按钮(默认显示
UTF-8) - 在弹出的菜单里,选择
Reopen with Encoding(通过编码重新打开) - 选择
GB2312或GBK👉 这两个是 Keil 的常用中文编码,选完立刻就能看到正常中文了。


方法 2:修改 VSCode 设置,让它自动识别 GBK
- 按
Ctrl + ,打开设置,搜索files.autoGuessEncoding - 勾选这个选项,让 VSCode 自动猜测文件编码。
- 再搜索
files.encoding,把默认编码设为gbk或gb2312。
也可以直接打开 settings.json(按 Ctrl+Shift+P → 输入 settings.json),粘贴这段配置:
json
{
"files.autoGuessEncoding": true,
"files.encoding": "gbk",
"files.associations": {
"*.h": "c",
"*.c": "c"
}
}
保存后重启 VSCode,再打开文件就不会乱码了。
方法 3:彻底解决:把文件转成 UTF-8(推荐)
为了以后不再出现乱码,建议直接把文件转成 UTF-8:
- 按方法 1 用
GB2312打开文件,看到正常中文后 - 再点击右下角的编码按钮,选择
Save with Encoding - 选择
UTF-8保存文件。👉 这样以后不管用 Keil 还是 VSCode 打开,都不会乱码了。
方法 4:如果以上都无效(兜底)
-
检查你的字体设置,确保使用的字体支持中文,比如: json
"editor.fontFamily": "Consolas, 'Microsoft YaHei', monospace" -
关闭所有文件,重新打开 VSCode 再试一次。
HAL 库安装


离线安装芯片支持包
如果网速不好,下载会比较慢。也可以选择离线安装。
下载地址:https://www.st.com/zh/development-tools/stm32cubemx.html#tools-software

导入芯片支持包



注意:
上面的离线安装方式只能安装基础包(en.stm32cubef1.zip),升级包(en.stm32cubef1-v1-8-5.zip)无法安装.

把en.stm32cubef1-v1-8-5.zip拷贝到stm32cube的仓库中,直接解压就行了.仓库位置: C:\Users\你的用户名\STM32Cube\Repository. 用解压的文件覆盖原来的基础包.
GPIO 流水灯


copy上一个项目


3)创建一个目录: Hardware/Led 存储我们的LED驱动文件。
4)在 Hardware/Led下创建2个文件 led.h和led.c。
5)使用keil打开项目,做下简单配置。



