STM32 学习笔记:GPIO 输出实验——LED 闪烁、流水灯与蜂鸣器

bash 复制代码
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_BackupResetCmd(FunctionalState NewState);
void RCC_ClockSecuritySystemCmd(FunctionalState NewState);
void RCC_MCOConfig(uint8_t RCC_MCO);
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
void RCC_ClearFlag(void);
ITStatus RCC_GetITStatus(uint8_t RCC_IT);
void RCC_ClearITPendingBit(uint8_t RCC_IT);
bash 复制代码
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState);
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

STM32 学习笔记:GPIO 输出实验------LED 闪烁、流水灯与蜂鸣器

对应课程:[3-2] LED 闪烁、LED 流水灯与蜂鸣器

课堂记录:D:/2026-08-30 14_32 记录 LED点亮_原文.docx

整理日期:2026-08-30

芯片:STM32F103C8T6

开发方式:Keil MDK + STM32F10x 标准外设库

主要源码:程序源码/STM32Project-有注释版/3-1~3-3

前置笔记:03_GPIO输出原理与硬件设计.md

本节边界:完成 GPIO 输出的三个程序;按键与传感器输入从下一节开始

1. 老师这一节的整条思路

上一节解决"GPIO 内部为什么能输出",这一节把原理落实成三个可运行程序。老师采用的是逐层扩展思路:
#mermaid-svg-tN91VWPKqMRqookM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tN91VWPKqMRqookM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tN91VWPKqMRqookM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tN91VWPKqMRqookM .error-icon{fill:#552222;}#mermaid-svg-tN91VWPKqMRqookM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tN91VWPKqMRqookM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tN91VWPKqMRqookM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tN91VWPKqMRqookM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tN91VWPKqMRqookM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tN91VWPKqMRqookM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tN91VWPKqMRqookM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tN91VWPKqMRqookM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tN91VWPKqMRqookM .marker.cross{stroke:#333333;}#mermaid-svg-tN91VWPKqMRqookM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tN91VWPKqMRqookM p{margin:0;}#mermaid-svg-tN91VWPKqMRqookM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tN91VWPKqMRqookM .cluster-label text{fill:#333;}#mermaid-svg-tN91VWPKqMRqookM .cluster-label span{color:#333;}#mermaid-svg-tN91VWPKqMRqookM .cluster-label span p{background-color:transparent;}#mermaid-svg-tN91VWPKqMRqookM .label text,#mermaid-svg-tN91VWPKqMRqookM span{fill:#333;color:#333;}#mermaid-svg-tN91VWPKqMRqookM .node rect,#mermaid-svg-tN91VWPKqMRqookM .node circle,#mermaid-svg-tN91VWPKqMRqookM .node ellipse,#mermaid-svg-tN91VWPKqMRqookM .node polygon,#mermaid-svg-tN91VWPKqMRqookM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tN91VWPKqMRqookM .rough-node .label text,#mermaid-svg-tN91VWPKqMRqookM .node .label text,#mermaid-svg-tN91VWPKqMRqookM .image-shape .label,#mermaid-svg-tN91VWPKqMRqookM .icon-shape .label{text-anchor:middle;}#mermaid-svg-tN91VWPKqMRqookM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tN91VWPKqMRqookM .rough-node .label,#mermaid-svg-tN91VWPKqMRqookM .node .label,#mermaid-svg-tN91VWPKqMRqookM .image-shape .label,#mermaid-svg-tN91VWPKqMRqookM .icon-shape .label{text-align:center;}#mermaid-svg-tN91VWPKqMRqookM .node.clickable{cursor:pointer;}#mermaid-svg-tN91VWPKqMRqookM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tN91VWPKqMRqookM .arrowheadPath{fill:#333333;}#mermaid-svg-tN91VWPKqMRqookM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tN91VWPKqMRqookM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tN91VWPKqMRqookM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tN91VWPKqMRqookM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tN91VWPKqMRqookM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tN91VWPKqMRqookM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tN91VWPKqMRqookM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tN91VWPKqMRqookM .cluster text{fill:#333;}#mermaid-svg-tN91VWPKqMRqookM .cluster span{color:#333;}#mermaid-svg-tN91VWPKqMRqookM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tN91VWPKqMRqookM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tN91VWPKqMRqookM rect.text{fill:none;stroke-width:0;}#mermaid-svg-tN91VWPKqMRqookM .icon-shape,#mermaid-svg-tN91VWPKqMRqookM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tN91VWPKqMRqookM .icon-shape p,#mermaid-svg-tN91VWPKqMRqookM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tN91VWPKqMRqookM .icon-shape .label rect,#mermaid-svg-tN91VWPKqMRqookM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tN91VWPKqMRqookM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tN91VWPKqMRqookM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tN91VWPKqMRqookM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 按接线图搭硬件
回顾工程配置
查RCC和GPIO库函数
PA0输出高低电平
加入Delay形成LED闪烁
验证推挽和开漏差异
扩展PA0~PA7做流水灯
换成PB12控制蜂鸣器
总结库函数查阅方法

三个程序的变化看似很多,实际都在复用同一套框架:

text 复制代码
开启端口时钟 → 配置引脚模式 → 在 while(1) 中改变输出 → 延时 → 观察负载

本节真正要学会的不是背三份程序,而是:看到接线图后,能独立替换端口、引脚和输出节奏。


2. 三个实验先建立全局认识

工程 输出引脚 外部器件 有效电平 主要新知识
3-1 LED闪烁 PA0 单个 LED 低电平点亮 时钟、结构体初始化、单个位输出、延时
3-2 LED流水灯 PA0~PA7 8 个 LED 低电平点亮 引脚位掩码、按位或、整端口写入、按位取反
3-3 蜂鸣器 PB12 有源蜂鸣器模块 低电平鸣响 更换端口和引脚、用时间序列设计声音节奏

