课时01 嵌入式技术应用设计实训 | GPIO 输出:跑马灯与蜂鸣器

嵌入式技术应用设计实训 | 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 种现象里响,其余现象全程静音;节奏全靠软件延时排出来,不涉及定时器与中断。

本节目标

  1. 能说出点亮一个 LED 要按"开时钟 → 配推挽 → 写电平"三步走,并说清顺序反了为什么既不报错也不亮
  2. 会改 main.c 里的 DEMO_MODE 宏(改成 1、2、3、4),把板载 8 个 LED 跑出全亮、间隔亮、跑马灯三种现象
  3. 会用同一套写法驱动 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)

相关文档

相关推荐
潘潘的嵌入式日记20 小时前
Keil 的三个 Go To:跳不动先查 Browse Information
调试技巧·keil·mdk·代码导航·browse information
@嵌入式扫地僧3 天前
STM32 面试实战:GPIO 配置常用模式
stm32·推挽输出·上拉输入·gpio 配置·stm32 面试
仲星(._.)3 天前
GPIO
嵌入式硬件·gpio
an123212113 天前
ARM---编译工具链|程序段|GPIO外设驱动
gcc·gpio
xxy2224116 天前
ARM裸机开发(4)GPIO与LED/按键驱动详解——引脚复用与电气特性配置
arm开发·gpio
xx~t18 天前
嵌入式——单片机2
嵌入式硬件·学习·51单片机·串口·蜂鸣器
Molesidy18 天前
【Embedded Development】 Keil或IAR如何制作静态库并装载入Keil或IAR工程当中进行编译烧录之库封装——lib库的生成
keil·lib·iar
十月的皮皮2 个月前
STM32从零到量产开发:四路继电器工业控制模块开发 -上位机 Program.cs 应用程序入口设计说明
c语言·stm32·单片机·stm32cubemx·hal库
Ronin-Lotus2 个月前
蓝桥杯篇---EEPROM内部存储地址
职场和发展·蓝桥杯·c·eeprom·keil