嵌入式技术应用设计实训 | 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/都是现成的(厂家例程里已经写好),照用即可:
课时文档/代码/课时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切换):
#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 全灭,蜂鸣器“嘀—嘀—”规律响(本课只有这一种现象会响) ===================================================================== */#defineDEMO_MODE3/* ============ 蜂鸣器极性开关(只有现象 5 会用到,按实测结果选一个) ============ 1 = 高电平响(PB8 输出高电平时蜂鸣器响) 0 = 低电平响(PB8 输出低电平时蜂鸣器响) 本板(正点原子 Nano V1.3)实测是“低电平响”,所以这里填 0;换成高电平响的板子就改成 1。 有源蜂鸣器只要给对电平就响,不需要 PWM(要“调音”才用 PWM,那是课时05) =========================================================================== */#defineBEEP_ACTIVE_HIGH0/* ---------- 板载 LED:低电平点亮,PC0~PC7 分别对应 DS0~DS7 ---------- */#defineLED_ALL_OFF()HAL_GPIO_WritePin(GPIOC,(uint16_t)0x00FF,GPIO_PIN_SET)/* 8 个灯全灭 */#defineLED_ALL_ON()HAL_GPIO_WritePin(GPIOC,(uint16_t)0x00FF,GPIO_PIN_RESET)/* 8 个灯全亮 */#defineLED_ON(x)HAL_GPIO_WritePin(GPIOC,(uint16_t)(1U<<(x)),GPIO_PIN_RESET)/* 点亮第 x 个,x=0~7 */#defineLED_OFF(x)HAL_GPIO_WritePin(GPIOC,(uint16_t)(1U<<(x)),GPIO_PIN_SET)/* 熄灭第 x 个 *//* ---------- 板载蜂鸣器:BEEP_ON 响、BEEP_OFF 停(极性由上面开关决定) ---------- */#if(BEEP_ACTIVE_HIGH==1)#defineBEEP_ON()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET)#defineBEEP_OFF()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET)#else#defineBEEP_ON()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET)#defineBEEP_OFF()HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET)#endif/* 蜂鸣器初始化:PB8 推挽输出 —— 和 LED_Init() 是同一套三步:开时钟 → 配模式 → 写电平 */staticvoidBEEP_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);}intmain(void){#if(DEMO_MODE==3)u8 i;/* 只有“跑马灯”这一种现象需要循环变量 */#endifHAL_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 | 是 | 选引脚,多个引脚用|连起来,如GPIO_PIN_0|GPIO_PIN_1(PC0、PC1 一起配) |
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 |
操作步骤(照着做就能跑):
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. 怎么编译下载(照着做就能跑)
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 输入与外部中断:按键