#mermaid-svg-58AlKskbjCT7izq1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-58AlKskbjCT7izq1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-58AlKskbjCT7izq1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-58AlKskbjCT7izq1 .error-icon{fill:#552222;}#mermaid-svg-58AlKskbjCT7izq1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-58AlKskbjCT7izq1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-58AlKskbjCT7izq1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-58AlKskbjCT7izq1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-58AlKskbjCT7izq1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-58AlKskbjCT7izq1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-58AlKskbjCT7izq1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-58AlKskbjCT7izq1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-58AlKskbjCT7izq1 .marker.cross{stroke:#333333;}#mermaid-svg-58AlKskbjCT7izq1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-58AlKskbjCT7izq1 p{margin:0;}#mermaid-svg-58AlKskbjCT7izq1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-58AlKskbjCT7izq1 .cluster-label text{fill:#333;}#mermaid-svg-58AlKskbjCT7izq1 .cluster-label span{color:#333;}#mermaid-svg-58AlKskbjCT7izq1 .cluster-label span p{background-color:transparent;}#mermaid-svg-58AlKskbjCT7izq1 .label text,#mermaid-svg-58AlKskbjCT7izq1 span{fill:#333;color:#333;}#mermaid-svg-58AlKskbjCT7izq1 .node rect,#mermaid-svg-58AlKskbjCT7izq1 .node circle,#mermaid-svg-58AlKskbjCT7izq1 .node ellipse,#mermaid-svg-58AlKskbjCT7izq1 .node polygon,#mermaid-svg-58AlKskbjCT7izq1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-58AlKskbjCT7izq1 .rough-node .label text,#mermaid-svg-58AlKskbjCT7izq1 .node .label text,#mermaid-svg-58AlKskbjCT7izq1 .image-shape .label,#mermaid-svg-58AlKskbjCT7izq1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-58AlKskbjCT7izq1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-58AlKskbjCT7izq1 .rough-node .label,#mermaid-svg-58AlKskbjCT7izq1 .node .label,#mermaid-svg-58AlKskbjCT7izq1 .image-shape .label,#mermaid-svg-58AlKskbjCT7izq1 .icon-shape .label{text-align:center;}#mermaid-svg-58AlKskbjCT7izq1 .node.clickable{cursor:pointer;}#mermaid-svg-58AlKskbjCT7izq1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-58AlKskbjCT7izq1 .arrowheadPath{fill:#333333;}#mermaid-svg-58AlKskbjCT7izq1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-58AlKskbjCT7izq1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-58AlKskbjCT7izq1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-58AlKskbjCT7izq1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-58AlKskbjCT7izq1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-58AlKskbjCT7izq1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-58AlKskbjCT7izq1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-58AlKskbjCT7izq1 .cluster text{fill:#333;}#mermaid-svg-58AlKskbjCT7izq1 .cluster span{color:#333;}#mermaid-svg-58AlKskbjCT7izq1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-58AlKskbjCT7izq1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-58AlKskbjCT7izq1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-58AlKskbjCT7izq1 .icon-shape,#mermaid-svg-58AlKskbjCT7izq1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-58AlKskbjCT7izq1 .icon-shape p,#mermaid-svg-58AlKskbjCT7izq1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-58AlKskbjCT7izq1 .icon-shape .label rect,#mermaid-svg-58AlKskbjCT7izq1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-58AlKskbjCT7izq1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-58AlKskbjCT7izq1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-58AlKskbjCT7izq1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 同一套GPIO输出模板
PA0

一个LED
PA0~PA7

八个LED
PB12

蜂鸣器

课堂位置:录音 00:02--00:56 介绍接线;03:21--20:54 完成三个程序。


3. 实验一的硬件:PA0 低电平点亮 LED

图 1 有注释版源码中的 3-1 LED闪烁 接线图。

3.1 接线关系

接线图中的关键连接是:

  • 最小系统板 3.3V 接面包板正电源轨;
  • 最小系统板 GND 接面包板负电源轨;
  • LED 正极接 3.3V
  • LED 负极接 PA0
  • ST-LINK 使用 3.3V、GND、SWCLK、SWDIO 与最小系统板连接。

电流路径为:

text 复制代码
3.3V → LED → PA0 → STM32内部N-MOS → GND

所以:

PA0 输出 LED 两端情况 LED 状态
0,低电平 形成正向压差并有电流 点亮
1,高电平 两端都接近 3.3 V 熄灭

这叫低电平有效。因此代码中的"清零"并不等于"关闭设备",必须根据原理图判断实际效果。

!WARNING

课堂为了搭建方便省略了串联限流电阻。自行设计和长期使用时,应给普通 LED 串联合适的限流电阻,例如 680 Ω~1 kΩ,不能把课堂临时接法当成通用硬件规范。

不同批次 ST-LINK 和最小系统板的排针顺序可能不同,要按照外壳或 PCB 丝印逐一对应:

ST-LINK 信号 最小系统板信号 作用
3.3V 3.3/3V3 目标电压参考或供电,按具体下载器使用
GND G/GND 共地
SWCLK DCLK/SWCLK SWD 时钟
SWDIO DIO/SWIO/SWDIO SWD 数据

不要根据"第几个针脚"盲接。先看丝印,再确认电源和地没有接反。

课堂位置:录音 00:02--00:56;师兄笔记 PDF 第 20 页。


4. 工程建立回顾:为什么后续直接复制工程

老师先快速重建了一次标准库工程,之后的实验直接复制已有工程修改。完整过程已在 02_软件安装与新建工程.md 中整理,本节只记住工程运行所需的几个条件:

  1. Start 中有正确启动文件、CMSIS 和芯片头文件;
  2. Library 中加入标准外设库源码;
  3. User 中有 main.c、配置文件和中断文件;
  4. Include Paths 包含 Start、Library、User,加入延时模块后还要包含 System
  5. 定义 USE_STDPERIPH_DRIVER
  6. Debug 选择 ST-LINK,Flash Download 勾选 Reset and Run

4.1 keilkill.bat 的作用

Keil 编译产生的 ListingsObjects 等中间文件会占用较多空间。课程提供的批处理文件用于分享工程前清理这些可重新生成的文件,从而显著减小工程体积。

它不会代替源码备份;分享前仍应确认 Start、Library、System、User 和 Keil 工程文件都在。

课堂位置:录音 00:56--03:21


5. 使用标准库控制 GPIO 的三步模板

老师把 GPIO 输出程序概括为三步:
#mermaid-svg-s5nHvKmUHKNkSfOq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s5nHvKmUHKNkSfOq .error-icon{fill:#552222;}#mermaid-svg-s5nHvKmUHKNkSfOq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s5nHvKmUHKNkSfOq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s5nHvKmUHKNkSfOq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s5nHvKmUHKNkSfOq .marker.cross{stroke:#333333;}#mermaid-svg-s5nHvKmUHKNkSfOq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s5nHvKmUHKNkSfOq p{margin:0;}#mermaid-svg-s5nHvKmUHKNkSfOq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s5nHvKmUHKNkSfOq .cluster-label text{fill:#333;}#mermaid-svg-s5nHvKmUHKNkSfOq .cluster-label span{color:#333;}#mermaid-svg-s5nHvKmUHKNkSfOq .cluster-label span p{background-color:transparent;}#mermaid-svg-s5nHvKmUHKNkSfOq .label text,#mermaid-svg-s5nHvKmUHKNkSfOq span{fill:#333;color:#333;}#mermaid-svg-s5nHvKmUHKNkSfOq .node rect,#mermaid-svg-s5nHvKmUHKNkSfOq .node circle,#mermaid-svg-s5nHvKmUHKNkSfOq .node ellipse,#mermaid-svg-s5nHvKmUHKNkSfOq .node polygon,#mermaid-svg-s5nHvKmUHKNkSfOq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s5nHvKmUHKNkSfOq .rough-node .label text,#mermaid-svg-s5nHvKmUHKNkSfOq .node .label text,#mermaid-svg-s5nHvKmUHKNkSfOq .image-shape .label,#mermaid-svg-s5nHvKmUHKNkSfOq .icon-shape .label{text-anchor:middle;}#mermaid-svg-s5nHvKmUHKNkSfOq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s5nHvKmUHKNkSfOq .rough-node .label,#mermaid-svg-s5nHvKmUHKNkSfOq .node .label,#mermaid-svg-s5nHvKmUHKNkSfOq .image-shape .label,#mermaid-svg-s5nHvKmUHKNkSfOq .icon-shape .label{text-align:center;}#mermaid-svg-s5nHvKmUHKNkSfOq .node.clickable{cursor:pointer;}#mermaid-svg-s5nHvKmUHKNkSfOq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s5nHvKmUHKNkSfOq .arrowheadPath{fill:#333333;}#mermaid-svg-s5nHvKmUHKNkSfOq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s5nHvKmUHKNkSfOq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s5nHvKmUHKNkSfOq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s5nHvKmUHKNkSfOq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s5nHvKmUHKNkSfOq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s5nHvKmUHKNkSfOq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s5nHvKmUHKNkSfOq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s5nHvKmUHKNkSfOq .cluster text{fill:#333;}#mermaid-svg-s5nHvKmUHKNkSfOq .cluster span{color:#333;}#mermaid-svg-s5nHvKmUHKNkSfOq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s5nHvKmUHKNkSfOq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s5nHvKmUHKNkSfOq rect.text{fill:none;stroke-width:0;}#mermaid-svg-s5nHvKmUHKNkSfOq .icon-shape,#mermaid-svg-s5nHvKmUHKNkSfOq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s5nHvKmUHKNkSfOq .icon-shape p,#mermaid-svg-s5nHvKmUHKNkSfOq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s5nHvKmUHKNkSfOq .icon-shape .label rect,#mermaid-svg-s5nHvKmUHKNkSfOq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s5nHvKmUHKNkSfOq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s5nHvKmUHKNkSfOq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s5nHvKmUHKNkSfOq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. RCC开启GPIO时钟
2. GPIO_Init配置引脚
3. GPIO输出函数改变电平

