1 引脚分布与两套编号
- STM32F103C8T6 是 LQFP48 封装(四边出脚的贴片封装,一共 48 个物理脚)。48 个脚里有 37 个是 GPIO(可当普通输入输出用),剩下 11 个是电源、复位、晶振等专用脚。 37 个 GPIO 按"端口(Port)"分组:PA0~PA15、PB0~PB15(各 16 个)、PC13~PC15(3 个)、PD0~PD1(2 个),合计 16+16+3+2 = 37。PC、PD 的其他脚在 48 脚封装上没有引出来,要 64 脚以上的型号才有。
- 物理引脚号(1~48,数芯片上第几个脚)和 GPIO 名(PA0、PB7 这种)是两套完全独立的编号,之间没有换算公式,只能查引脚图。 芯片正面有一个小圆点(或缺角),圆点旁边就是 1 脚,然后逆时针数,每边 12 个。
- 在这块 48 脚芯片上写 PC0、PC12、PD2 这类引脚是不存在的——编译不会报错(软件不知道硬件),但物理上没有这个脚,程序自然没反应。
- PD0/PD1 同时是 HSE 外部晶振脚(PD0 = OSC_IN 晶振输入、PD1 = OSC_OUT 晶振输出),一旦接了晶振就不能再当普通 IO 用;PC13/PC14/PC15 与低速晶振/备份功能相关(PC13 = TAMPER-RTC 侵入检测、PC14 = OSC32_IN、PC15 = OSC32_OUT)。
- 补充:HSE(High Speed External,高速外部时钟)就是外接的 8MHz 主晶振;RTC(Real-Time Clock,实时时钟)是掉电后靠纽扣电池还能走时的时钟。
- BOOT 两个引脚决定上电后从哪里开始运行程序:BOOT0=0 从 Flash(闪存,即你烧录程序的地方)启动,这是正常工作状态;BOOT0=1 且 BOOT1=0 从"系统存储器"启动,那里出厂固化了一段串口下载程序(ISP,In-System Programming,在系统编程),这就是 BOOT 跳线帽的作用。 跳线帽放错位置时,现象很像"程序没运行/烧不进去",其实芯片在跑原厂的 ISP 程序。
- VDDA/VSSA(模拟部分的电源/地脚)悬空会导致 ADC 读数乱跳,即使不用 ADC 也必须接好。
| 特殊脚 | 作用 |
|---|---|
| VDD / VSS(多组) | 数字部分电源 3.3V / 地 |
| VDDA / VSSA | 模拟部分电源/地,不能悬空,否则 ADC 不准 |
| VBAT | 备用电池脚(给 RTC 和备份寄存器供电) |
| NRST | 复位脚(Reset),接复位按钮,低电平有效(按下接地就复位) |
| BOOT0 / BOOT1 | 启动模式选择 |
| PD0 / PD1 | 兼 HSE 晶振 OSC_IN / OSC_OUT |
| PA13 / PA14 | SWDIO / SWCLK,SWD 调试下载口(ST-Link 占用,不要当普通 IO) |
| PA0 | WKUP,待机模式唤醒脚 |
- 型号按位读:
STM32F103 x y z- x = 引脚数:C=48、R=64、V=100、Z=144;
- y = Flash 容量:4=16K、6=32K、8=64K、B=128K、C=256K、D=384K、E=512K(单位 KB);
- z 前位 = 封装形式:T=LQFP、H=TFBGA、U=VFQFPN;
- 末位 = 工作温度范围:6=−40~85℃、7=−40~105℃。 所以 C8T6 = 48 脚 / 64KB Flash / LQFP 封装 / −40~85℃,SRAM(运行内存)20KB。
- 引脚名 = 端口字母 + 组内序号:PA0 就是 A 组的 0 号引脚。注意同一组的引脚在芯片四边并不连续排列,别想当然地按顺序推位置,查图为准。
- 查资料分两本手册,别搞混:
- 数据手册(Datasheet,简称 DS):查引脚分布、电气参数(耐压、电流)、引脚复用位置;
- 参考手册 RM0008(Reference Manual):查所有外设的寄存器和每一位的定义。
C8T6 属于"中密度(medium density)"产品,启动文件用
startup_stm32f10x_md.s(md = medium density)。
- 市场上有标着 C8T6 实际却是 C6T6(只有 32KB Flash) 的打磨翻新片,程序超过 32KB 就会出莫名其妙的问题,买芯片尽量选正规渠道。
2 时钟:任何外设初始化的第一步
- GPIO 挂在 APB2 这条总线上,给它开时钟用
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE)。 "开时钟"可以理解为给外设合上电闸:STM32 为了省电,出厂时所有外设的时钟都是关闭的,不开时钟,对这个外设的任何寄存器读写都石沉大海、而且不会报任何错。 这是"程序看着没问题、外设就是不动"的第一大原因。 - 常用外设挂在哪条总线(背下来):
- APB2(最高 72MHz,高速总线):GPIOA~D、AFIO、TIM1、SPI1、USART1、ADC1/2、EXTI;
- APB1(最高 36MHz,低速总线):TIM2~4、USART2/3、SPI2、I2C1/2、bxCAN、USB。
- 时钟是按"组(端口)"开的,不是按引脚开的:开一次
RCC_APB2Periph_GPIOA,PA0~PA15 全部 16 个脚就都有时钟了。 RCC_APB2PeriphClockCmd和RCC_APB1PeriphClockCmd用哪个,取决于外设挂在哪条总线:- 宏名写错(比如不存在的
RCC_APB2Periph_I2C1)会直接编译报错; - 但端口名写错(该开 GPIOC 却开了 GPIOA)编译能过,只是配置没落到目标引脚上,属于"静默失效",很难查。
- 宏名写错(比如不存在的
3 引脚内部结构
- 输出通路(CPU → 引脚):CPU/外设把电平写进 ODR(Output Data Register,输出数据寄存器) → 输出控制电路 → 驱动 P-MOS/N-MOS 两个场效应管的栅极 → 引脚上出现高/低电平。
- 输入通路(引脚 → CPU):引脚上的电压先经过 施密特触发器(Schmitt Trigger,一种把连续电压整形成干净 0/1 的电路) → 结果被锁存进 IDR(Input Data Register,输入数据寄存器) → CPU 读 IDR 就知道引脚是高还是低。
- 通路中还有:输入驱动器、上拉开关(闭合后通过电阻接 VDD)、下拉开关(闭合后通过电阻接 VSS);引脚最外侧有两只保护二极管,分别"钳"向 VDD 和 VSS,用来防静电和过压。
- CPU 读到的不是引脚此刻的连续电压,而是施密特触发器输出端被采样锁存下来的那一瞬间的 0/1。
- 虚线框里整块叫"转换电路",干三件事:上下拉电阻给引脚一个默认电平、保护二极管防静电、施密特触发器把电压翻译成 0/1。所谓四种输入模式,本质上就是同一张图里上拉、下拉两个开关"通/断"的不同组合而已。
4 推挽与开漏
- 推挽输出(Push-Pull):内部有两个 MOS 管——上管 P-MOS(接通就把引脚接到 VDD)、下管 N-MOS(接通就把引脚接到 VSS),两管交替工作,一推一拉,所以叫推挽。
开漏输出(Open-Drain):只有下管 N-MOS,上管在芯片内部根本不接,下管的"漏极"直接对外开路(这就是"开漏"名字的由来)。
- 补充:MOS 管可以粗略理解成"电压控制的电子开关",栅极给电压就导通。
- 两种输出写 0/1 时的四个状态:
| 上管 | 下管 | 引脚状态 | 电流方向 | |
|---|---|---|---|---|
| 推挽写 1 | 导通 | 截止 | 接 VDD,输出高电平 | 从引脚流出(叫拉电流/源电流) |
| 推挽写 0 | 截止 | 导通 | 接 VSS,输出低电平 | 从外部流入引脚(叫灌电流) |
| 开漏写 1 | 没有 | 截止 | 高阻态(相当于引脚断开),电平由外部电路决定 | 无回路 |
| 开漏写 0 | 没有 | 导通 | 接 VSS,输出低电平 | 灌进引脚(灌电流) |
- 开漏写 1 的本质不是"输出高电平",而是"松手"——引脚进入高阻态,把总线的控制权交出去。 想要稳定的高电平,必须在外部接一个几 kΩ 到几十 kΩ 的上拉电阻到电源;没有上拉电阻时引脚悬空,万用表量到的是飘忽不定的电压。
- 开漏的两大用途:
- ① 线与(Wired-AND):多个开漏引脚可以直接并联在同一根线上、共用一个上拉电阻。大家都松手时线上才是高电平,任何一个拉低整条线就是低电平,互不冲突。I2C 的 SCL/SDA 两根线就是靠这个特性工作的。
- ② 电平转换/兼容:上拉电阻接到几伏,高电平就是几伏。比如上拉到 5V,3.3V 的单片机就能与 5V 器件共用总线。
- 推挽输出绝不能线与:两个推挽输出并联,一个输出高、一个输出低时,会形成 VDD → 上管 → 引脚 → 引脚 → 下管 → VSS 的低阻通路,相当于电源直接短路,大电流会烧坏引脚。
- STM32 引脚的灌电流(往里吸)能力比拉电流(往外推)强,所以开发板常把 LED 设计成"VCC → LED → 电阻 → 引脚",引脚输出低电平点亮(电流灌进芯片)。
- 限流电阻可以自己验算:红 LED 导通后两端的正向压降约 1.8~2V,剩下的电压降在电阻上:(3.3−2)/1000 ≈ 1.3mA、(3.3−2)/510 ≈ 2.5mA,亮度足够。 单个引脚灌/拉电流的绝对最大值约 25mA,全芯片所有 IO 电流合计约 100mA。所以 LED 串几百欧到 1kΩ 电阻是标准做法,绝对不允许不串电阻直接把 LED 接在 IO 和地/电源之间。
5 通用 / 复用 与 8 种工作模式
- 通用输出(General Purpose):CPU 直接通过 ODR 控制引脚,点灯就是这种。 复用输出(Alternate Function,AF):CPU 不再直接控制引脚,而是把引脚的控制权交给片上外设(USART 串口、SPI 等),由外设自动输出波形。 复用模式下 ODR 对引脚不起作用,但输入通路依然有效(外设照样能读引脚电平)。
- 输出类型(推挽/开漏)× 控制主体(通用/复用)= 四种输出模式。
- 配置复用功能分两步,缺一不可:① GPIO 设成
AF_PP(复用推挽)或AF_OD(复用开漏);② 初始化并打开对应的外设。 只做第一步,引脚不会有任何波形。 - 注意区分两个容易混的概念:
- GPIO 复用输出:把引脚交给外设来驱动;
- AFIO 复用重映射(Remap):AFIO(Alternate Function IO,复用功能 IO 管理模块)负责把某个外设的信号从默认引脚整体"搬家"到另一组备用引脚上。 两者是两回事。
- F103 没有 F4/H7 等新型号那种"复用功能选择寄存器"(不能任意指定引脚功能):外设与引脚的对应关系是固定的,想换脚只能按手册做重映射。
- 八种工作模式与寄存器编码(每个引脚由 MODE[1:0](模式位)+ CNF[1:0](配置位)共 4 位控制,写在
GPIOx_CRL/GPIOx_CRH两个寄存器里:CRL 管一个端口的低 8 个脚(0~7)、CRH 管高 8 个脚(8~15)):
| 模式 | 库宏 | MODE | CNF |
|---|---|---|---|
| 模拟输入(给 ADC 用) | GPIO_Mode_AIN |
00 | 00 |
| 输入浮空(不带上下拉) | GPIO_Mode_IN_FLOATING |
00 | 01 |
| 输入上拉 / 下拉 | GPIO_Mode_IPU / GPIO_Mode_IPD |
00 | 10 |
| 通用输出推挽 | GPIO_Mode_Out_PP |
速度 | 00 |
| 通用输出开漏 | GPIO_Mode_Out_OD |
速度 | 01 |
| 复用输出推挽 | GPIO_Mode_AF_PP |
速度 | 10 |
| 复用输出开漏 | GPIO_Mode_AF_OD |
速度 | 11 |
- 速度编码不是按频率大小顺序排的,别想当然:
01=10MHz、10=2MHz、11=50MHz(输入模式下 MODE=00)。 - 同一个编码 10(CNF)在输入模式下表示上拉/下拉,至于是上拉还是下拉,由 ODR 对应位决定:ODR=1 上拉、ODR=0 下拉——这些细节
GPIO_Init函数内部已经帮你做了,知道原理即可。 - 宏名的拼写规律=来源(Out 通用输出 / AF 复用输出 / IN 输入 / AI 模拟输入)+ 极性或类型(PP 推挽 / OD 开漏 / PU 上拉 / PD 下拉 / FLOATING 浮空),理解了就不用死记。
6 IO 最大输出速度
- 定义:让引脚电平高速交替翻转(0→1→0→1……)时,波形还能保持基本不变形的最高频率。 它由引脚内部寄生电容、MOS 管导通电阻等硬件特性决定,和你写寄存器的速度没有关系。
- 实际波形不是教科书里的理想方波:一个位周期 = 上升时间(从低爬到高)+ 保持时间(稳定在高/低电平)+ 下降时间(从高跌到低),真正有效的是中间的保持时间。
- 失真判据:上升时间 + 下降时间 ≥ 整个位周期时,保持时间被挤成 0,方波就退化成了三角波。
- 三档速度下的时间分配(单位 ns,最后一列是该档的位周期):
| 档位 | 上升 | 保持 | 下降 | 周期 |
|---|---|---|---|---|
| 2MHz | 125 | 250 | 125 | 500 |
| 10MHz | 25 | 50 | 25 | 100 |
| 50MHz | 5 | 10 | 5 | 20 |
2MHz 那一行是"边沿占掉半个周期"的失真临界示意。 5. 选档原则:在能满足信号频率要求的前提下,选最小的档位。 边沿太陡会增加耗电、产生 EMI(Electromagnetic Interference,电磁干扰)和振铃过冲。 6. 三个典型例题:
| 场景 | 需求 | 选档 |
|---|---|---|
| 驱动 LED | 闪烁频率几十 Hz(45.8/50/100Hz 量级) | 2MHz |
| 读 TLE5012 磁传感器 | SCK 时钟周期最小 120ns ≈ 8.3MHz | 10MHz(能盖住 8.3MHz 的最小档) |
| USB 2.0 Full Speed | 12Mbps | 50MHz |
- 经验法则:GPIO 速度档 ≥ 信号实际翻转频率就行;按键、LED、慢速控制一律 2MHz;USART、SPI 等复用脚按外设的最高时钟选(比如 SPI 时钟 18MHz,就要选 50MHz 档)。
- 反推需求时别把时序表里的 fSSC 8.0Mbit/s(典型值) 和 1/120ns ≈ 8.3MHz(由最小周期反推) 混成一个数——选型要用"最小周期"算最坏情况。
TLE5012 时序表只记这几行(其余建立/保持时间等真正写驱动时再查):
| 参数 | 符号 | 值 |
|---|---|---|
| SSC 波特率(typ) | f_SSC | 8.0 Mbit/s |
| SCK 周期(min) | t_SCKp | 120 ns → 1/120ns ≈ 8.3MHz |
| SCK 高电平(min) | t_SCKh | 40 ns |
| SCK 低电平(min) | t_SCKl | 30 ns |
8.3MHz 取能覆盖它的最小档 = 10MHz。另外板上有两颗晶振:8MHz HSE(主时钟)与 32.768kHz LSE(给 RTC 用),还有 BOOT0/BOOT1 跳线;PC13 丝印就是板载 LED 所在的那只脚。
7 点灯实验:板载接线与配置顺序
- 板载 LED 的接线:D2(LED)阳极接 VCC3V3,阴极经 R5(510Ω/0603 封装)接到 PC13,所以 PC13 要配成开漏输出、写低电平点亮。 写代码前一定先看电路图确认 LED 的极性(电流方向):外接 LED 用推挽通常是"写 1 亮",板载这颗是"写 0 亮",方向正好相反。直接搬代码不改电平,会出现"按下按钮灯反而灭"的怪现象。
- PC13/PC14/PC15 是"受限引脚":灌电流上限约 3mA、翻转频率不得超过 2MHz,所以代码里
GPIO_Speed只能选 2MHz;它们还兼作 RTC 闹钟/侵入检测功能,用 RTC 时会冲突。(待机唤醒脚 WKUP 是 PA0,不是 PC13。) - GPIO 配置的固定四步顺序:开该端口所在总线的时钟 → 填结构体"老三样" → 调用
GPIO_Init写入寄存器 → 循环里读写电平。 老三样是GPIO_InitTypeDef三个字段:
| 字段 | 含义 | 常用取值 |
|---|---|---|
GPIO_Pin |
配置哪个引脚 | GPIO_Pin_0~GPIO_Pin_15,可多个按位或 |
GPIO_Speed |
输出速度档(输出模式才需要) | GPIO_Speed_2MHz(LED/按键常用)、_10MHz、_50MHz |
GPIO_Mode |
工作模式 | 见第 5 节八种模式表(点灯用 Out_OD/Out_PP,按键用 IPU) |
| 4. 四个常用 API(Application Programming Interface,应用程序接口,这里就是库函数)各操作什么: |
| API | 作用 | 读/写哪个寄存器 |
|---|---|---|
GPIO_Init |
配置模式 + 速度 | 写 CRL/CRH |
GPIO_WriteBit |
写 0/1 控制引脚 | 写 ODR(库内部实际走 BSRR) |
GPIO_ReadInputDataBit |
读引脚上的真实电平 | 读 IDR |
GPIO_ReadOutputDataBit |
读"你最近一次让它输出的值" | 读 ODR 的影子 |
- 开漏引脚在外部被别的器件拉低时,
ReadOutputDataBit仍可能读出 1(因为它读的是你软件写进去的值,不是真实电平);想知道引脚此刻的真实电压,必须读 IDR。 void GPIO_WriteBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, BitAction BitValue):第三个参数类型是 BitAction(位动作,一种枚举),成员只有Bit_RESET=0、Bit_SET=1 两个;写成GPIO_Pin_SET是错误的标识符,编译不过。- 整端口一次性写用
GPIO_Write; GPIO_SetBits(置 1)/GPIO_ResetBits(清 0)走的是 BSRR/BRR 专用寄存器,是"原子操作"(一条指令完成、不可被打断),在中断里用更安全——普通的"读 ODR → 改一位 → 写回 ODR"是读-改-写三步,中途若被中断打断可能出错。
- 整端口一次性写用
GPIO_Init只在被调用的那一瞬间读取结构体内容并写进寄存器,之后再改结构体变量不会影响已经配好的引脚。 第二次初始化另一个引脚时可以放心省掉三行:重复开时钟(同组已经开过)、重复声明结构体、输入模式下的GPIO_Speed(输入模式不需要速度)。 但要注意:复用同一个结构体时,上一轮赋过的字段会残留(本例残留的恰好是GPIO_Speed,对输入无影响所以看不出来);稳妥做法是每次初始化前把结构体= {0}清零,或把所有字段都显式重新赋值。- 现象验证:配置一完成 LED 就已经亮了——因为 ODR 复位默认值是 0,引脚被配成开漏输出的瞬间相当于写了 0,灯即亮;随后
GPIO_WriteBit(..., Bit_SET)反而让灯灭。(复位后引脚是浮空输入、不驱动任何电平,所以配置前灯不亮;亮发生在GPIO_Init之后。)
8 四种输入模式与施密特触发器
- 这一章要解决的问题:CPU 通过读寄存器里的 0/1,来判断引脚上现在是低电压还是高电压。 链路分三段:引脚模拟电压 → 整形/保护电路 → 采样锁存进 IDR。
- 输入通路上各元件的作用:
| 元件 | 作用 |
|---|---|
| 上拉电阻(经开关接 VDD) | 没人驱动引脚时,让引脚默认是高电平 |
| 下拉电阻(经开关接 GND) | 没人驱动引脚时,让引脚默认是低电平 |
| 施密特触发器 | 把缓慢变化、带毛刺的电压整成干净的 0/1;带迟滞(回差)能抗抖动;输入阻抗极高,几乎不取电流,不会把外部电平拽偏 |
| 保护二极管(接 VDD / VSS) | 过压/负压时钳位,防静电(见第 10 张卡) |
| 输入寄存器 IDR | CPU 读的就是触发器锁存的输出 |
- 判决靠两个阈值电压:输入高于正向阈值 Vt+ 才判 1,低于负向阈值 Vt− 才判 0,落在两者之间时保持上一次的结果不变。 两个阈值之间的差距叫"回差(迟滞,Hysteresis)",正是它给了抗抖动能力——电压在阈值附近小幅抖动时,输出不会跟着乱跳。
- 四种输入模式 = 上拉开关、下拉开关通断的四种组合:上拉闭合 = IPU,下拉闭合 = IPD,两个都断开 = 浮空输入; 模拟输入 AIN 则把施密特触发器这条数字通路整个断开,只留引脚直达 ADC 等模拟外设的通道,所以上下拉开关也必须断开(否则电阻会与外部信号分压,破坏采集精度),同时省电。
- 内部上/下拉电阻典型值约 30~50kΩ。外部接地时电流 I = 3.3V / 40kΩ ≈ 80μA。阻值故意做这么大:既能把悬空引脚稳定在一个确定电平上,又保证被外部反向驱动时只有几十到上百微安电流,不会烧芯片。 这就是"按键一端接地、单片机用上拉输入"成为标准接法的原因。
- 上拉输入时的三种外部情况:
| 外部状态 | 电流路径 | 读到 |
|---|---|---|
| 悬空(什么都没接) | 几乎无电流,电压被拉到接近 VDD | 1 |
| 接地/外部给低 | VDD → 上拉电阻 → 引脚 → 外部 GND,电流被电阻限制在几十 μA | 0 |
| 外部给高(不超过 VDD) | 两边电位一致,几乎无电流 | 1 |
外部给 5V 时电流方向会反过来并走保护二极管,就不再是"无电流"了(详见第 10 张卡)。 7. 下拉输入完全镜像:悬空读 0、外部给高读 1,适合"平时低电平、有效时高电平"的信号(比如按键另一端接 VDD)。 8. 上拉 ≠ 输出高电平。 上拉只是"没人驱动时给个默认高电平",外部给一个低电平(按键接地)就能轻松把它拉下来;引脚方向始终是输入,不会和外部器件"打架"。 9. STM32F103 复位后,所有 GPIO 默认都是浮空输入——忘了配置模式的引脚就像一根天线,读到什么全看环境干扰。
9 何时浮空、何时上下拉
- 判断原则:外部有确定的驱动源(对端是推挽输出、总线驱动器等)→ 浮空输入即可;外部可能悬空(按键、没插的接插件、开路集电极输出)→ 必须配上拉或下拉,否则读到随机值,程序可能莫名其妙地乱跳。
- 浮空引脚会像天线一样感应出抖动波形:手靠近一点、摸一下板子,读数就可能翻转。排查"按键偶尔自己触发"这类 bug,第一个怀疑对象就是引脚没配上拉/下拉。
- 按钮另一端接 GND → 只能配输入上拉(IPU):若配成下拉,松开时引脚被拉到 0、按下时引脚通过按钮还是接到 GND 读到 0,两种状态永远读不出区别。 反之,按钮另一端接 3.3V → 只能配输入下拉(IPD)。上下拉的默认电平必须与按钮另一端所接电平相反,才能读出两个状态。
- 轻触开关通常有 4 个脚,内部只有对角的两个脚是导通的(另外两个是另一对),插错孔位的表现就是"按了没反应"。
10 保护二极管与绝对极限电压
- 每个引脚内部有两只二极管:一只朝 VDD、一只朝 VSS,引脚接在两只二极管的中间节点上,属于芯片内部 ESD(Electro-Static Discharge,静电放电)保护结构的一部分;正常工作时两只二极管都反偏截止、不导通。
- 引脚电压过高(超过 VDD)时,引脚 → 二极管 → VDD 导通,多余电流被"泄放"进电源轨;引脚电压为负(低于 VSS)时,VSS → 二极管 → 引脚 导通,把引脚钳在约 −0.7V。
- 电流方向的定义=正电荷移动的方向,从高电位流向低电位;VSS=0V 比 −1V 电位高,所以电流从 VSS 流向引脚,把引脚电位"托住",这就是"钳位"。
- 二极管只能限制电压幅度,不能限制电流大小:如果外部源内阻很小(比如 5V 直接硬连),会持续向 VDD 灌入大电流,可能顶坏芯片,或把整片电源抬高引起复位。
- VSS−0.3V ~ VDD+0.3V 是手册给的"允许超出电源轨的最大电压"(绝对极限参数),按"超出 0.3V 即属于违规使用"来记;二极管真正明显导通的正向压降约 0.6~0.7V,0.3V 只是厂家留的安全余量——到 0.3V 时其实已经有可观的注入电流了。
- "5V 容忍(FT,5V Tolerant)"靠的是另一套结构(厚氧化层器件/内部电平转换),不是这两只二极管。F103 大多数数字脚带 FT 标记、可容忍 5V 输入;不耐 5V 的脚是 PC13~PC15、PD0/PD1、PA11/PA12。"不得超出电源轨 0.3V"和"FT 脚可承受 5V"是两个不同的概念,别混。
| 参数 | 数值 | 说明 |
|---|---|---|
| 引脚电压(绝对极限) | VSS−0.3V ~ VDD+0.3V | 超出即属违规使用,不是"还能凑合用" |
| 单脚灌 / 拉电流(绝对最大) | 约 25mA | 超了烧引脚 |
| 全片 IO 电流合计 | 约 100mA | 材料给的量级,具体以数据手册为准 |
| 保护二极管明显导通 | 0.6~0.7V | 0.3V 时已有可观注入电流 |
| 内部上 / 下拉电阻 | 30~50kΩ | 3.3V/40kΩ≈80μA |
| FT 引脚输入 | 可到 5V | PC13~15、PD0/1、PA11/12 不是 FT |
11 完整代码
点灯
这段代码做什么这段代码让板载 PC13 LED 以 100ms 亮、100ms 灭循环闪烁,用来验证 GPIO 输出的固定四步:开时钟、填结构体、GPIO_Init、循环写电平。
- 先开 GPIOC 时钟。时钟不开,后面无论怎么写 ODR、CRL/CRH 都不会生效,这是“代码没错但灯不亮”的头号原因。
- PC13 是受限引脚,速度只能选 2MHz;板载 LED 是开漏接法,所以配 GPIO_Mode_Out_OD,写 0 把引脚拉低、灌电流点亮。
- GPIO_Init 把引脚号、模式、速度写进寄存器;主循环再用 GPIO_WriteBit 翻转电平,两次 Delay 控制亮灭时间。
- 写 1 时开漏管松手,引脚变高阻,LED 两端没有压差所以熄灭;写 0 才是点亮。
PC13 低电平点亮;开漏写 1 是高阻,不是输出高电平。
按钮长按跟随
这段代码做什么这段代码让 PA1 按钮按住时 PA0 LED 亮、松开灭,用来练输入上拉和 IDR 读取。
- PA0 和 PA1 同属 GPIOA,开一次时钟即可,不必为每根脚重复开。
- PA0 配推挽输出,写 1 点亮;PA1 配内部上拉输入,按钮另一端接 GND,所以松开读 1、按下读 0。
- 主循环读 PA1 的 IDR:读到 0 就让 PA0 输出高,读到 1 就输出低。这里没有消抖,机械抖动时可能闪跳。
按钮接 GND 必须上拉;输入模式不需要 GPIO_Speed。
代码流程四步:开时钟 → 填老三样 → GPIO_Init → 循环里"读 IDR 判断电平、写 ODR 输出电平"。
⚠️ 这段代码没有做按键消抖,实际按下/松开瞬间会有机械抖动,灯可能偶尔闪几下——留到后面按钮驱动章节用 Delay(10) 消抖解决。
⚠️ 输入模式那轮没写 GPIO_Speed,结构体里残留着上一轮的 2MHz;对输入无影响,但养成清零习惯,换成别的字段残留就会踩坑。
📌 本章必背速记
- 任何外设初始化第一步都是开时钟:GPIOA/B/C 在 APB2 总线,用
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);不开时钟引脚完全不动。 - 8 种工作模式:4 种输入(浮空/上拉/下拉/模拟)+ 4 种输出(推挽/开漏/复用推挽/复用开漏);点灯用推挽输出,接 I2C 用开漏。
- 推挽 vs 开漏:推挽=芯片主动输出强 0 和强 1(LED、数字信号);开漏=只能拉低到 0,输出 1 必须靠外部上拉电阻(I2C 总线就靠这个"线与")。
- 速度档位 2/10/50MHz:指引脚电平翻转的速率,不是通信波特率;点灯/按键这类慢信号填 2MHz,高速通信才拉高。
- 读和写:输入用
GPIO_ReadInputDataBit(GPIOx, GPIO_Pin_y)读 IDR;输出用GPIO_SetBits/GPIO_ResetBits/GPIO_WriteBit写 ODR。 - 按键配置口诀:上拉输入 + 按下接地 → 按下读到 0、松开读到 1;不要用浮空输入接机械按键(悬空电平会乱跳)。
- 初始化四步走:开时钟 →
GPIO_InitTypeDef填引脚/模式/速度 →GPIO_Init→ 主循环里读写。 - 电压红线:引脚输入电压不能超过 VDD+0.3V(F103 是 3.6V 上限);只有标注 5V tolerant 的脚才能直接接 5V 信号。