嵌入式技术应用设计实训 | GPIO 输出:跑马灯与蜂鸣器
本课程开源地址(Gitee) :https://gitee.com/fujianxinxi/qianrushishixundianzi.git
课件、示例代码与验收脚本都在该仓库,可直接
git clone或下载 ZIP 使用。开发板资料A盘(百度网盘) :https://pan.baidu.com/s/1_EXrjRUewwmGMgcy38zEZg(提取码:
zbri)正点原子官方资料下载 :http://www.openedv.com/docs/boards/stm32/zdyz_stm32f103_nano.html
模块一 基础外设开发 · 2 学时 · 环境:Keil MDK5 + VS Code + 正点原子 STM32F103 Nano V1.3
一句话概述 :从 GPIO 的"开时钟 → 配推挽 → 写电平"三步入手,把 PC0~PC7(板载 DS0~DS7)配成推挽输出点亮 8 个 LED;同一份 main.c 里用 DEMO_MODE 宏切换五种现象------全亮、间隔亮、跑马灯、全亮全灭交替四种灯效,外加一种只响蜂鸣器,蜂鸣器只在第 5 种现象里响,其余现象全程静音;节奏全靠软件延时排出来,不涉及定时器与中断。
本节目标
- 能说出点亮一个 LED 要按"开时钟 → 配推挽 → 写电平"三步走,并说清顺序反了为什么既不报错也不亮
- 会改
main.c里的DEMO_MODE宏(改成 1、2、3、4),把板载 8 个 LED 跑出全亮、间隔亮、跑马灯三种现象 - 会用同一套写法驱动 PB8 上的蜂鸣器(把
DEMO_MODE改成 5 单独试),并说清"有源蜂鸣器给对电平就响,要调音才用 PWM"
一、原理说明
GPIO 输出要按固定顺序做三步:先给引脚所在的端口开时钟,再把引脚配成推挽输出,最后才是写电平------顺序不能反,时钟没开的时候写寄存器等于没写,可编译器一声不响,这是新手最容易踩的坑。板载 8 个 LED(DS0~DS7)的阳极已经通过限流电阻接在 3.3V 上,阴极分别连到 PC0~PC7,所以 IO 输出低电平灯才亮、输出高电平灯灭;蜂鸣器接在 PB8 上,也是"写个电平就能响"的器件,只不过响不响还取决于板子上的接法极性。"跑马灯"说到底就是"点亮一个 → 等一会儿 → 灭掉它、点亮下一个"的循环,等待用软件延时 delay_ms() 排出来,本课不碰定时器和中断。把这五种现象写进同一个 main.c、用一个宏切换,是为了让你改一个数字就能对比不同写法,而不是每换一种现象就要重敲一遍代码。
二、讲解内容
2.1 知识讲解
先认清三个词:端口、引脚、位 。STM32 的 IO 是按端口分组的,GPIOA、GPIOB、GPIOC 各是一个端口,每个端口管 16 个引脚,引脚名叫 PA0`PA15`、`PB0`PB15、PC0~PC15。在 HAL 库里,"位"就是一个 16 位整数里的一个 bit:GPIO_PIN_0 等于 0x0001,GPIO_PIN_1 等于 0x0002,......GPIO_PIN_7 等于 0x0080。所以想一次操作 8 个灯,把它们的位"或"起来就行------0x00FF 就是 PC0~PC7 全部。
本课程用的引脚(正点原子 STM32F103 Nano V1.3 板载,不用额外接线):
| 板载元件 | 开发板引脚 | 点亮/工作电平 | 说明 |
|---|---|---|---|
| 红色 LED DS0 | PC0 | 低电平 | DS0~DS7 阳极接 3.3V,IO 拉低才亮 |
| 红色 LED DS1 | PC1 | 低电平 | 同上 |
| 红色 LED DS2 | PC2 | 低电平 | 同上 |
| 红色 LED DS3 | PC3 | 低电平 | 同上 |
| 红色 LED DS4 | PC4 | 低电平 | 同上 |
| 红色 LED DS5 | PC5 | 低电平 | 同上 |
| 红色 LED DS6 | PC6 | 低电平 | 同上 |
| 红色 LED DS7 | PC7 | 低电平 | 同上 |
| 蜂鸣器 BEEP | PB8 | 低电平响(V1.3 实测) | 有源蜂鸣器,给对电平就响;极性由代码里的 BEEP_ACTIVE_HIGH 开关决定 |
三步走,一步都不能少:
| 步骤 | 干什么 | 代码 | 不做会怎样 |
|---|---|---|---|
| ① 开时钟 | 给引脚所在端口使能时钟 | __HAL_RCC_GPIOC_CLK_ENABLE(); |
写寄存器没有任何效果,且不报错(灯就是不亮) |
| ② 配模式 | 引脚方向、输出类型、上下拉、速度 | GPIO_InitTypeDef + HAL_GPIO_Init() |
引脚还是复位后的浮空输入,输出不了电平 |
| ③ 写电平 | 把引脚拉高或拉低 | HAL_GPIO_WritePin() |
配置好了却不动,灯自然也不亮 |
推挽输出(GPIO_MODE_OUTPUT_PP)到底输出了什么 :推挽的意思是引脚内部上下两个管子轮流导通,芯片能主动把引脚拉到 3.3V(高)或 0V(低),带得动 LED 这种小负载。另一种常见模式是开漏输出,它只能主动拉低,要输出高电平必须外面接上拉电阻------本课点灯、驱蜂鸣器都用推挽,最省事。
为什么要先全灭、再点亮当前这一个 :如果只写"点亮下一个",却不把上一个灭掉,灯的位会一直累加(第一次一个灯、第二次两个灯......),看起来就不是跑马灯而是一串"点亮进度条"。所以每一轮循环里第一句永远是 LED_ALL_OFF(),或者像 LED_ON(x) 之前先清掉旧位------这是本课最容易写错的地方,写错时现象很好认:灯只增不减。
延时是怎么来的(本课与定时器的分界线) :delay_ms()、delay_us() 在 SYSTEM/delay/ 里,是拿 Cortex-M3 内核自带的 SysTick 做忙等 延时:CPU 原地数够时间再往下走,简单、直观、不占用任何外设定时器。所以本课只靠它就能排出"每 200ms 移一位"的节奏;等到课时04 再用真正的定时器中断来做"到点自动干活、CPU 不用死等",你会立刻体会到两者的差别。
条件编译:一份工程,五种现象 。#define DEMO_MODE 3 写在文件开头,下面用 #if / #elif / #else 分成五段:编译时只保留 DEMO_MODE 对应的那一段,其余几段被预处理器直接删掉,既不占程序空间,也不会互相干扰。所以"改现象"在本课就等于"改一个数字 + 重新编译下载",你甚至可以在同一份代码里来回切换、对比五种现象。
DEMO_MODE |
现象 | 主要用到的写法 | 蜂鸣器 |
|---|---|---|---|
| 1 | 8 个灯一直全亮 | 整端口写电平 0x00FF |
静音 |
| 2 | 奇数灯、偶数灯交替闪(0.5 秒一换) | 整端口写电平 0x00AA / 0x0055 |
静音 |
| 3(默认) | 上电自检(依次点亮 + 全亮/全灭)→ 跑马灯循环 | 单个引脚 1U << i + delay_ms() |
静音 |
| 4 | 8 个灯一起亮 0.5 秒、一起灭 0.5 秒 | 整端口 LED_ALL_ON() / LED_ALL_OFF() + 延时 |
静音 |
| 5 | 灯全灭,蜂鸣器规律"嘀---嘀---"响 | 写 PB8 电平 + 延时 | 唯一会响 |
蜂鸣器为什么要留一个极性开关 :板载蜂鸣器是有源蜂鸣器(内部自带振荡电路),只要给它对的那个电平就一直响,不需要 PWM、也不需要频率;但"高电平响"还是"低电平响"取决于板子上的驱动接法------本板(Nano V1.3)实测是低电平响 ,所以代码里写的是 #define BEEP_ACTIVE_HIGH 0,BEEP_OFF() 就是输出高电平。极性搞反时现象特别明显:本该静音的模式 1~4 里,蜂鸣器会一直叫------因为 BEEP_Init() 里当作"不响"的那个电平,恰恰是它的"响"电平;这时把开关在 0 / 1 之间对调、重新编译即可。这也正是本课把它做成开关而不是写死的原因。另外,本课只有现象 5 会真正让蜂鸣器响 :BEEP_Init() 照常初始化(引脚就绪、并停在"不响"电平上),其余四种现象从头到尾不碰 PB8,所以课堂上连着演示灯效不会吵,也不用把那几行代码删来删去。
2.2 代码示例
本课只改一个文件 :课时文档/代码/课时01-GPIO输出跑马灯与蜂鸣器/USER/main.c。同一个工程里的 HARDWARE/LED/、SYSTEM/、HALLIB/、CORE/ 都是现成的(厂家例程里已经写好),照用即可:
text
课时文档/代码/课时01-GPIO输出跑马灯与蜂鸣器/
├── USER/LED.uvprojx ← Keil 打开这个工程(本课改的 main.c 也在这里)
├── USER/main.c ← 本课唯一的自写文件(五种现象都在里面)
├── HARDWARE/LED/ ← LED 驱动:led.c / led.h(PC0~PC7 配成推挽输出,不用改)
├── SYSTEM/ ← sys、delay(delay_ms 就在这里)、usart
├── HALLIB/ CORE/ ← HAL 库与启动文件(不用改)
└── OBJ/ ← 编译输出(已加入 .gitignore,不进仓库)
下面是本课 USER/main.c 的完整代码 (一个文件五种现象,靠 DEMO_MODE 切换):
c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
/************************************************
《嵌入式技术应用设计实训》课时01 GPIO 输出:跑马灯与蜂鸣器
本文件基于正点原子 ALIENTEK NANO STM32F103 开发板例程(跑马灯实验-HAL库版本)改写,
原例程版权归广州市星翼电子科技有限公司所有。
硬件:正点原子 STM32F103 Nano V1.3
DS0~DS7(板载红色 LED)→ PC0~PC7,低电平点亮
蜂鸣器(板载) → PB8
本课只改这一个文件:下面 DEMO_MODE 换一个数字、重新编译下载,现象就换一种,
不涉及定时器、中断,等待节奏全部用软件延时 delay_ms() 排出来。
注意:五种现象里只有现象 5 会驱动蜂鸣器,其余现象全程静音(上课连着演示不吵)。
************************************************/
/* ==================== 演示现象选择(改这一行即可) ====================
1 = 全亮 :DS0~DS7 一直全亮
2 = 间隔亮 :DS0/DS2/DS4/DS6 与 DS1/DS3/DS5/DS7 交替闪,0.5 秒换一次
3 = 跑马灯 :上电先自检(DS0→DS7 依次点亮一遍 + 全亮/全灭),随后 DS0→DS7 循环流水
4 = 全闪 :8 个灯一起亮 0.5 秒、一起灭 0.5 秒
5 = 只蜂鸣 :LED 全灭,蜂鸣器"嘀---嘀---"规律响(本课只有这一种现象会响)
===================================================================== */
#define DEMO_MODE 3
/* ============ 蜂鸣器极性开关(只有现象 5 会用到,按实测结果选一个) ============
1 = 高电平响(PB8 输出高电平时蜂鸣器响)
0 = 低电平响(PB8 输出低电平时蜂鸣器响)
本板(正点原子 Nano V1.3)实测是"低电平响",所以这里填 0;换成高电平响的板子就改成 1。
有源蜂鸣器只要给对电平就响,不需要 PWM(要"调音"才用 PWM,那是课时05)
=========================================================================== */
#define BEEP_ACTIVE_HIGH 0
/* ---------- 板载 LED:低电平点亮,PC0~PC7 分别对应 DS0~DS7 ---------- */
#define LED_ALL_OFF() HAL_GPIO_WritePin(GPIOC, (uint16_t)0x00FF, GPIO_PIN_SET) /* 8 个灯全灭 */
#define LED_ALL_ON() HAL_GPIO_WritePin(GPIOC, (uint16_t)0x00FF, GPIO_PIN_RESET) /* 8 个灯全亮 */
#define LED_ON(x) HAL_GPIO_WritePin(GPIOC, (uint16_t)(1U << (x)), GPIO_PIN_RESET) /* 点亮第 x 个,x=0~7 */
#define LED_OFF(x) HAL_GPIO_WritePin(GPIOC, (uint16_t)(1U << (x)), GPIO_PIN_SET) /* 熄灭第 x 个 */
/* ---------- 板载蜂鸣器:BEEP_ON 响、BEEP_OFF 停(极性由上面开关决定) ---------- */
#if (BEEP_ACTIVE_HIGH == 1)
#define BEEP_ON() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET)
#define BEEP_OFF() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET)
#else
#define BEEP_ON() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET)
#define BEEP_OFF() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET)
#endif
/* 蜂鸣器初始化:PB8 推挽输出 ------ 和 LED_Init() 是同一套三步:开时钟 → 配模式 → 写电平 */
static void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); /* 1. 开 GPIOB 时钟:不开时钟,后面全白写 */
BEEP_OFF(); /* 先写成"不响"的电平,避免上电瞬间乱响 */
GPIO_Initure.Pin = GPIO_PIN_8; /* 2. 配模式:PB8 */
GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
GPIO_Initure.Pull = GPIO_PULLUP; /* 上拉 */
GPIO_Initure.Speed = GPIO_SPEED_HIGH; /* 高速 */
HAL_GPIO_Init(GPIOB, &GPIO_Initure);
}
int main(void)
{
#if (DEMO_MODE == 3)
u8 i; /* 只有"跑马灯"这一种现象需要循环变量 */
#endif
HAL_Init(); /* 初始化 HAL 库:顺带配好 1ms 延时基准 */
Stm32_Clock_Init(RCC_PLL_MUL9); /* 设置时钟:主频 72MHz */
delay_init(72); /* 初始化延时函数 */
LED_Init(); /* 初始化 LED:DS0~DS7(PC0~PC7)推挽输出,上电默认全灭 */
BEEP_Init(); /* 初始化蜂鸣器:PB8 推挽输出,上电默认不响(就绪但不出声) */
#if (DEMO_MODE == 1)
/* ---------- 现象 1:全亮(一句话现象:8 个灯一直亮着) ---------- */
LED_ALL_ON();
while (1)
{
/* 电平已经写下去了,灯就一直亮着;这里什么都不用做 */
}
#elif (DEMO_MODE == 2)
/* ---------- 现象 2:间隔亮(奇数灯、偶数灯交替闪) ---------- */
while (1)
{
HAL_GPIO_WritePin(GPIOC, (uint16_t)0x00AA, GPIO_PIN_RESET); /* DS1、DS3、DS5、DS7 亮 */
HAL_GPIO_WritePin(GPIOC, (uint16_t)0x0055, GPIO_PIN_SET); /* DS0、DS2、DS4、DS6 灭 */
delay_ms(500);
HAL_GPIO_WritePin(GPIOC, (uint16_t)0x0055, GPIO_PIN_RESET); /* DS0、DS2、DS4、DS6 亮 */
HAL_GPIO_WritePin(GPIOC, (uint16_t)0x00AA, GPIO_PIN_SET); /* DS1、DS3、DS5、DS7 灭 */
delay_ms(500);
}
#elif (DEMO_MODE == 3)
/* ---------- 现象 3:上电自检 + 跑马灯(静音,默认) ---------- */
for (i = 0; i < 8; i++) /* 上电自检:DS0→DS7 依次各亮 0.2 秒,走一遍共 1.6 秒 */
{
LED_ALL_OFF(); /* 先全灭,再点亮当前位:否则会越亮越多(新手常见坑) */
LED_ON(i);
delay_ms(200);
}
LED_ALL_ON(); /* 8 个灯全亮 0.3 秒:一眼看清 8 路 IO 是不是都通 */
delay_ms(300);
LED_ALL_OFF(); /* 再全灭 0.3 秒 */
delay_ms(300);
while (1)
{
for (i = 0; i < 8; i++) /* 跑马灯:每次只亮一个,0.2 秒移一位,一圈 1.6 秒 */
{
LED_ALL_OFF();
LED_ON(i);
delay_ms(200);
}
}
#elif (DEMO_MODE == 4)
/* ---------- 现象 4:全闪(8 个灯一起亮、一起灭) ---------- */
while (1)
{
LED_ALL_ON(); /* 整端口一次写:8 个灯全亮 0.5 秒 */
delay_ms(500);
LED_ALL_OFF(); /* 再整端口一次写:8 个灯全灭 0.5 秒 */
delay_ms(500);
}
#elif (DEMO_MODE == 5)
/* ---------- 现象 5:只试蜂鸣器(本课唯一让蜂鸣器响的现象,LED 全灭) ---------- */
LED_ALL_OFF(); /* 先把灯全灭,听声音时不受灯干扰 */
while (1)
{
BEEP_ON(); /* 响 0.2 秒 */
delay_ms(200);
BEEP_OFF(); /* 停 0.8 秒 */
delay_ms(800);
}
#else
#error "DEMO_MODE 只能填 1~5,请修改文件开头的 DEMO_MODE 宏"
#endif
}
代码里的关键宏与函数:
| 宏 / 函数 | 作用 | 取值与说明 |
|---|---|---|
DEMO_MODE |
选择本课演示哪种现象 | 1 全亮 / 2 间隔亮 / 3 跑马灯(默认)/ 4 全闪 / 5 只蜂鸣器(唯一会让蜂鸣器响的模式) |
BEEP_ACTIVE_HIGH |
蜂鸣器响对应的电平 | 0 低电平响(V1.3 实测,本课默认 )/ 1 高电平响;只有现象 5 用到 |
LED_ALL_ON() / LED_ALL_OFF() |
8 个灯全亮 / 全灭 | 一次写 0x00FF,整个端口一把写完 |
LED_ON(x) / LED_OFF(x) |
点亮 / 熄灭第 x 个灯 | x 取 0~7(对应 DS0~DS7),内部用 1U << x 算出位 |
BEEP_ON() / BEEP_OFF() |
蜂鸣器响 / 停 | 就是写 PB8 的电平,响多久由后面的 delay_ms() 决定 |
BEEP_Init() |
蜂鸣器初始化 | 与 LED_Init() 同一套三步:开时钟 → 配模式 → 写电平 |
delay_ms(n) |
延时 n 毫秒 | SYSTEM/delay 里的 SysTick 忙等,不是外设定时器 |
配模式那一步的参数(GPIO_InitTypeDef):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
Pin |
uint32_t |
是 | 选引脚,多个引脚用 ` |
Mode |
uint32_t |
是 | 输出用 GPIO_MODE_OUTPUT_PP(推挽);开漏是 GPIO_MODE_OUTPUT_OD |
Pull |
uint32_t |
是 | 上拉 GPIO_PULLUP、下拉 GPIO_PULLDOWN、浮空 GPIO_NOPULL |
Speed |
uint32_t |
是 | 输出速度:GPIO_SPEED_FREQ_LOW/MEDIUM/HIGH 或简写 GPIO_SPEED_HIGH |
怎么换现象(本课的"改一行") :把文件开头的 #define DEMO_MODE 3 改成 1、2、4、5,保存 → Build(F7)→ Download(F8)→ 按复位键看新现象(只有 5 会听见蜂鸣器,1~4 全程静音)。注意必须重新编译下载,只改代码不下载,板子上跑的还是上一个程序------这是新手最常见的一次"现象不对"。
三、实验操作与现象
3.1 接线与操作步骤
本课不用接任何线:LED 和蜂鸣器都在板子上,已经焊好连到引脚了,你只要插上 USB 供电、接好 ST-Link 就能做。
| 板载元件 | 元件引脚 | 开发板引脚 | 说明(为什么这样接) |
|---|---|---|---|
| 红色 LED DS0~DS7 | 阴极(负极) | PC0~PC7 | 阳极经限流电阻接 3.3V,所以 IO 输出低电平才亮;用 0Ω 电阻/排针连到 PC 口 |
| 蜂鸣器 BEEP | 信号脚 | PB8 | 有源蜂鸣器、低电平响;信号脚接 PB8 |
操作步骤(照着做就能跑):
text
1. 上电接线:USB 线接板子供电口(课时00 认过的那个口)→ ST-Link 接 SWD 三根线 → ST-Link 插电脑
2. 打开工程:Keil 打开 课时文档/代码/课时01-GPIO输出跑马灯与蜂鸣器/USER/LED.uvprojx
3. 选现象 :看 main.c 开头的 #define DEMO_MODE 3(默认跑马灯);想先看静态的改成 1,想单独听蜂鸣器改成 5
4. 编译 :Build(F7)→ 确认 0 Error(s), 0 Warning(s)
5. 下载 :Download(F8)→ 确认 Programming Done. Verify OK.
6. 复位 :按板子 RST 键(下载时勾了 Reset and Run 的会自动跑)→ 对照 3.2 的现象核对
3.2 观察到的现象
本课现象以随课视频演示为准(视频里会把五种现象连着跑一遍),文档不逐条配截图;你自己做完请拍一张或录一段,作为提交物。
DEMO_MODE |
看到什么算成功 | 怎么核对 |
|---|---|---|
| 1 全亮 | 上电/复位后 DS0~DS7 八个红灯一起亮着不动 | 数一数是 8 个都亮;有一个不亮就是那一路的问题(引脚、驱动、焊接) |
| 2 间隔亮 | DS0、DS2、DS4、DS6 亮 0.5 秒 ↔ DS1、DS3、DS5、DS7 亮 0.5 秒,来回交替 | 一秒变两次;相邻两个灯永远不同时亮,奇偶两组正好互补 |
| 3 跑马灯(默认) | 复位后先自检:DS0→DS7 依次各亮 0.2 秒走一遍 → 八个灯全亮 0.3 秒 → 全灭 0.3 秒;随后进入循环:每次只亮一个灯、0.2 秒移一位、一圈 1.6 秒 | 先看见"走一遍"再看循环;任意时刻只有 1 个灯亮,全程没有声音 |
| 4 全闪 | 八个灯一起亮 0.5 秒、一起灭 0.5 秒,整整齐齐地闪 | 8 个灯永远同亮同灭;一秒变两次;同样没有声音 |
| 5 只蜂鸣器 | 八个灯全灭,蜂鸣器"响 0.2 秒 → 停 0.8 秒"规律循环 | 每秒一声、节奏均匀;灯一点都不亮(1~4 静音,要听声音就切到 5) |
现象 3 的时间轴(从按复位键算起):
| 时刻 | 看到 |
|---|---|
| 0 ~ 1.6 s | DS0→DS7 依次点亮,每 0.2 秒移一位(自检) |
| 1.6 ~ 1.9 s | 八个灯全亮(查 8 路是不是都通) |
| 1.9 ~ 2.2 s | 八个灯全灭 |
| 2.2 s 起 | 循环:DS0→DS7 逐个点亮,每 0.2 秒移一位,一圈 1.6 秒,周而复始 |
看不到现象怎么办(按最可能的顺序查):
| 现象 | 最可能的原因 | 怎么处理 |
|---|---|---|
| 八个灯一个都不亮 | 忘了开时钟,或者电平写反了 | 检查 __HAL_RCC_GPIOC_CLK_ENABLE():本板低电平点亮 ,写 GPIO_PIN_SET 是"灭" |
| 灯只增不减、越亮越多 | 循环里漏了"先全灭" | 每一轮先 LED_ALL_OFF(),再点亮当前位 |
| 下载过了,还是旧现象 | 改了 DEMO_MODE 却只保存没重新编译下载 |
重新 Build(F7)+ Download(F8) |
| 现象 1~4 里蜂鸣器一直叫(本该静音) | 极性反了:板子其实是"高电平响" | 把 #define BEEP_ACTIVE_HIGH 0 改成 1 重新编译(本课默认 0 = 低电平响) |
| 现象 5 里蜂鸣器节奏不对(一直响/不响) | 同样是极性不对 | 把 BEEP_ACTIVE_HIGH 在 0 / 1 之间对调后重新编译 |
| 想听蜂鸣器却一直没声音 | 1~4 四种现象本来就是静音的 |
把 DEMO_MODE 改成 5 重新编译下载 |
下载报 No Cortex-M Device found |
下载器没连好 / 板子没上电 | 查 SWD 三根线和供电(课时00 已练过一遍) |
四、学生练习
课堂上按顺序做,一条一条改、一条一条看现象:
| 编号 | 任务 | 提示 | 预期结果 |
|---|---|---|---|
| L1.1 | 只点亮一个灯 | 在 DEMO_MODE 1 里把 LED_ALL_ON() 换成 LED_ON(0),再换成 LED_ON(7) 各试一次 |
只有 DS0(或只有 DS7)亮,其余全灭 |
| L1.2 | 改速度 | 把现象 3 里的 delay_ms(200) 分别改成 50、500 各编译下载一次 |
灯明显跑得更快 / 更慢,顺序不变(现象 3 全程静音) |
| L1.3 | 来回跑 | 在现象 3 的循环里再加一段 for (i = 6; i > 0; i--) 反向点亮 |
DS0→DS7→DS0 来回跑,两头不停顿(反向那段从 6 开始,免得第 7 个灯连亮两次) |
| L1.4 | 奇偶互换 | 现象 2 里把两处 0x00AA 与 0x0055 对调 |
亮的灯正好相反:这次是 DS0、DS2、DS4、DS6 先亮 |
| L1.5 | 改蜂鸣节奏 | 在现象 5 里把 delay_ms(200) / delay_ms(800) 改成 100 / 300,再试"嘀嘀---停"这种快节奏 |
声音变得又急又密,LED 仍然全灭 |
| L1.6 | 挑战:改成"中间向外亮" | 用 LED_ON(3)、LED_ON(4) 再往两边扩,像 DS3/DS4 → DS2/DS5 → DS1/DS6 → DS0/DS7 |
一圈从中间向两边散开(在现象 3 上改,仍然是静音的) |
五、作业
- 必做:把五种现象(
DEMO_MODE改成 1~5)各跑一遍,把跑马灯那一种录成 10~15 秒短视频;再用一句话说清"哪种现象是整端口一次写 8 位、哪种是单个引脚移位" - 必做:完成练习 L1.2(
delay_ms(200)改成50/500),各录 5 秒或截图,写一句观察:速度变了,灯的顺序变了吗? - 选做:把跑马灯改成"两个灯同时跑"(在
LED_ON(i)后面再加一句LED_ON((i + 4) % 8)),录视频和原来的对比 - 提交:现象视频或截图 + 一段说明"我改了哪一行、现象怎么变、卡在哪一步怎么解决的";验收标准:跑马灯顺序正确(DS0→DS7,任意时刻只亮一个)、现象 5 里蜂鸣器能规律"嘀---嘀---"响、编译输出
0 Error(s), 0 Warning(s)
六、本节小结与下节预告
本节把 GPIO 输出的三步(开时钟 → 配推挽 → 写电平)完整走了一遍:PC0~PC7 输出低电平点亮 DS0~DS7,PB8 写对电平让蜂鸣器响(蜂鸣器只在现象 5 里响,其余四种灯效全程静音);五种现象写在同一个 main.c 里、用 DEMO_MODE 切换,节奏全靠软件延时 delay_ms() 排出来,不涉及定时器和中断。记住三句话就够了:命令顺序不能反 (先开时钟)、本板低电平点亮 、点亮下一个之前先灭掉上一个。下节进入 GPIO 输入与外部中断:读按键的电平,让灯听你的话。
本节函数与宏速查
| 名称 | 一句话作用 |
|---|---|
__HAL_RCC_GPIOC_CLK_ENABLE() / __HAL_RCC_GPIOB_CLK_ENABLE() |
给端口开时钟(GPIOC 点灯、GPIOB 控蜂鸣器) |
HAL_GPIO_Init(端口, &结构体) |
按 GPIO_InitTypeDef 把引脚配成推挽输出 |
HAL_GPIO_WritePin(端口, 引脚, 电平) |
写一个或一组引脚的电平(GPIO_PIN_SET 高、GPIO_PIN_RESET 低) |
HAL_GPIO_TogglePin(端口, 引脚) |
翻转引脚电平(选做里可以试:一行就能闪) |
delay_ms(n) / delay_us(n) |
软件延时 n 毫秒 / n 微秒(SysTick 忙等,不是外设定时器) |
附录
A. 怎么编译下载(照着做就能跑)
text
1. 上电接线:板子 USB 供电 → ST-Link 接 SWD 三根线(SWDIO / SWCLK / GND)→ ST-Link 插电脑
2. Keil 打开:课时文档/代码/课时01-GPIO输出跑马灯与蜂鸣器/USER/LED.uvprojx
3. 选下载器:Options for Target → Debug → ST-Link Debugger → OK
4. 选现象 :main.c 开头 #define DEMO_MODE 3(可改成 1 / 2 / 4 / 5;只有 5 会响蜂鸣器)
5. 编译 :Build(F7),确认 0 Error(s), 0 Warning(s)
6. 下载 :Download(F8),确认 Programming Done. Verify OK.
7. 复位 :按 RST 键,看 DS0→DS7 依次点亮,随后进入跑马灯循环
B. 本课代码
本课只改一个文件 :课时文档/代码/课时01-GPIO输出跑马灯与蜂鸣器/USER/main.c(完整代码见 2.2 节)。工程里其余文件都是现成的,不用动:
| 文件 | 作用 |
|---|---|
USER/LED.uvprojx |
Keil 工程文件(用 Keil 打开这个) |
USER/main.c |
本课自写代码:五种现象(1~4 是灯效、5 只响蜂鸣器)+ 蜂鸣器初始化(BEEP_Init) |
HARDWARE/LED/led.c、led.h |
LED 驱动:把 PC0~PC7 配成推挽输出并上电置全灭,提供 LED0~LED7 宏 |
SYSTEM/sys、SYSTEM/delay |
时钟配置、SysTick 延时(delay_ms / delay_us) |
SYSTEM/usart |
串口(本课不用,课时03 才用) |
HALLIB/、CORE/ |
STM32F1 HAL 库与启动文件 |
OBJ/ |
编译输出(已加入 .gitignore,不进仓库) |
C. 引脚对照表(贴在板子旁边用)
| 引脚 | 板载元件 | 点亮 / 工作电平 |
|---|---|---|
| PC0 | DS0(红色 LED) | 低电平点亮 |
| PC1 | DS1(红色 LED) | 低电平点亮 |
| PC2 | DS2(红色 LED) | 低电平点亮 |
| PC3 | DS3(红色 LED) | 低电平点亮 |
| PC4 | DS4(红色 LED) | 低电平点亮 |
| PC5 | DS5(红色 LED) | 低电平点亮 |
| PC6 | DS6(红色 LED) | 低电平点亮 |
| PC7 | DS7(红色 LED) | 低电平点亮 |
| PB8 | 蜂鸣器 BEEP | 低电平响 (V1.3 实测;若你的板子是高电平响,把 BEEP_ACTIVE_HIGH 改成 1) |
相关文档
- 课程大纲(课时01 在"一、基础外设开发模块")
- 课程文档写作规范(0.1 教学环境约定)
- 上一课:课时00 开发环境搭建与开发板认识
- 下一课:课时02 GPIO 输入与外部中断:按键