对应到 PA0 点灯:

c 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_ResetBits(GPIOA, GPIO_Pin_0);  // PA0低电平,LED亮

代码分为"配置"和"运行"两层:

  • 时钟和 GPIO_Init() 通常只执行一次,决定 PA0 以后怎样工作;
  • SetBits/ResetBits/WriteBit 可以反复调用,决定 PA0 此刻输出什么电平。

6. 第一步:打开 GPIOA 的 APB2 时钟

c 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

6.1 两个参数分别表示什么

参数 本例取值 含义
RCC_APB2Periph RCC_APB2Periph_GPIOA 选择 GPIOA 外设时钟
NewState ENABLE 开启时钟;DISABLE 则关闭

标准库头文件中:

c 复制代码
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)

这些数值分别对应 RCC_APB2ENR 的 IOPAEN、IOPBEN 位。库函数内部最终执行:

c 复制代码
RCC->APB2ENR |= RCC_APB2Periph;

所以标准库不是绕开寄存器,而是用可读的名字封装寄存器位操作。
#mermaid-svg-O41B1EShVrihHFEe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O41B1EShVrihHFEe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O41B1EShVrihHFEe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O41B1EShVrihHFEe .error-icon{fill:#552222;}#mermaid-svg-O41B1EShVrihHFEe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O41B1EShVrihHFEe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O41B1EShVrihHFEe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O41B1EShVrihHFEe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O41B1EShVrihHFEe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O41B1EShVrihHFEe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O41B1EShVrihHFEe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O41B1EShVrihHFEe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O41B1EShVrihHFEe .marker.cross{stroke:#333333;}#mermaid-svg-O41B1EShVrihHFEe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O41B1EShVrihHFEe p{margin:0;}#mermaid-svg-O41B1EShVrihHFEe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-O41B1EShVrihHFEe .cluster-label text{fill:#333;}#mermaid-svg-O41B1EShVrihHFEe .cluster-label span{color:#333;}#mermaid-svg-O41B1EShVrihHFEe .cluster-label span p{background-color:transparent;}#mermaid-svg-O41B1EShVrihHFEe .label text,#mermaid-svg-O41B1EShVrihHFEe span{fill:#333;color:#333;}#mermaid-svg-O41B1EShVrihHFEe .node rect,#mermaid-svg-O41B1EShVrihHFEe .node circle,#mermaid-svg-O41B1EShVrihHFEe .node ellipse,#mermaid-svg-O41B1EShVrihHFEe .node polygon,#mermaid-svg-O41B1EShVrihHFEe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-O41B1EShVrihHFEe .rough-node .label text,#mermaid-svg-O41B1EShVrihHFEe .node .label text,#mermaid-svg-O41B1EShVrihHFEe .image-shape .label,#mermaid-svg-O41B1EShVrihHFEe .icon-shape .label{text-anchor:middle;}#mermaid-svg-O41B1EShVrihHFEe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-O41B1EShVrihHFEe .rough-node .label,#mermaid-svg-O41B1EShVrihHFEe .node .label,#mermaid-svg-O41B1EShVrihHFEe .image-shape .label,#mermaid-svg-O41B1EShVrihHFEe .icon-shape .label{text-align:center;}#mermaid-svg-O41B1EShVrihHFEe .node.clickable{cursor:pointer;}#mermaid-svg-O41B1EShVrihHFEe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-O41B1EShVrihHFEe .arrowheadPath{fill:#333333;}#mermaid-svg-O41B1EShVrihHFEe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-O41B1EShVrihHFEe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-O41B1EShVrihHFEe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O41B1EShVrihHFEe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-O41B1EShVrihHFEe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O41B1EShVrihHFEe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-O41B1EShVrihHFEe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-O41B1EShVrihHFEe .cluster text{fill:#333;}#mermaid-svg-O41B1EShVrihHFEe .cluster span{color:#333;}#mermaid-svg-O41B1EShVrihHFEe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-O41B1EShVrihHFEe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-O41B1EShVrihHFEe rect.text{fill:none;stroke-width:0;}#mermaid-svg-O41B1EShVrihHFEe .icon-shape,#mermaid-svg-O41B1EShVrihHFEe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O41B1EShVrihHFEe .icon-shape p,#mermaid-svg-O41B1EShVrihHFEe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-O41B1EShVrihHFEe .icon-shape .label rect,#mermaid-svg-O41B1EShVrihHFEe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O41B1EShVrihHFEe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-O41B1EShVrihHFEe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-O41B1EShVrihHFEe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RCC_APB2PeriphClockCmd
标准库检查参数
RCC->APB2ENR置位
GPIOA时钟打开

参考:有注释版 stm32f10x_rcc.c 第 1095--1107 行;参考手册 PDF 第 70--71 页。

6.2 为什么 GPIOA 和 GPIOB 不能混用

后面的 GPIO_Init(GPIOA, ...) 和输出函数都访问 GPIOA。如果只打开 GPIOB 时钟,GPIOA 仍未工作,配置不会得到预期结果。

端口必须前后一致:

text 复制代码
RCC开启GPIOA → GPIO_Init传GPIOA → 输出函数传GPIOA

蜂鸣器改用 PB12 时,这三处都要改成 GPIOB。


7. 第二步:用结构体描述 GPIO 配置

7.1 为什么使用结构体

一个 GPIO 初始化动作同时需要模式、引脚和速度。标准库把这些参数组合到 GPIO_InitTypeDef

c 复制代码
GPIO_InitTypeDef GPIO_InitStructure;

它是一个局部结构体变量。随后使用点运算符访问成员:

c 复制代码
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

老式 C 编译器常要求局部变量定义放在函数开头。课程当前编译器允许在执行语句之后定义,但为了兼容性和可读性,也可以统一把局部变量放在函数开头。

7.2 为什么选择通用推挽输出

c 复制代码
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

名字可以拆开:

text 复制代码
Out = 输出
PP  = Push-Pull,推挽

LED 由 CPU 普通 GPIO 控制,不需要片上外设接管;同时需要引脚能够主动输出高、低电平,所以选择通用推挽输出

