1.1 GPIO 输出电压 - 实验篇

经过了刚刚原理部分的学习, 你已经对这个陌生的单片机有了一定的信心, 那么接下来我们开始实操吧!

注意观察, 这是一篇结构较为严谨的博客, 你可以从旁边的目录看到每一个 Step 之后所有的实验篇, 将遵循

Step X - 对应说明

  1. 具体步骤 1
    • 步骤的解释 1
    • 步骤的解释 2
  2. 具体步骤 2

希望这样的结构严谨的文章对你的学习会有帮助 !


我们这一章的内容是通过 GPIO 点灯

  • BulePile - 蓝色小药丸, 就是我们使用的这个 STM32 F03C8T6 开发板
  • , 仔细观察你的开发板, 在靠近SWD 调试接口的位置有一个 PC13 指示灯, 我们点的便是这个灯
  • GPIO, 前文提到的通用输入输出接口

这一部分是最基础的构建整个工程的部分. 今后你的每个新工程都要进行如下的步骤来进行相应的配置

Step 1 - 芯片选型

  1. 打开 CubeMX
  2. 搜索并选择对应型号的 MCU 芯片

Pasted image 20260606001726

Step2 - 工程配置

1. 配置复位时钟控制器 (RCC) - Reset Clock Control

  1. 配置成晶振 - Crystal/ceramic resonator

    • 注意观察, 点选之后, 你的页面发生了怎样的变化?(说明了什么?)
    • Pasted image 20260606002311

2. 配置时钟树 - Clock Configuration

  1. HSE 一般设置为 8MHz
    • 通过开发板晶振的频率来设置 HSE 的频率, 这里我们采用的芯片(F103C8T6)
  2. 梯形框框 - PLL Source Mux (PLL 时钟源选择器)选择主频通过 HSE 输出
    • 还记得PLL吗?, 这个梯形是时钟源选择器, 是选择 PLL 的输入时钟, 不是 PLL 哦)
  3. 通过 PLL 倍率设置 - PLLMul系统时钟 - SYSCLK配置成 72MHz

如果你激起了有关时钟的好奇心那么详细细节请看番外篇

配置如下图所示

Pasted image 20260606022856

小练习: 用你自己的话说一说图上的这个过程吧! 注意我用绿色剪头标注的小节点都是什么意思 - 将回答写在下方的评论区

3. 选择正确的调试模式

根据不同的烧写器, 选择不同的调试模式 这里说的调试模式就是下图中的Debug, 他在 SYS下面 - 我们需要将 Debug 配置成 Serial Wire Pasted image 20260620170140

3. 配置引脚

根据原理图我们确定蓝色逍遥丸上的灯是挂载在 PC13 引脚上 原理图: Pasted image 20260620175149 Pasted image 20260620181306 仔细看看这个 LED, 观察图中的箭头所指的地方

  • LED 左侧连接到高电平
  • LED侧连接到 PC13

这就说明, 如果 PC13 设置为高电平, LED 无电流通过此时为灯灭 ; 如果 PC13 变成低电平, LED 有电流通过, 此时为灯亮

我们前面学习了 GPIO 分为输入和输出两种不同的模式, 在这里我们需要控制 PC13 这个 GPIO 接口, 所以我们需要将其配置为输出模式

在 CubeMX 里面搜索 PC13, 并左键将其配置为输出模式 - GPIO_Output

  • Pasted image 20260620195100

配置 GPIO 详情信息

  • GPIO output level 用来确定默认电平, 这里我们选择 high 表示 PC13 默认为高电平, LED 中无电流通过. 现象就是 LED 默认不亮
  • GPIO mode 指定 GPIO 的模式, 这里我们选择推挽输出, 因为推挽对高低电平都有驱动能力
  • GPIO Pull-up/Pull-down 指定上下拉电阻, 这里是推挽模式, 选择什么都不影响推挽的效果, 但是为了最大程度减少上下拉电阻对推挽的驱动能力, 我们选择浮空
  • Maximum output speed: 指的是引脚输出驱动器的翻转速率(slew rate),也就是引脚电平在高低之间切换时,边沿能跳得多快(上升/下降时间多短),而不是指你能多快地去翻转这个引脚. 看不懂没关系, 反正能 low 就 low. 如果感兴趣就去看番外篇 - GPIO 配置里的[Maximum output speed 是什么意思?]
  • User Label : 给这个 GPIO 引脚写个备注吧!, 这里最好使用下划线_分割单词
  • Pasted image 20260620195841

配置工程管理

  • 命名这个项目, 不要使用中文和空格

  • 选择项目所在的文件夹, 这里就是我在 example 这个文件夹下面会有一个叫做example_F103_LED的项目文件夹

  • Toolchain/IDE选择 cmake, 方便我们使用 Clion 编写代码

  • 编译链接器 默认选择 GCC就行

  • Pasted image 20260620203525

  • code generator 中勾选选择 Generate peripheral initialization as a pair of '.c/.h' files per periphera 表示给每个外设单独拆一对文件, 便于保持项目结构的清晰

  • Pasted image 20260620203846

