当前位置: 首页 > news >正文

STM32之beep、多文件、延迟、按键以及呼吸灯

一、Beep控制

原理图分析:

  • 蜂鸣器三极管控制引脚对应 MCU PB8。
  • 当前蜂鸣器对应的电路中,三极管是 NPN 三极管,当前【基极】存在小电流,当前三极管导通。要求对应 PB8 引脚对外输出电压 / 电流。
  • 当前 PB8 输出高电平,当前三极管导通,BEEP 工作。PB8 提供一个低电平,三极管未导通,BEEP 不工作。当前 PB8 要求的 GPIO 工作模式必须是【推挽模式】。

代码流程:

  • 时钟使能,RCC->APB2ENR。
  • 设置当前 GPIO PB8 工作模式必须是【推挽模式】。需要通过 GPIOB->CRH 配置 PB8 引脚工作模式。需要配置的寄存器位置是【位 3:0】。
  • 利用 GPIOx_ODR 控制输出电平情况。高电平 BEEP 工作,低电平 BEEP 停止工作。

代码实现:

#include "stm32f10x.h"
/*
STM32 核心头文件,标准头文件,当前对应 STM32F10x 系列,
当前使用的芯片是 STM32F103ZET6
开发中,需要使用相关函数,相关类型,相关配置都在当前
头文件中。
*/
void Delay(u32 tim);
int main(void)
{// 1. 时钟使能,RCC 控制使用 GPIOB GPIO组RCC->APB2ENR = 0x01 << 3;// 2. 配置 GPIOB --> PB8 推挽模式,需要使用 CRHGPIOB->CRH &= ~(0x0F);GPIOB->CRH |= 0x03; // 推挽模式,同时输出速度最高位 50MHz/*3. ODR 控制输出高电平或者低电平高电平 BEEP 工作低电平 BEEP 不工作*/while (1){// 控制输出高电平GPIOB->ODR |= 0x01 << 8;Delay(200000);// 控制输出低电平GPIOB->ODR &= ~(0x01 << 8);Delay(200000);}
}
void Delay(u32 tim)
{while (tim--){for (int i = 0; i < 72; i++){}}
}

二、keil的多文件编程

        将 LED 和 BEEP 控制模式进行函数封装,同时利用多文件方式,将不同的模块划分为不同的文件,从而实现模块化开发。
需要创建不同模块的 .c 和 .h。同时完成 Keil5 工程配置。
估计模块需求创建新文件,需要提供 .c 和 .h:

  • 利用 Ctrl + N 创建新文件。
  • Ctrl + S 保存当前文件。
  • 后续操作如图。

代码实现:

led.c:

#include "led.h"/*** @brief LED初始化函数:配置GPIO引脚为推挽输出模式*/
void Led_Init(void)
{// 1. 使能GPIOB和GPIOE时钟RCC->APB2ENR |= 0x01 << 3;  // GPIOB时钟使能RCC->APB2ENR |= 0x01 << 6;  // GPIOE时钟使能// 2. 配置PB5为推挽输出模式(50MHz)GPIOB->CRL &= ~(0x0F << 20);  // 清除PB5原有配置GPIOB->CRL |= 0x03 << 20;     // 配置PB5为推挽输出// 3. 配置PE5为推挽输出模式(50MHz)GPIOE->CRL &= ~(0x0F << 20);  // 清除PE5原有配置GPIOE->CRL |= 0x03 << 20;     // 配置PE5为推挽输出// 4. 默认输出高电平,LED初始状态为熄灭GPIOB->ODR |= 0x01 << 5;  // PB5输出高电平,LED0灭GPIOE->ODR |= 0x01 << 5;  // PE5输出高电平,LED1灭
}/*** @brief 控制LED0的亮灭* * @param flag 1-点亮LED0,0-熄灭LED0*/
void Led0_Ctrl(u8 flag)
{if (flag){// 输出低电平,点亮LED0GPIOB->ODR &= ~(0x01 << 5);}else{// 输出高电平,熄灭LED0GPIOB->ODR |= 0x01 << 5;}
}/*** @brief 控制LED1的亮灭* * @param flag 1-点亮LED1,0-熄灭LED1*/
void Led1_Ctrl(u8 flag)
{if (flag){// 输出低电平,点亮LED1GPIOE->ODR &= ~(0x01 << 5);}else{// 输出高电平,熄灭LED1GPIOE->ODR |= 0x01 << 5;}
}/*** @brief 实现LED0和LED1交替闪烁效果*/
void Led_Blink(void)
{Led0_Ctrl(1);  // 点亮LED0Led1_Ctrl(0);  // 熄灭LED1Delay_ms(500); // 延时500msLed0_Ctrl(0);  // 熄灭LED0Led1_Ctrl(1);  // 点亮LED1Delay_ms(500); // 延时500ms
}

led.h:

#ifndef _LED_H
#define _LED_H#include "stm32f10x.h"
#include "delay.h"/*** @brief 当前开发板中 DS0 和 DS1 LED 灯配置初始化*/
void Led_Init(void);/*** @brief DS0 对应的 LED0 灯控制函数* * @param flag 控制标志:1-LED0亮,0-LED0灭*/
void Led0_Ctrl(u8 flag);/*** @brief DS1 对应的 LED1 灯控制函数* * @param flag 控制标志:1-LED1亮,0-LED1灭*/
void Led1_Ctrl(u8 flag);/*** @brief LED0 和 LED1 交替闪烁*/
void Led_Blink(void);#endif

三、__Nop()函数实现延时操作

  1. delay.h:头文件

    • 使用条件编译防止重复引用(#ifndef _DELAY_H 等)
    • 声明了微秒级延时(Delay_us)和毫秒级延时(Delay_ms)函数
    • 包含函数注释,说明功能和参数含义
  2. delay.c:源文件

    • 实现了头文件中声明的两个延时函数
    • Delay_us:通过连续调用 72 个 __NOP() 空操作实现约 1 微秒的延时(基于 STM32F103ZET6 的 72MHz 主频设计)
    • Delay_ms:通过循环调用 Delay_us(1000) 实现毫秒级延时(1 毫秒 = 1000 微秒)

使用时,只需在需要延时的地方调用这两个函数即可,例如:

  • Delay_us(500):延时 500 微秒
  • Delay_ms(100):延时 100 毫秒

        该延时模块采用纯寄存器级操作,不依赖系统滴答定时器,适合在初始化阶段或对延时精度要求不极高的场景使用。

delay.c:

#include "delay.h"/*** @brief 微秒级延时函数* * @param us 要延时的微秒数* @note 基于STM32F103ZET6的72MHz主频设计,1个__NOP()占用约13.889ns*       72个__NOP()约占用1us(72 * 13.889ns ≈ 1000ns)*/
void Delay_us(u32 us)
{while (us--){// 连续调用72个空操作,实现约1us的延时__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();}
}/*** @brief 毫秒级延时函数* * @param ms 要延时的毫秒数* @note 基于微秒延时函数实现,1ms = 1000us*/
void Delay_ms(u32 ms)
{while (ms--){Delay_us(1000);  // 调用1000次微秒延时,实现1ms延时}
}

delay.h:

#ifndef _DELAY_H
#define _DELAY_H
#include "stm32f10x.h"/*** @brief 延时微秒控制函数,延时单位是 us** @param us 延时微秒时间(取值范围:0~4294967295)*/
void Delay_us(u32 us);/*** @brief 延时毫秒控制函数,延时单位是 ms** @param ms 延时毫秒时间(取值范围:0~4294967295)*/
void Delay_ms(u32 ms);#endif

四、可编程按键控制

原理图分析:

  • 根据原理图分析,KEY0、KEY1、KEY2 按键按下之后,当前电路接地,对应低电平。
  • KEY_UP 按键按下之后,当前电路接 3.3V,对应高电平。
  • 如果想要通过按键按下之后的电平切换作为按键判断条件。KEY0、KEY1、KEY2 对应 MCU 引脚默认【高电平】,KEY_UP 对应 MCU 引脚默认【低电平】。
  • KEY0、KEY1、KEY2 对应 MCU 引脚为【上拉输入模式】。
  • KEY_UP 对应 MCU 引脚为【下拉输入模式】。

KEY0、KEY1、KEY2 引脚对应关系:

  • KEY0 ==> PE4;KEY1 ==> PE3;KEY2 ==> PE2。

KEY_UP 引脚对应关系:

  • KEY_UP ==> PA0。

开发流程:

  • 时钟使能,需要使能的 GPIO 组为 GPIOE 和 GPIOA
    • RCC->APB2ENR 使用 GPIOE 和 GPIOA
  • 控制 GPIO 工作模式
    • PA0【下拉输入模式】
    • PE2 ~ PE4【上拉输入模式】
  • 利用 GPIOx_IDR 寄存器,读取对应引脚的电平状态,判断对应按键是否按下。
    • PA0 对应 KEY_UP 按下,电平状态从低电平切换到高电平状态。
    • PE2 ~ PE4 对应 KEY2、KEY1、KEY0 按下,电平状态从高电平切换到低电平状态。

寄存器文档分析:

代码实现:

key.c:

#include "key.h"/*** @brief 按键初始化函数* 配置KEY0(PE4)、KEY1(PE3)、KEY2(PE2)为上拉输入* 配置KEY_UP(PA0)为下拉输入*/
void Key_Init(void)
{// 使能GPIOA和GPIOE时钟RCC->APB2ENR |= (1 << 2);  // 使能GPIOA时钟RCC->APB2ENR |= (1 << 6);  // 使能GPIOE时钟// 配置PE2、PE3、PE4为上拉输入GPIOE->CRL &= ~(0x0F << (2*4));  // 清除PE2配置GPIOE->CRL &= ~(0x0F << (3*4));  // 清除PE3配置GPIOE->CRL &= ~(0x0F << (4*4));  // 清除PE4配置GPIOE->CRL |= (0x08 << (2*4));   // PE2: 上拉输入(1000)GPIOE->CRL |= (0x08 << (3*4));   // PE3: 上拉输入(1000)GPIOE->CRL |= (0x08 << (4*4));   // PE4: 上拉输入(1000)GPIOE->ODR |= (1 << 2);    // PE2上拉GPIOE->ODR |= (1 << 3);    // PE3上拉GPIOE->ODR |= (1 << 4);    // PE4上拉// 配置PA0为下拉输入GPIOA->CRL &= ~(0x0F << (0*4));  // 清除PA0配置GPIOA->CRL |= (0x08 << (0*4));   // PA0: 下拉输入(1000)// PA0默认下拉,无需额外配置ODR
}/*** @brief 获取按键状态* @return 按键值,分别对应KEY0、KEY1、KEY2、KEY_UP或无按键*/
u8 Key_GetValue(void)
{// 检测KEY0(PE4)if (!(GPIOE->IDR & (1 << 4)))    // 低电平表示按下{Delay_ms(10);                // 消抖if (!(GPIOE->IDR & (1 << 4))){while (!(GPIOE->IDR & (1 << 4)));  // 等待释放return KEY0_VALUE;}}// 检测KEY1(PE3)if (!(GPIOE->IDR & (1 << 3)))    // 低电平表示按下{Delay_ms(10);                // 消抖if (!(GPIOE->IDR & (1 << 3))){while (!(GPIOE->IDR & (1 << 3)));  // 等待释放return KEY1_VALUE;}}// 检测KEY2(PE2)if (!(GPIOE->IDR & (1 << 2)))    // 低电平表示按下{Delay_ms(10);                // 消抖if (!(GPIOE->IDR & (1 << 2))){while (!(GPIOE->IDR & (1 << 2)));  // 等待释放return KEY2_VALUE;}}// 检测KEY_UP(PA0)if (GPIOA->IDR & (1 << 0))       // 高电平表示按下{Delay_ms(10);                // 消抖if (GPIOA->IDR & (1 << 0)){while (GPIOA->IDR & (1 << 0));      // 等待释放return KEY_UP_VALUE;}}return NO_KEY_PRESSED;  // 无按键按下
}

key.h:

#ifndef _KEY_H
#define _KEY_H
#include "stm32f10x.h"
#include "delay.h"// 按键值定义
#define KEY0_VALUE     (0)    // KEY0按键对应返回值
#define KEY1_VALUE     (1)    // KEY1按键对应返回值
#define KEY2_VALUE     (2)    // KEY2按键对应返回值
#define KEY_UP_VALUE   (3)    // 上拉按键对应返回值
#define NO_KEY_PRESSED (4)    // 无按键按下返回值// 函数声明
void Key_Init(void);          // 按键初始化函数
u8 Key_GetValue(void);        // 获取按键值函数#endif

main.c:

#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "delay.h"int main(void)
{// 初始化外设Delay_Init();  // 延时初始化Led_Init();    // LED初始化Key_Init();    // 按键初始化while (1){u8 key_value = Key_GetValue();  // 获取按键值// 根据按键值控制LEDswitch (key_value){case KEY0_VALUE:Led0_Ctrl(1);  // 打开LED0break;case KEY1_VALUE:Led0_Ctrl(0);  // 关闭LED0break;case KEY2_VALUE:Led1_Ctrl(1);  // 打开LED1break;case KEY_UP_VALUE:Led1_Ctrl(0);  // 关闭LED1break;default:// 无按键操作时无需处理break;}}
}

五、闪烁灯与呼吸灯

完整代码:

led.c:

#include "led.h"void Led_Init(void)
{//RCC对PB端口和PE端口时钟使能RCC->APB2ENR |= (0x01 << 3 | 0x01 << 6);//GPIO寄存器配置GPIOB->CRL &= ~(0xf << 20);GPIOE->CRL &= ~(0xf << 20);GPIOB->CRL |= (0x1 << 20);GPIOE->CRL |= (0x1 << 20);
}
void Led_Ctrl(int value)
{if(value){GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR &= ~(0x1 << 5);}else{GPIOB->ODR |= (0x1 << 5);GPIOE->ODR |= (0x1 << 5);}
}
void Led_Blink(void)
{//低电平LED0灯亮 高电平LED1灯灭 GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR |= (0x1 << 5);Delay_ms(300);//高电平LED0灯灭 低电平LED1灯亮GPIOB->ODR |= (0x1 << 5);GPIOE->ODR &= ~(0x1 << 5);Delay_ms(300);
}
void Led_Breath(void)
{int j = 0;while(1){GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR |= (0x1 << 5);Delay_us(5000 - j);GPIOE->ODR &= ~(0x1 << 5);GPIOB->ODR |= (0x1 << 5);Delay_us(j);j += 10;if(j == 5000){break;}}j = 0;while(1){GPIOE->ODR &= ~(0x1 << 5);GPIOB->ODR |= (0x1 << 5);Delay_us(5000 - j);GPIOB->ODR &= ~(0x1 << 5);GPIOE->ODR |= (0x1 << 5);Delay_us(j);j += 10;if(j == 5000){break;}}
}

led.h:

#ifndef __LED_H
#define __LED_H#include "stm32f10x.h"
#include <stdio.h>
#include "delay.h"void Led_Init(void);
void Led_Ctrl(int value);
void Led_Blink(void);
void Led_Breath(void);#endif

main.c:

#include "stm32f10x.h"
#include "led.h"
#include "delay.h"int main(void)
{Led_Init();while(1){//Led_Ctrl(3);Led_Blink();//Led_Breath();}
}

代码解析:

一、Led_Blink(void) 函数:LED 交替闪烁功能

该函数实现 两个 LED 灯(LED0 和 LED1)固定频率交替亮灭 的效果,具体逻辑如下:

 
  1. 初始状态设置

    • GPIOB->ODR &= ~(0x1 << 5);:将 GPIOB 端口的第 5 位(PB5)置为低电平(假设 LED0 接 PB5,低电平点亮),此时 LED0 亮。
    • GPIOE->ODR |= (0x1 << 5);:将 GPIOE 端口的第 5 位(PE5)置为高电平(假设 LED1 接 PE5,高电平熄灭),此时 LED1 灭。
  2. 延时保持

    • Delay_ms(300);:维持 “LED0 亮、LED1 灭” 的状态 300 毫秒。
  3. 状态反转

    • GPIOB->ODR |= (0x1 << 5);:PB5 置为高电平,LED0 灭。
    • GPIOE->ODR &= ~(0x1 << 5);:PE5 置为低电平,LED1 亮。
  4. 再次延时

    • Delay_ms(300);:维持 “LED0 灭、LED1 亮” 的状态 300 毫秒。
 

效果总结
LED0 和 LED1 以 600 毫秒为一个周期 交替闪烁(各亮 300ms,灭 300ms),状态切换清晰、节奏固定,类似 “红绿灯交替” 的效果。

二、Led_Breath(void) 函数:LED 呼吸灯效果

该函数通过 改变 LED 亮灭时间的占空比,实现两个 LED 灯(LED0 和 LED1)交替 “呼吸”(亮度逐渐变化)的效果,具体分为两个阶段:

阶段 1:LED0 逐渐变暗,LED1 逐渐变亮
  • 初始化变量 j = 0,通过 while(1) 循环逐步调整亮灭时间:

    • 先让 LED0 亮(PB5 低电平)、LED1 灭(PE5 高电平),并延时 5000 - j 微秒(随j增大,此延时逐渐减小)。
    • 再让 LED0 灭(PB5 高电平)、LED1 亮(PE5 低电平),并延时 j 微秒(随j增大,此延时逐渐增大)。
    • 每次循环 j += 10,直到 j = 5000 时退出循环。

    过程解析

    • 初始时 j=0:LED0 亮 5000us(常亮),LED1 灭 0us(常灭)→ LED0 最亮,LED1 最暗。
    • 随着j增大:LED0 亮的时间缩短、灭的时间增长 → 亮度逐渐降低;LED1 亮的时间增长、灭的时间缩短 → 亮度逐渐升高。
    • 结束时 j=5000:LED0 亮 0us(常灭),LED1 亮 5000us(常亮)→ LED0 最暗,LED1 最亮。
阶段 2:LED1 逐渐变暗,LED0 逐渐变亮
  • 重置 j = 0,再次进入 while(1) 循环,逻辑与阶段 1 对称:

    • 先让 LED1 亮(PE5 低电平)、LED0 灭(PB5 高电平),延时 5000 - j 微秒(随j增大而减小)。
    • 再让 LED1 灭(PE5 高电平)、LED0 亮(PB5 低电平),延时 j 微秒(随j增大而增大)。
    • 直到 j = 5000 时退出循环。

    过程解析

    • 初始时 j=0:LED1 常亮,LED0 常灭 → LED1 最亮,LED0 最暗。
    • 随着j增大:LED1 亮度逐渐降低,LED0 亮度逐渐升高。
    • 结束时 j=5000:LED1 常灭,LED0 常亮 → LED1 最暗,LED0 最亮。
 

效果总结
两个 LED 灯交替实现 “呼吸” 效果:

 
  • 先 LED0 从最亮逐渐变暗至熄灭,同时 LED1 从熄灭逐渐变亮至最亮;
  • 再 LED1 从最亮逐渐变暗至熄灭,同时 LED0 从熄灭逐渐变亮至最亮。
    整体效果类似人呼吸时的 “明暗渐变”,过渡平滑自然。

https://github.com/0voice

http://www.lryc.cn/news/625884.html

相关文章:

  • 【数据结构】用堆解决TOPK问题
  • 服务器数据恢复—硬盘坏道离线导致raid崩溃的StorNext文件系统数据恢复案例
  • 深度学习-167-MCP技术之工具函数的设计及注册到MCP服务器的两种方式
  • 应用控制技术、内容审计技术、AAA服务器技术
  • Commons-io
  • Syntax Error: Error: PostCSS received undefined instead of CSS string
  • CSS封装大屏自定义组件(标签线)
  • 2025年6月中国电子学会青少年软件编程(图形化)等级考试试卷(一级)答案 + 解析
  • LangChain —多模态 / 多源上下文管理
  • 云原生俱乐部-mysql知识点归纳(3)
  • 【论文阅读】SIMBA: single-cell embedding along with features(1)
  • 《Dual Prompt Personalized Federated Learning in Foundation Models》——论文阅读
  • 自然语言处理(NLP)技术的发展历史
  • 【QT入门到晋级】进程间通信(IPC)-socket(包含性能优化案例)
  • Python爬虫实战:研究ICP-Checker,构建ICP 备案信息自动查询系统
  • GIS在海洋大数据的应用
  • 数据结构:深入解析常见数据结构及其特性
  • 3 创建wordpress网站
  • 【实时Linux实战系列】实时大数据处理与分析
  • 【数据库】通过‌phpMyAdmin‌管理Mysql数据
  • 计算机毕设推荐:痴呆症预测可视化系统Hadoop+Spark+Vue技术栈详解
  • [Polly智能维护网络] 网络重试原理 | 弹性策略
  • 图像采集卡与工业相机:机器视觉“双剑合璧”的效能解析
  • CMake进阶: CMake Modules---简化CMake配置的利器
  • 小迪安全v2023学习笔记(六十六讲)—— Java安全SQL注入SSTISPELXXE
  • Webpack 5 配置完全指南:从入门到精通
  • 云手机矩阵:重构企业云办公架构的技术路径与实践落地
  • HarmonyOS 中的 泛型类和泛型接口
  • oc-mirror plugin v2 错误could not establish the destination for the release i
  • 力扣hot100:三数之和(排序 + 双指针法)(15)