标准库的 8 种模式定义在 stm32f10x_gpio.h 第 71--80 行。当前课程最常见的几项:

标准库枚举 模式
GPIO_Mode_AIN 模拟输入
GPIO_Mode_IN_FLOATING 浮空输入
GPIO_Mode_IPD 下拉输入
GPIO_Mode_IPU 上拉输入
GPIO_Mode_Out_OD 通用开漏输出
GPIO_Mode_Out_PP 通用推挽输出
GPIO_Mode_AF_OD 复用开漏输出
GPIO_Mode_AF_PP 复用推挽输出

7.3 GPIO_Pin_0 为什么是 0x0001

c 复制代码
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;

标准库把每个引脚定义成一个独立位:

引脚宏 十六进制 16 位二进制中的置位位置
GPIO_Pin_0 0x0001 bit0
GPIO_Pin_1 0x0002 bit1
GPIO_Pin_2 0x0004 bit2
GPIO_Pin_12 0x1000 bit12
GPIO_Pin_All 0xFFFF bit0~bit15 全部为 1

这样既能选择一个引脚,也能用按位或同时选择多个引脚:

c 复制代码
GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2

结果低 3 位为 111,表示 PA0、PA1、PA2 都被选中。

7.4 GPIO_Speed_50MHz 表示什么

c 复制代码
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

这是 GPIO 输出速度等级,影响输出边沿和最大切换能力,并不是调用后引脚会自动产生 50 MHz 方波。老师在教学工程中统一选择 50 MHz;正式工程宜选择满足需求的最低速度。

7.5 为什么 GPIO_Init() 要传地址

c 复制代码
GPIO_Init(GPIOA, &GPIO_InitStructure);
  • 第一个参数 GPIOA:指定要配置哪个端口;
  • 第二个参数 &GPIO_InitStructure:把结构体变量的地址传给函数。

函数拿到地址后,能读取三个成员并完成寄存器配置。对于 PA0,它最终修改 GPIOA 的 CRL;对于 PB12,它最终修改 GPIOB 的 CRH

库函数的主要逻辑是:

  1. 从模式枚举中取出 CNF 配置;
  2. 合并速度对应的 MODE 位;
  3. 遍历 GPIO_Pin 中被选中的位;
  4. Px0~Px7 写入 CRL,Px8~Px15 写入 CRH

参考:有注释版 stm32f10x_gpio.c 第 173--258 行;参考手册 PDF 第 106、113--114 页。


8. 第三步:四个 GPIO 输出函数怎样选择

本节学习了四个输出函数:

函数 控制范围 参数表达 底层寄存器 适合用途
GPIO_SetBits() 一个或多个选定位 置为高电平 BSRR 把指定引脚置 1
GPIO_ResetBits() 一个或多个选定位 置为低电平 BRR 把指定引脚清 0
GPIO_WriteBit() 单个引脚 Bit_SET/Bit_RESET BSRR/BRR 用一个函数表达高低电平
GPIO_Write() 整个 16 位端口 16 位数值 ODR 同时更新一个端口的全部位

8.1 SetBitsResetBits

c 复制代码
GPIO_SetBits(GPIOA, GPIO_Pin_0);    // PA0 = 1
GPIO_ResetBits(GPIOA, GPIO_Pin_0);  // PA0 = 0

在本实验的低电平点亮电路中:

text 复制代码
ResetBits → PA0低 → LED亮
SetBits   → PA0高 → LED灭

函数内部非常直接:

c 复制代码
GPIOx->BSRR = GPIO_Pin;  // SetBits
GPIOx->BRR  = GPIO_Pin;  // ResetBits

8.2 GPIO_WriteBit

c 复制代码
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);  // PA0 = 0
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);    // PA0 = 1

BitAction 是枚举类型:

c 复制代码
typedef enum
{
    Bit_RESET = 0,
    Bit_SET
} BitAction;

如果直接写数字:

c 复制代码
GPIO_WriteBit(GPIOA, GPIO_Pin_0, 0);

编译器可能警告"枚举类型与其他类型混用"。课程演示了显式强制转换:

c 复制代码
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);

工程中优先使用 Bit_RESETBit_SET,语义更清楚,也避免把其他数字误传给枚举参数。

8.3 GPIO_Write

c 复制代码
GPIO_Write(GPIOA, 0xFFFE);

它会直接执行:

c 复制代码
GPIOA->ODR = 0xFFFE;

因此 GPIOA 的 16 个输出位会一起更新。它适合流水灯这种整组端口输出,但如果同一端口还有其他设备,就可能把那些引脚的状态一起覆盖。

参考:有注释版 stm32f10x_gpio.c 第 358--423 行;参考手册 PDF 第 115--116 页。


9. 程序一:LED 闪烁

9.1 课堂逻辑的最小版本

c 复制代码
#include "stm32f10x.h"
#include "Delay.h"

int main(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    while (1)
    {
        GPIO_ResetBits(GPIOA, GPIO_Pin_0);  // 低电平,LED亮
        Delay_ms(500);
        GPIO_SetBits(GPIOA, GPIO_Pin_0);    // 高电平,LED灭
        Delay_ms(500);
    }
}

9.2 一轮循环发生了什么

LED PA0 CPU LED PA0 CPU #mermaid-svg-MwC3eKocnDJKW1Bc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MwC3eKocnDJKW1Bc .error-icon{fill:#552222;}#mermaid-svg-MwC3eKocnDJKW1Bc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MwC3eKocnDJKW1Bc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MwC3eKocnDJKW1Bc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MwC3eKocnDJKW1Bc .marker.cross{stroke:#333333;}#mermaid-svg-MwC3eKocnDJKW1Bc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MwC3eKocnDJKW1Bc p{margin:0;}#mermaid-svg-MwC3eKocnDJKW1Bc .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MwC3eKocnDJKW1Bc text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-MwC3eKocnDJKW1Bc .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MwC3eKocnDJKW1Bc .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-MwC3eKocnDJKW1Bc .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-MwC3eKocnDJKW1Bc .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-MwC3eKocnDJKW1Bc #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-MwC3eKocnDJKW1Bc .sequenceNumber{fill:white;}#mermaid-svg-MwC3eKocnDJKW1Bc #sequencenumber{fill:#333;}#mermaid-svg-MwC3eKocnDJKW1Bc #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-MwC3eKocnDJKW1Bc .messageText{fill:#333;stroke:none;}#mermaid-svg-MwC3eKocnDJKW1Bc .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MwC3eKocnDJKW1Bc .labelText,#mermaid-svg-MwC3eKocnDJKW1Bc .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-MwC3eKocnDJKW1Bc .loopText,#mermaid-svg-MwC3eKocnDJKW1Bc .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-MwC3eKocnDJKW1Bc .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MwC3eKocnDJKW1Bc .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-MwC3eKocnDJKW1Bc .noteText,#mermaid-svg-MwC3eKocnDJKW1Bc .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-MwC3eKocnDJKW1Bc .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MwC3eKocnDJKW1Bc .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MwC3eKocnDJKW1Bc .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MwC3eKocnDJKW1Bc .actorPopupMenu{position:absolute;}#mermaid-svg-MwC3eKocnDJKW1Bc .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-MwC3eKocnDJKW1Bc .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MwC3eKocnDJKW1Bc .actor-man circle,#mermaid-svg-MwC3eKocnDJKW1Bc line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-MwC3eKocnDJKW1Bc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ResetBits,输出0 灌电流,点亮 Delay_ms(500) SetBits,输出1 电流停止,熄灭 Delay_ms(500)

