QiuQiu

Robotics - Embedding 2 探索 STM32G4

什么是 STM32G4 ?

(子问题: 什么是MCU)

子问题:什么是 MCU?什么是引脚?什么是 GPIO、PWM 和 ADC?Rust HAL 中的 GPIOAPA5split() 又代表什么?

目标: 每天加几个内容到库里

STM32G4 是意法半导体 STM32 系列中的一类 MCU(微控制器),全称通常指 STM32G4 系列,不是某一块具体开发板。

特别擅长“实时控制 + 电机控制 + 模拟信号处理”的 STM32。

一颗 STM32G4 通常集成:

能理解的东西: 生活中日常实体或者隐喻, 比如动物, 盒子

可以先把它想成一只装在芯片里的“小型控制动物”:

<用stm32-rs/stm32g4xx-hal> 来分辨好定义

引脚 (pin)

  1. 物理实体 - Pin 就是芯片外面的金属脚、金属触点
  2. rust HAL里面的一个类型

引脚(pin)就是芯片用来连接外部电路的金属端子。

pub struct Pin<

const P: char,

const N: u8,

MODE = DefaultMode,

{

_mode: PhantomData<MODE>,

}

引脚(pin)就是芯片用来连接外部电路的金属端子。

芯片内部的电路被塑料/陶瓷封装包住,需要通过引脚才能与 PCB、传感器、电源等连接。

芯片内部电路
→ 芯片内部焊盘
→ 很细的连接线
→ 芯片外部引脚
→ PCB铜线
→ 外部设备

引脚和电线的区别

就像:

插座孔 = 引脚/接口
电源线 = 电线

引脚本身一般只是一块金属,它之所以能输入、输出,是因为后面连接了芯片内部的 GPIO 电路。

并非所有引脚都是 GPIO

STM32 的引脚可能有不同用途:

引脚类型 用途
GPIO 输入或输出数字电平
VDD 给芯片提供正电源
GND/VSS 接地
RESET 复位芯片
OSC 连接外部晶振
ADC 接收模拟电压
USB USB 通信
调试引脚 烧录和调试程序

因此:

GPIO 是引脚背后的功能之一,引脚是把这个功能连接到外部的物理端子。

PA5 是什么?

PA5 不是芯片外壳上的第 5 根金属腿,而是逻辑名称:

P  = Port,引脚端口
A  = A组
5  = A组中的第5号

它对应芯片内部 GPIOA 控制器里的第 5 路 GPIO 电路。

但它在芯片外壳上的实际位置取决于封装:

逻辑名称:PA5
封装编号:可能是第21脚,也可能是其他位置
开发板接口:又可能标成 D13 等名称

需要通过具体芯片的 datasheet 和开发板原理图确认。

完整关系是:

GPIOA内部电路的第5路
→ PA5芯片引脚
→ PCB铜线
→ 开发板排针
→ 杜邦线
→ LED或传感器

所以最准确的一句话是:

引脚是芯片与外界电路连接的物理金属端点;PA5 是其中某个可由 GPIOA 控制的引脚的逻辑名字。

Port

  Port A

  ├── PA0 → 一个金属 pin

  ├── PA1 → 一个金属 pin

  ├── PA2 → 一个金属 pin

  └── PA5 → 一个金属 pin

GPIO

说法 实际含义
GPIO 电路 MCU 内部控制电压的硬件电路
GPIO 功能 读取或输出数字电平的能力
GPIO 引脚 连接这套内部电路的芯片金属引脚

GPIO = General-Purpose Input/Output,通用输入输出引脚。

它是 MCU 用来和外部电路交换最简单数字信号的“电线接口”。

每个 GPIO 通常可以配置成两种基本模式:

同一根 GPIO 引脚可以通过程序配置成:

例如一根叫 PA5 的引脚:

PA5 配置为输入:读取按钮是否按下
PA5 配置为输出:控制 LED 是否点亮

能同时输入和输出吗?

一般来说,一根引脚在某一时刻主要选择一种模式

现在作为输入 → 听
之后切换成输出 → 说

可以在程序运行时快速切换,但不能随意让外部设备和 MCU 同时向同一根线输出,否则可能发生冲突:

MCU 想输出 3.3V
外部设备想输出 0V
→ 类似电源正负直接打架,可能损坏电路

不过,一个 MCU 有很多 GPIO,所以通常是:

PA0:输入,读取按钮
PA1:输入,读取传感器
PA5:输出,控制 LED
PA6:输出,控制电机驱动器

因此 MCU 整体当然可以同时输入和输出,只是使用不同的引脚。

某些协议会让同一根线双向通信,例如 I²C,但它使用了特殊的“开漏输出 + 上拉电阻”,大家遵守规则,避免两个设备在电线上打架。

(不好理解, 高低电平是语言吗, mcu是一种会说0, 1 的动物?)

GOIPA