Step3 - 生成项目

点右上角的GENERATE CODE 生成项目代码, 读条完成后使用 Clion 打开工程 Pasted image 20260620204823

在 Clion 中选择打开你的项目Pasted image 20260620205110

配置好一个 OpenOCD运行配置, 如果对环境还有问题的可以接着这个番外篇看[番外篇 - STM32_CubeMX_CLion_全平台配置教程]

Step4 - 写代码

位置

在这个由 CubeMX 生成的项目中的手写代码不能乱放位置, 必须夹在 begin 和 end 之间. 如果不在这之间, 那么再次GENERATE CODE 的时候你的代码就会被覆盖

Pasted image 20260620231930

这里有一对begin 和 end 这就是我们这个实验要写代码的地方

因为我的灯要一直能够闪烁, 所以 我们需要把代码写在 main 函数的 while(1){}

函数

为了点灯我们会用到两个函数

HAL_GPIO_TogglePin()

它的作用是翻转(toggle)一个引脚的输出电平 : 当前是高电平就变成低,是低就变成高。每调用一次,电平就反转一次

我希望之后你们能别害怕函数的带参写法, 其实HAL_GPIO_TogglePin()HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) 是同样的, 这里是第一次出现, 为了避免吓住你们我没有写参数, 以后的函数都会写上参数

它有两个参数:

  • GPIOx:哪个端口,比如 GPIOC
  • GPIO_Pin:哪个引脚,比如 GPIO_PIN_13

如果你刚刚定义了自己的 GPIO 口备注, 比如你备注 Blue_Pill_LED , 那么

  • 端口就可以写成Blue_Pill_LED_GPIO_Port
  • 引脚就可以写成 Blue_Pill_LED_Pin

用法(两种写法等价,后者用的是 CubeMX 帮你生成的 Label 宏):

1
2
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // 更推荐,可读性好

为什么可以这样呢?

你在 CubeMX 里面写的备注其实被自动转为代码了, 这段代码放在 main.h 中, 你可以看到

1
2
3
/* Private defines -----------------------------------------------------------*/  
#define Blue_Pill_LED_Pin GPIO_PIN_13
#define Blue_Pill_LED_GPIO_Port GPIOC

这个自动帮你写好的 define 便是原因

HAL_Delay(uint32_t Delay)

它的作用是阻塞延时 单位是毫秒(ms) 程序执行到这里会”卡住”等待指定的毫秒数,然后才继续往下走

1
HAL_Delay(500); // 延迟 500 ms

Step5 - 构建并烧录代码

现在代码已经写完。接下来,先在电脑上完成编译构建、生成固件文件(.elf),然后再把这个固件烧录到开发板上运行

Pasted image 20260620235002

常见 bug

出现

1
Error: Error connecting DP: cannot read IDR

原因: - 你在 CubeMX 里,SYS 的 Debug 设成了 “No Debug”,而不是 “Serial Wire” - 线接错了, 检查接线

如果是选成了 No Debug, 解决方法如下: ### 救法一, BOOT0 法

Blue Pill 复位按钮旁边有两个黄色跳线帽,标着 BOOT0BOOT1 原理是: 把 BOOT0 设为 1,芯片上电后会进自带的出厂 Bootloader,而不会运行你的程序,于是 SWD 就空出来了,就能重新连上烧录

但在救之前,必须先把出问题的固件改好,否则烧回去又会被锁

第 1 步:先在 CubeMX 里改好根因

CubeMX 打开你的 .ioc System Core - SYS - Debug,把它设成 Serial Wire(不要选 No Debug)。顺便看一眼 PA13、PA14 没有被配成普通 GPIO。然后 重新生成代码 → 在 CLion 里 Build,得到一个修正后的 .elf

第 2 步:用 BOOT0 进 Bootloader

  • BOOT0 跳线帽移到 1 的位置(靠 3.3V 那一侧),BOOT1 保持 0
  • 按一下板上的 RESET 按钮(或断电重新上电)

第 3 步:重新烧录修正后的固件

保持接线和供电不变,跑你那条 OpenOCD 命令(注意烧的是新生成的 .elf)。这次因为你的程序没在跑、SWD 是空的,应该能正常连上,读出 0x1BA01477,然后烧录成功

第 4 步:恢复 BOOT0

  • BOOT0 跳线帽移回 0
  • 按 RESET。这次运行的是你修正后的程序(SWD 保持开启),灯应该正常闪,而且以后再烧录就不用动 BOOT0 了

救法二, connect under reset 法

如果你不想动跳线,也可以接上复位线来”复位时连接”:

  • 调试器的 RST/nRST 接到 Blue Pill 的 R(RESET)脚;
  • 在你的 daplink.cfg 里加一行(放在 source target 之前):
1
reset_config srst_only srst_nogate connect_assert_srst
  • 这样 OpenOCD 会在连接时一直按住复位,让你的程序来不及运行去关 SWD,从而连上重烧