亮 500 ms、灭 500 ms,一轮约 1 s,所以完整闪烁频率约为 1 Hz

9.3 有注释版为什么连续写了三种方法

3-1 LED闪烁/User/main.c 第 24~42 行依次执行:

  1. GPIO_ResetBits/SetBits
  2. GPIO_WriteBit + Bit_RESET/Bit_SET
  3. GPIO_WriteBit + (BitAction)0/1

它们是为了教学对照,不是一个闪烁程序必须做三遍。实际项目保留一种风格即可。由于三组代码依次重复相同的亮灭动作,当前有注释版在每次外层循环中会连续完成三次 500 ms 亮、500 ms 灭。

9.4 上电初始状态的工程补充

GPIO 复位后 ODR 初始通常为 0。PA0 一旦被切换为推挽输出,低电平有效 LED 可能立即点亮。如果要求上电默认熄灭,可在配置输出模式前先把输出锁存位置 1:

c 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_SetBits(GPIOA, GPIO_Pin_0);  // 先准备高电平
GPIO_Init(GPIOA, &GPIO_InitStructure);

这是工程安全状态补充,不是本段课堂的必写步骤。

课堂位置:录音 05:44--14:46;师兄笔记 PDF 第 20--21 页。


10. Delay 模块:为什么加入 System 文件夹

课程提供:

text 复制代码
System/
├─ Delay.c
└─ Delay.h

加入工程时要完成三件事:

  1. 磁盘工程目录中建立 System 并复制文件;
  2. Keil 中建立同名 Group,把 Delay.c 加入工程;
  3. C/C++ Include Paths 加入 System,在 main.c#include "Delay.h"

10.1 延时函数提供什么接口

c 复制代码
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);

本节只使用:

c 复制代码
Delay_ms(500);

10.2 内部怎样延时

Delay_us() 使用 Cortex-M3 内核的 SysTick 定时器:

  1. 按 72 MHz 主频计算重装值;
  2. 清空当前计数;
  3. 启动 SysTick;
  4. 等待计数到 0;
  5. 关闭计数器。

Delay_ms() 则循环调用 Delay_us(1000)

!NOTE

该模块中的系数 72 假设系统时钟为 72 MHz。如果后来修改主频,延时会产生比例误差,需要同步调整或改成基于 SystemCoreClock 的实现。老师在本节要求先会调用,不要求立即研究 SysTick 细节。
课堂位置:录音 11:20--13:32;源码 3-1 LED闪烁/System/Delay.c


11. 推挽和开漏的实物验证

老师通过改变 LED 接法和 GPIO 模式验证上一节理论。

11.1 推挽输出

当 PA0 配置为:

c 复制代码
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  • 低电平时,N-MOS 主动下拉,可以灌电流;
  • 高电平时,P-MOS 主动上拉,可以提供电流。

所以把 LED 改成高电平点亮接法后,仍能正常闪烁。

11.2 开漏输出

改为:

c 复制代码
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  • 低电平仍由 N-MOS 主动拉低,具有灌电流能力;
  • 写高电平时只是关闭 N-MOS,引脚处于高阻态,不会主动输出高电平。

因此:

LED 接法 开漏输出结果
高电平点亮,PA0 需要向外提供电流 没有外部上拉时不亮
低电平点亮,PA0 只需向地灌电流 可以点亮

结论不是"开漏不能输出高电平逻辑",而是"开漏自身不能主动拉高";加外部上拉后,引脚释放时可由上拉电阻形成高电平。

课堂位置:录音 14:46--15:32


12. 程序二:PA0~PA7 LED 流水灯

图 2 有注释版源码中的 3-2 LED流水灯 接线图。8 个 LED 均为低电平点亮。

12.1 硬件变化

  • 8 个 LED 正极接 3.3 V 电源轨;
  • 8 个 LED 负极依次接 PA0~PA7;
  • 每个 LED 都是对应 GPIO 输出低电平时点亮。

12.2 源码初始化

c 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

课堂源码使用 GPIO_Pin_All,把 GPIOA 的 16 个引脚全部配置为推挽输出,写法简单,但真正连接的只有 PA0~PA7。

12.3 更稳妥的引脚选择

真实项目应尽量只配置需要的引脚:

c 复制代码
GPIO_InitStructure.GPIO_Pin =
      GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3
    | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;

这样不会无意改变 PA8~PA15。尤其 PA13、PA14 默认用于 SWD 调试,PA15 还与 JTAG 调试功能复用,整端口配置会让工程意图变得不清晰。

12.4 为什么要按位取反

源码第一步:

c 复制代码
GPIO_Write(GPIOA, ~0x0001);

0x0001 的低 16 位只有 bit0 为 1:

text 复制代码
0000 0000 0000 0001

按位取反并转换为 16 位后:

text 复制代码
1111 1111 1111 1110

结果是 PA0 输出 0,PA1~PA15 输出 1。由于 LED 低电平点亮,所以只有 PA0 对应 LED 亮。

完整序列:

写入表达式 低电平所在位 点亮 LED
~0x0001 bit0 PA0
~0x0002 bit1 PA1
~0x0004 bit2 PA2
~0x0008 bit3 PA3
~0x0010 bit4 PA4
~0x0020 bit5 PA5
~0x0040 bit6 PA6
~0x0080 bit7 PA7

每次保持 100 ms,就形成从 PA0 向 PA7 移动的亮点。

12.5 源码的完整控制部分

c 复制代码
while (1)
{
    GPIO_Write(GPIOA, ~0x0001); Delay_ms(100);
    GPIO_Write(GPIOA, ~0x0002); Delay_ms(100);
    GPIO_Write(GPIOA, ~0x0004); Delay_ms(100);
    GPIO_Write(GPIOA, ~0x0008); Delay_ms(100);
    GPIO_Write(GPIOA, ~0x0010); Delay_ms(100);
    GPIO_Write(GPIOA, ~0x0020); Delay_ms(100);
    GPIO_Write(GPIOA, ~0x0040); Delay_ms(100);
    GPIO_Write(GPIOA, ~0x0080); Delay_ms(100);
}

12.6 师兄笔记中的循环写法

上面 8 组代码遵循同一规律,可以改为:

c 复制代码
for (uint8_t i = 0; i < 8; i++)
{
    uint16_t pattern = (uint16_t)~(1U << i);
    GPIO_Write(GPIOA, pattern);
    Delay_ms(100);
}

1U << i 每次把唯一的 1 左移一位,再取反得到唯一的 0。这属于师兄笔记中的等价优化,不是老师本段录音逐行写出的版本。

12.7 GPIO_Write 的风险

GPIO_Write(GPIOA, value) 会覆盖整个 ODR。如果 GPIOA 的其他引脚还控制别的器件,这种写法可能破坏它们的状态。

因此:

  • 教学流水灯:整端口写入直观,可以使用;
  • 多功能项目:优先使用 BSRR/BRRSetBits/ResetBits 精确改变目标位;
  • 若仍整端口写入,应先规划端口资源和保留位。

课堂位置:录音 15:32--19:31;师兄笔记 PDF 第 21 页;源码 3-2 LED流水灯/User/main.c