对,我现在发现了。你前面连续问“GPIO 是电路还是电线”“PA5 是不是类型”,其实说明我跳过了中间最关键的 GPIOA

最简单地说:

GPIOA 是一组 GPIO 引脚的控制器,PA5 是这一组里的第 5 号引脚。

STM32 把大量 GPIO 分组管理,每组最多约 16 个:

GPIOA 这一组
├── PA0
├── PA1
├── PA2
├── ...
├── PA5
└── PA15

GPIOB 这一组
├── PB0
├── PB1
└── ...

命名规则是:

PA5
││└── 第5号
│└── A组
└── Pin(引脚)

  - Pin:一只金属脚

  - Port:一组可以一起管理的金属脚

  - PA5:Port A 这组里的第 5 个 pin

  - 线路:从金属 pin 焊出去的 PCB 铜线

GPIOA 到底是硬件还是软件?

首先它是 MCU 内部的真实硬件模块,负责控制 A 组引脚:

GPIOA控制器
├── 输入状态记录
├── 输出状态记录
├── 模式配置
└── PA0~PA15的控制电路

可以把它想象成一个有 16 路的控制柜:

GPIOA = A号控制柜
PA5   = A号控制柜的第5路

控制柜内部有一些寄存器:

MODER:每个引脚是输入还是输出
IDR:读取引脚电平
ODR:记录输出电平
BSRR:把某个引脚设为高或低

例如对 GPIOA 的第 5 位进行操作,就是控制 PA5。

Rust 代码中的 GPIOA

dp.GPIOA

它是代表整个 GPIOA 硬件模块的软件句柄。

然后:

let gpioa = dp.GPIOA.split(&mut rcc);

HAL 把整个 A 组拆成多个独立的软件对象:

gpioa.pa0
gpioa.pa1
gpioa.pa2
gpioa.pa5

所以:

名称 意思
GPIO 通用输入输出这种硬件功能
GPIOA 管理 A 组 GPIO 的硬件模块
PA5 A 组第 5 个具体引脚
dp.GPIOA Rust 中整个 GPIOA 的软件句柄
gpioa.pa5 Rust 中代表 PA5 的具体值

完整关系就是:

STM32
→ GPIOA控制模块
→ A组第5路控制电路
→ PA5物理引脚
→ PCB铜线
→ 外部LED或传感器

所以此前的代码:

gpioa.pa5.into_push_pull_output()

真正的意思是:

**可以说 split() 完成了 GPIOA 的基础初始化,但它不只是初始化。**它同时做了三件事:

let gpioa = dp.GPIOA.split(&mut rcc);

1. 开启 GPIOA 的时钟

STM32 为了省电,GPIOA 上电后可能没有时钟。split() 内部先执行类似:

GPIOA::enable(rcc);

没有时钟,GPIOA 的寄存器和控制电路就不能正常工作。

这里的:

&mut rcc

是对 RCC(Reset and Clock Control,复位与时钟控制器)的可变借用。

2. 复位 GPIOA

它接着执行类似:

GPIOA::reset(rcc);

让 GPIOA 回到已知的初始状态。

3. 拆出每个引脚的句柄

它最后返回:

Parts {
    pa0: PA0::new(),
    pa1: PA1::new(),
    // ...
    pa5: PA5::new(),
}

因此:

let gpioa = ...

得到的 gpioa 大致是:

gpioa.pa0
gpioa.pa1
gpioa.pa5
// ...

真实源码的结构可以在 gpio.rs 中看到。

为什么叫 split

因为它把对整个 GPIOA 的控制权:

dp.GPIOA

拆成了 16 个相对独立的引脚句柄:

dp.GPIOA
   ↓ split
gpioa.pa0
gpioa.pa1
gpioa.pa2
...
gpioa.pa15

而且 split(self, ...) 会取得 dp.GPIOA 的所有权,所以不能重复拆分:

let gpioa1 = dp.GPIOA.split(&mut rcc);
let gpioa2 = dp.GPIOA.split(&mut rcc);
// 编译错误:dp.GPIOA 已经被消耗

这样可以防止程序中出现两套对象同时声称自己控制同一个 GPIOA。

但这时 PA5 还不是输出

split() 只完成 GPIOA 整体的启用、复位和句柄拆分。这个 HAL 中普通引脚默认是 Analog

gpioa.pa5 // 类型大致是 PA5<Analog>

还需要单独配置:

let mut led = gpioa.pa5.into_push_pull_output();

现在才变成:

Pin<'A', 5, Output<PushPull>>

随后才能:

led.set_high();

所以完整过程是:

split()
→ 启用并复位整个 GPIOA
→ 取得 PA0~PA15 的软件句柄

into_push_pull_output()
→ 配置具体 PA5 的硬件模式

set_high()
→ 让 PA5 输出高电平

因此最准确的说法是:

split() 是“GPIOA 基础初始化 + 将整个外设拆成独立引脚资源”,而不是完成每个引脚的具体功能配置。