13. 程序三:PB12 控制有源蜂鸣器

图 3 有注释版源码中的 3-3 蜂鸣器 接线图。

13.1 接线关系与有效电平

蜂鸣器模块引脚 连接
VCC 3.3 V
GND GND
I/O PB12

该模块内部带驱动电路,为低电平有效:

text 复制代码
PB12 = 0 → 蜂鸣器响
PB12 = 1 → 蜂鸣器停

13.2 从 PA0 程序迁移到 PB12

只需要替换三类信息:

项目 LED 闪烁 蜂鸣器
时钟 RCC_APB2Periph_GPIOA RCC_APB2Periph_GPIOB
初始化端口 GPIOA GPIOB
引脚 GPIO_Pin_0 GPIO_Pin_12

模式仍为推挽输出,速度仍按课堂设置为 50 MHz。

13.3 源码

c 复制代码
#include "stm32f10x.h"
#include "Delay.h"

int main(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    while (1)
    {
        GPIO_ResetBits(GPIOB, GPIO_Pin_12);  // 响
        Delay_ms(100);
        GPIO_SetBits(GPIOB, GPIO_Pin_12);    // 停
        Delay_ms(100);
        GPIO_ResetBits(GPIOB, GPIO_Pin_12);  // 再响
        Delay_ms(100);
        GPIO_SetBits(GPIOB, GPIO_Pin_12);    // 长停顿
        Delay_ms(700);
    }
}

13.4 声音节奏怎样形成

#mermaid-svg-mYDO3a18C1conphz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mYDO3a18C1conphz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mYDO3a18C1conphz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mYDO3a18C1conphz .error-icon{fill:#552222;}#mermaid-svg-mYDO3a18C1conphz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mYDO3a18C1conphz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mYDO3a18C1conphz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mYDO3a18C1conphz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mYDO3a18C1conphz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mYDO3a18C1conphz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mYDO3a18C1conphz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mYDO3a18C1conphz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mYDO3a18C1conphz .marker.cross{stroke:#333333;}#mermaid-svg-mYDO3a18C1conphz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mYDO3a18C1conphz p{margin:0;}#mermaid-svg-mYDO3a18C1conphz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mYDO3a18C1conphz .cluster-label text{fill:#333;}#mermaid-svg-mYDO3a18C1conphz .cluster-label span{color:#333;}#mermaid-svg-mYDO3a18C1conphz .cluster-label span p{background-color:transparent;}#mermaid-svg-mYDO3a18C1conphz .label text,#mermaid-svg-mYDO3a18C1conphz span{fill:#333;color:#333;}#mermaid-svg-mYDO3a18C1conphz .node rect,#mermaid-svg-mYDO3a18C1conphz .node circle,#mermaid-svg-mYDO3a18C1conphz .node ellipse,#mermaid-svg-mYDO3a18C1conphz .node polygon,#mermaid-svg-mYDO3a18C1conphz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mYDO3a18C1conphz .rough-node .label text,#mermaid-svg-mYDO3a18C1conphz .node .label text,#mermaid-svg-mYDO3a18C1conphz .image-shape .label,#mermaid-svg-mYDO3a18C1conphz .icon-shape .label{text-anchor:middle;}#mermaid-svg-mYDO3a18C1conphz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mYDO3a18C1conphz .rough-node .label,#mermaid-svg-mYDO3a18C1conphz .node .label,#mermaid-svg-mYDO3a18C1conphz .image-shape .label,#mermaid-svg-mYDO3a18C1conphz .icon-shape .label{text-align:center;}#mermaid-svg-mYDO3a18C1conphz .node.clickable{cursor:pointer;}#mermaid-svg-mYDO3a18C1conphz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mYDO3a18C1conphz .arrowheadPath{fill:#333333;}#mermaid-svg-mYDO3a18C1conphz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mYDO3a18C1conphz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mYDO3a18C1conphz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mYDO3a18C1conphz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mYDO3a18C1conphz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mYDO3a18C1conphz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mYDO3a18C1conphz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mYDO3a18C1conphz .cluster text{fill:#333;}#mermaid-svg-mYDO3a18C1conphz .cluster span{color:#333;}#mermaid-svg-mYDO3a18C1conphz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mYDO3a18C1conphz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mYDO3a18C1conphz rect.text{fill:none;stroke-width:0;}#mermaid-svg-mYDO3a18C1conphz .icon-shape,#mermaid-svg-mYDO3a18C1conphz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mYDO3a18C1conphz .icon-shape p,#mermaid-svg-mYDO3a18C1conphz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mYDO3a18C1conphz .icon-shape .label rect,#mermaid-svg-mYDO3a18C1conphz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mYDO3a18C1conphz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mYDO3a18C1conphz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mYDO3a18C1conphz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 0~100 ms


100~200 ms


200~300 ms


300~1000 ms

一轮总时长:

text 复制代码
100 + 100 + 100 + 700 = 1000 ms

改变延时时间就能改变节奏,但有源蜂鸣器的音调主要由模块内部振荡器决定;本节改变的是"响多久、停多久",不是音调频率。

13.5 录音中调试引脚说明的校正

录音在蜂鸣器前出现了"几个引脚默认是调试端口"的说明,转写位置和名称较混乱。STM32F103 的 SWJ 调试相关引脚为:

  • PA13:JTMS/SWDIO;
  • PA14:JTCK/SWCLK;
  • PA15:JTDI;
  • PB3:JTDO/TRACESWO;
  • PB4:JNTRST。

PB12 不是默认 JTAG/SWD 调试引脚,本实验不需要为 PB12 关闭调试功能。若以后要把 PA15、PB3、PB4 当普通 GPIO,需要配置 AFIO 的 SWJ_CFG,并保留需要的 SWD 下载通道。

课堂位置:录音 19:31--20:54;师兄笔记 PDF 第 22 页;参考手册 PDF 第 117--118 页;数据手册 PDF 第 18、20 页。


14. 从库函数一路追到寄存器

老师强调可以在 Keil 中输入函数前缀、查看补全列表,再使用"Go To Definition"查看声明、注释和实现。以点灯程序为例:
#mermaid-svg-VzT1wSGC8e5PFQ5i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VzT1wSGC8e5PFQ5i .error-icon{fill:#552222;}#mermaid-svg-VzT1wSGC8e5PFQ5i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VzT1wSGC8e5PFQ5i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .marker.cross{stroke:#333333;}#mermaid-svg-VzT1wSGC8e5PFQ5i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VzT1wSGC8e5PFQ5i p{margin:0;}#mermaid-svg-VzT1wSGC8e5PFQ5i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .cluster-label text{fill:#333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .cluster-label span{color:#333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .cluster-label span p{background-color:transparent;}#mermaid-svg-VzT1wSGC8e5PFQ5i .label text,#mermaid-svg-VzT1wSGC8e5PFQ5i span{fill:#333;color:#333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .node rect,#mermaid-svg-VzT1wSGC8e5PFQ5i .node circle,#mermaid-svg-VzT1wSGC8e5PFQ5i .node ellipse,#mermaid-svg-VzT1wSGC8e5PFQ5i .node polygon,#mermaid-svg-VzT1wSGC8e5PFQ5i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VzT1wSGC8e5PFQ5i .rough-node .label text,#mermaid-svg-VzT1wSGC8e5PFQ5i .node .label text,#mermaid-svg-VzT1wSGC8e5PFQ5i .image-shape .label,#mermaid-svg-VzT1wSGC8e5PFQ5i .icon-shape .label{text-anchor:middle;}#mermaid-svg-VzT1wSGC8e5PFQ5i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VzT1wSGC8e5PFQ5i .rough-node .label,#mermaid-svg-VzT1wSGC8e5PFQ5i .node .label,#mermaid-svg-VzT1wSGC8e5PFQ5i .image-shape .label,#mermaid-svg-VzT1wSGC8e5PFQ5i .icon-shape .label{text-align:center;}#mermaid-svg-VzT1wSGC8e5PFQ5i .node.clickable{cursor:pointer;}#mermaid-svg-VzT1wSGC8e5PFQ5i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .arrowheadPath{fill:#333333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VzT1wSGC8e5PFQ5i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VzT1wSGC8e5PFQ5i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VzT1wSGC8e5PFQ5i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VzT1wSGC8e5PFQ5i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VzT1wSGC8e5PFQ5i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VzT1wSGC8e5PFQ5i .cluster text{fill:#333;}#mermaid-svg-VzT1wSGC8e5PFQ5i .cluster span{color:#333;}#mermaid-svg-VzT1wSGC8e5PFQ5i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VzT1wSGC8e5PFQ5i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VzT1wSGC8e5PFQ5i rect.text{fill:none;stroke-width:0;}#mermaid-svg-VzT1wSGC8e5PFQ5i .icon-shape,#mermaid-svg-VzT1wSGC8e5PFQ5i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VzT1wSGC8e5PFQ5i .icon-shape p,#mermaid-svg-VzT1wSGC8e5PFQ5i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VzT1wSGC8e5PFQ5i .icon-shape .label rect,#mermaid-svg-VzT1wSGC8e5PFQ5i .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VzT1wSGC8e5PFQ5i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VzT1wSGC8e5PFQ5i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VzT1wSGC8e5PFQ5i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} main.c调用库函数
stm32f10x_gpio.h

看类型、宏和函数声明
stm32f10x_gpio.c

看函数实现
CRL/CRH、BSRR、BRR、ODR寄存器
引脚电平
LED或蜂鸣器现象

14.1 本节常查的文件

文件 查什么
stm32f10x_rcc.h GPIOA/GPIOB 时钟宏、函数声明
stm32f10x_rcc.c RCC_APB2PeriphClockCmd() 怎样写 APB2ENR
stm32f10x_gpio.h 模式枚举、速度枚举、引脚宏、输出函数声明
stm32f10x_gpio.c GPIO_Init() 和输出函数怎样操作寄存器
stm32f10x.h GPIO、RCC 等寄存器结构和外设基地址

14.2 三种资料查法

老师最后给出三条途径:

  1. 在 Keil 中查看头文件声明与函数定义;
  2. 查看标准外设库用户手册或固件包内的 HTML 帮助;
  3. 搜索其他代码作为思路参考。

使用时应再加一条原则:别人的代码只能帮助找到 API,最终参数含义要回到当前版本的头文件、源码和官方手册确认。 课堂提到的中文用户手册对应较旧标准库版本,可能与当前 V3.5.0 有少量差异。

课堂位置:录音 03:21--05:4420:54--22:21


15. 三个程序的共同模板

以后控制继电器、模块使能脚或其他数字负载,可以套用下面的推理模板:
#mermaid-svg-ySPmZuXsVRQtHGDK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ySPmZuXsVRQtHGDK .error-icon{fill:#552222;}#mermaid-svg-ySPmZuXsVRQtHGDK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ySPmZuXsVRQtHGDK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ySPmZuXsVRQtHGDK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ySPmZuXsVRQtHGDK .marker.cross{stroke:#333333;}#mermaid-svg-ySPmZuXsVRQtHGDK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ySPmZuXsVRQtHGDK p{margin:0;}#mermaid-svg-ySPmZuXsVRQtHGDK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ySPmZuXsVRQtHGDK .cluster-label text{fill:#333;}#mermaid-svg-ySPmZuXsVRQtHGDK .cluster-label span{color:#333;}#mermaid-svg-ySPmZuXsVRQtHGDK .cluster-label span p{background-color:transparent;}#mermaid-svg-ySPmZuXsVRQtHGDK .label text,#mermaid-svg-ySPmZuXsVRQtHGDK span{fill:#333;color:#333;}#mermaid-svg-ySPmZuXsVRQtHGDK .node rect,#mermaid-svg-ySPmZuXsVRQtHGDK .node circle,#mermaid-svg-ySPmZuXsVRQtHGDK .node ellipse,#mermaid-svg-ySPmZuXsVRQtHGDK .node polygon,#mermaid-svg-ySPmZuXsVRQtHGDK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ySPmZuXsVRQtHGDK .rough-node .label text,#mermaid-svg-ySPmZuXsVRQtHGDK .node .label text,#mermaid-svg-ySPmZuXsVRQtHGDK .image-shape .label,#mermaid-svg-ySPmZuXsVRQtHGDK .icon-shape .label{text-anchor:middle;}#mermaid-svg-ySPmZuXsVRQtHGDK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ySPmZuXsVRQtHGDK .rough-node .label,#mermaid-svg-ySPmZuXsVRQtHGDK .node .label,#mermaid-svg-ySPmZuXsVRQtHGDK .image-shape .label,#mermaid-svg-ySPmZuXsVRQtHGDK .icon-shape .label{text-align:center;}#mermaid-svg-ySPmZuXsVRQtHGDK .node.clickable{cursor:pointer;}#mermaid-svg-ySPmZuXsVRQtHGDK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ySPmZuXsVRQtHGDK .arrowheadPath{fill:#333333;}#mermaid-svg-ySPmZuXsVRQtHGDK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ySPmZuXsVRQtHGDK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ySPmZuXsVRQtHGDK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySPmZuXsVRQtHGDK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ySPmZuXsVRQtHGDK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySPmZuXsVRQtHGDK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ySPmZuXsVRQtHGDK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ySPmZuXsVRQtHGDK .cluster text{fill:#333;}#mermaid-svg-ySPmZuXsVRQtHGDK .cluster span{color:#333;}#mermaid-svg-ySPmZuXsVRQtHGDK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ySPmZuXsVRQtHGDK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ySPmZuXsVRQtHGDK rect.text{fill:none;stroke-width:0;}#mermaid-svg-ySPmZuXsVRQtHGDK .icon-shape,#mermaid-svg-ySPmZuXsVRQtHGDK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySPmZuXsVRQtHGDK .icon-shape p,#mermaid-svg-ySPmZuXsVRQtHGDK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ySPmZuXsVRQtHGDK .icon-shape .label rect,#mermaid-svg-ySPmZuXsVRQtHGDK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySPmZuXsVRQtHGDK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ySPmZuXsVRQtHGDK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ySPmZuXsVRQtHGDK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 看原理图
确定端口和引脚
确定高有效还是低有效
看负载电流

判断是否需要驱动器
开启对应GPIO时钟
配置模式、引脚和速度
先设置安全初始状态
在主循环中输出和延时
编译、下载、观察现象

代码骨架:

c 复制代码
#include "stm32f10x.h"
#include "Delay.h"

int main(void)
{
    /* 1. 开时钟 */
    RCC_APB2PeriphClockCmd(/* 对应GPIO端口 */, ENABLE);

    /* 2. 配GPIO */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = /* 对应引脚 */;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(/* 对应GPIO端口 */, &GPIO_InitStructure);

    /* 3. 循环控制 */
    while (1)
    {
        /* 输出有效电平 */
        Delay_ms(/* 保持时间 */);
        /* 输出无效电平 */
        Delay_ms(/* 保持时间 */);
    }
}

16. 常见错误与排查顺序

现象 优先检查
编译提示找不到 Delay.h System 是否加入 Include Paths
链接提示找不到 Delay_ms Delay.c 是否真正加入 Keil Group 并参与编译
下载后完全无现象 ST-LINK 接线、共地、目标芯片、Reset and Run
代码能运行但 LED 逻辑相反 看原理图确认高有效/低有效
GPIO 操作无效 是否打开正确端口的 APB2 时钟
配置了 PA0 却接在别的孔 对照最小系统板丝印和面包板内部连通关系
LED 一直不亮 正负极、限流电阻、PA0 接线、输出模式
开漏模式高电平点不亮 LED 开漏高状态为高阻,是否有外部上拉
流水灯顺序不对 PA0~PA7 接线次序、位掩码、低有效取反
流水灯影响其他器件 GPIO_Write 覆盖整个 ODR,检查端口共享
蜂鸣器逻辑反了 模块是否低电平有效、I/O 是否接 PB12
延时明显不准 系统主频是否仍为 72 MHz

排查时从硬件到软件逐层缩小:

text 复制代码
电源与共地 → 物理接线 → 端口时钟 → GPIO初始化 → 输出函数 → 延时与循环

17. 本节转写与表述校正

录音转写 正确内容
RTCRTCAHBY 多数是在说 RCC 及 RCC_AHB/APB 时钟函数
SPIO/GPIOD 混乱 本节外设为 GPIO,初始化函数为 GPIO_Init()
label ENABLE;另一取值为 DISABLE
推广输出 推挽输出,GPIO_Mode_Out_PP
开路输出 开漏输出,GPIO_Mode_Out_OD
GPIOREST... GPIO_ResetBits()
GPIOSSPID... GPIO_SetBits()
GPIORTBT GPIO_WriteBit()
图像处理 GPIO_Write()
位设置/位清除 标准库底层分别使用 BSRR、BRR
颜色函数 延时函数 Delay_ms()
分明器 蜂鸣器
PB12 附近的调试口描述 默认调试 GPIO 是 PA13、PA14、PA15、PB3、PB4,不包括 PB12

18. 复习自测

  1. 为什么使用 GPIO 前必须先调用 RCC 时钟函数?
  2. GPIO_InitTypeDef 的三个成员分别描述什么?
  3. 为什么 PA0 点灯选择 GPIO_Mode_Out_PP
  4. GPIO_Pin_0 为什么等于 0x0001GPIO_Pin_12 为什么等于 0x1000
  5. GPIO_Init(GPIOA, &GPIO_InitStructure) 中的 & 有什么作用?
  6. SetBitsResetBitsWriteBitWrite 分别适合什么场景?
  7. 为什么本实验 ResetBits 反而会让 LED 点亮?
  8. 500 ms 亮、500 ms 灭时,闪烁周期和频率约为多少?
  9. 推挽输出和开漏输出在高电平驱动能力上有什么区别?
  10. GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 为什么能同时选择三个引脚?
  11. 流水灯数据为什么要对 0x0001、0x0002... 取反?
  12. GPIO_Write() 为什么可能影响同一端口的其他器件?
  13. 为什么不建议为了 8 个 LED 直接把 GPIOA 全部引脚都初始化?
  14. 蜂鸣器的两次鸣响和长停顿分别由哪些延时组成?
  15. 如果把系统主频从 72 MHz 改成 36 MHz,当前 Delay 模块会发生什么?
  16. PA15、PB3、PB4 不能直接当普通 GPIO 时,应查参考手册的哪个功能?

19. 参考资料与页码/源码索引

19.1 课堂与工程源码

来源 本笔记采用内容
D:/2026-08-30 14_32 记录 LED点亮_原文.docx 全文,录音 00:02--22:21
3-1 LED闪烁/User/main.c PA0 初始化、三种单个位输出方式、LED 闪烁
3-1 LED闪烁/System/Delay.c/.h SysTick 延时模块
3-2 LED流水灯/User/main.c GPIOA 整端口输出、8 路低有效流水灯
3-3 蜂鸣器/User/main.c PB12 低有效蜂鸣器节奏
1-1 接线图/3-1、3-2、3-3 三个实验的实际接线图

源码根目录:../程序源码/STM32Project-有注释版/

19.2 师兄笔记

PDF 页码 内容
第 20 页 LED 闪烁接线、RCC/GPIO 函数、8 种模式和输出函数
第 21 页 LED 闪烁完整代码、流水灯循环优化写法
第 22 页 蜂鸣器接线、PB12 初始化和声音节奏

文件:../STM32笔记.pdf

19.3 《STM32F10xxx参考手册(中文)》

PDF 页码 内容
第 70--71 页 RCC_APB2ENR,GPIOA/GPIOB 时钟使能
第 106 页 GPIO 位结构和 MODE/CNF 配置
第 113--114 页 GPIOx_CRL/CRH 配置寄存器
第 115--116 页 ODR、BSRR、BRR 输出寄存器
第 117--118 页 JTAG/SWD 调试引脚和 SWJ_CFG

文件:../参考文档/STM32F10xxx参考手册(中文).pdf

19.4 《STM32F103x8/B数据手册(中文)》

PDF 页码 内容
第 17 页 PA0 引脚定义与复用功能
第 18 页 PB12 引脚定义与复用功能
第 20 页 PB3/PB4 调试引脚定义及引脚表注释
第 40--41 页 GPIO 输入、驱动电流、输出电压和输出速度条件

文件:../参考文档/STM32F103x8B数据手册(中文).pdf


20. 一句话总结

GPIO 输出程序的核心不是函数名,而是因果链:接线图决定有效电平,RCC 让端口工作,GPIO_Init 决定引脚怎样驱动,输出函数改变寄存器位,延时把电平序列变成可见的灯光或可听的节奏。

相关推荐
2601_9620739724 分钟前
【MySQL】全面学习数据库查询技巧:查询指令深度学习指南
数据库·学习·mysql
存在morning33 分钟前
【Paimon 学习笔记 二】存储架构:快照、manifest、LSM 树、bucket
笔记·学习·架构
双眼鈹1 小时前
周报8.31
笔记·学习
打团从来人不齐1 小时前
Linux服务器搭建笔记-008:台式机共享WIFI
linux·服务器·笔记
疯狂打码的少年2 小时前
【数据库技术】SQL概述与数据定义(DDL:CREATE/DROP/ALTER)
数据库·笔记·sql·oracle
硬件yun2 小时前
学功能安全
学习·安全·汽车
火眼金睛炼单词2 小时前
图像记单词:视觉化学习能否破解英语记忆难题
学习
一条破秋裤2 小时前
STM32 学习笔记:GPIO 输出原理与硬件设计
笔记·stm32·学习
andrsted2 小时前
作业发布、课堂检测和错题讲评,用一个小程序完成
学习·微信小程序·小程序·学习方法