小项目:蓝牙模块点亮RGB三色灯

news2025/1/18 11:51:18

在之前的教程中,我们学习了蓝牙模块的原理,并动手写了驱动,实现了串口的接收和发送。本次我们就来教大家如何使用蓝牙串口控制灯。这是一个简单的示例,展示了如何将蓝牙通信与硬件控制相结合,实现远程控制的功能。你也可以扩展这个示例,添加更多的指令和功能,以满足自己的需求。

1. 源码下载及前置阅读

本文首发 良许嵌入式网 :https://www.lxlinux.net/e/ ,欢迎关注!

本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):

https://www.lxlinux.net/e/stm32/bluetooth-rgb-led.html

如果你是嵌入式开发小白,那么建议你先读读下面几篇文章。

  • 了解不同的下载程序方法,为你的嵌入式开发提供更多选择:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html
  • 手把手让你掌握MDK的使用方式和技巧,助你更高效地进行开发:https://www.lxlinux.net/e/stm32/mdk-development-tool-tutorial.html
  • 逐步引导你入门STM32开发,无需担心基础问题:https://www.lxlinux.net/e/stm32/stm32-quick-start-for-beginner.html

前期教程,没看过的小伙伴可以先看下。

  • 深入了解蓝牙模块的原理和驱动方法,让你能够轻松应用于实际项目中:https://www.lxlinux.net/e/stm32/bluetooth-turorial.html
  • 嵌入式基本功,为后续学习打下坚实的基础:https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-rxne-time-out.html

2. 项目需求

实现目标是我们有一个三色 LED 灯,手机连上蓝牙后,向蓝牙串口发送关键词 green 则绿灯亮,再次发送 green 则绿灯灭,黄灯和红灯的关键词是 yellow、red ,效果类似。

3. 编程实战

3.1 硬件接线

本教程使用的硬件如下:

  • 单片机:STM32F103C8T6
  • 蓝牙模块:HC-08
  • 小灯:三色 LED 灯模块
  • 串口:USB 转 TTL
  • 烧录器:ST-LINK V2
HC-08LEDSTM32USB 转 TTL
VCC3.3
RXDA2
TXDA3
GNDG
RA5
YA6
GA7
GNDG
A10TX
A9RX
GGND

烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章 STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html。

ST-Link V2STM32
SWCLKSWCLK
SWDIOSWDIO
GNDGND
3.3V3V3

接好如下图:

3.2 LED逻辑代码

LED 灯的代码简简单单,只要进行一下三个灯的初始化就行。

void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    LED1_GPIO_CLK_ENABLE();                                 /* LED1时钟使能 */
    LED2_GPIO_CLK_ENABLE();                                 /* LED2时钟使能 */
    LED3_GPIO_CLK_ENABLE();                                 /* LED3时钟使能 */

    gpio_init_struct.Pin = LED1_GPIO_PIN;                   /* LED1引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);       /* 初始化LED1引脚 */

    gpio_init_struct.Pin = LED2_GPIO_PIN;                   /* LED2引脚 */
    HAL_GPIO_Init(LED2_GPIO_PORT, &gpio_init_struct);       /* 初始化LED2引脚 */

    gpio_init_struct.Pin = LED3_GPIO_PIN;                   /* LED3引脚 */
    HAL_GPIO_Init(LED3_GPIO_PORT, &gpio_init_struct);       /* 初始化LED3引脚 */

    LED1(0);                                                /* 关闭 LED1 */
    LED2(0);                                                /* 关闭 LED2 */
    LED3(0);                                                /* 关闭 LED3 */
}

LED 的 .h文件:

#ifndef _LED_H
#define _LED_H
#include "sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define LED1_GPIO_PORT                  GPIOA
#define LED1_GPIO_PIN                   GPIO_PIN_7
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口时钟使能 */

#define LED2_GPIO_PORT                  GPIOA
#define LED2_GPIO_PIN                   GPIO_PIN_6
#define LED2_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口时钟使能 */

#define LED3_GPIO_PORT                  GPIOA
#define LED3_GPIO_PIN                   GPIO_PIN_5
#define LED3_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PB口时钟使能 */

/******************************************************************************************/
/* LED端口定义 */
#define LED1(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED2(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED2_GPIO_PORT, LED2_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

#define LED3(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED3_GPIO_PORT, LED3_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)

/* LED取反定义 */
#define LED1_TOGGLE()   do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)        /* 翻转LED1 */
#define LED2_TOGGLE()   do{ HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_GPIO_PIN); }while(0)        /* 翻转LED2 */
#define LED3_TOGGLE()   do{ HAL_GPIO_TogglePin(LED3_GPIO_PORT, LED3_GPIO_PIN); }while(0)        /* 翻转LED3 */

/******************************************************************************************/
/* 外部接口函数*/
void led_init(void);                                                                            /* LED初始化 */

#endif

3.3 蓝牙收发

蓝牙收发我们在【手把手教你玩转蓝牙模块(原理+驱动):https://www.lxlinux.net/e/stm32/bluetooth-turorial.html】有详细教程,在这里就简单带过+浅浅复习下,没看过或者忘记了的小伙伴可以点击链接看看。

蓝牙模块通过串口与 MCU 进行通讯,所以第一步需要先做好串口的配置。

关于串口的配置,我写过一篇文章手把手教你玩串口,大家可以移步下文查看:

【STM32串口接收不定长数据(接收中断+超时判断):https://www.lxlinux.net/e/stm32/stm32-usart-receive-data-using-rxne-time-out.html】

具体代码如下:

UART_HandleTypeDef bt_uart_handle;

uint8_t bt_uart_rx_buf[BT_RX_BUF_SIZE];
uint8_t bt_uart_tx_buf[BT_TX_BUF_SIZE];
uint16_t bt_uart_rx_len = 0;

void bt_init(uint32_t baudrate)
{
    bt_uart_handle.Instance          = BT_INTERFACE;                 /* BT */
    bt_uart_handle.Init.BaudRate     = baudrate;                     /* 波特率 */
    bt_uart_handle.Init.WordLength   = UART_WORDLENGTH_8B;           /* 数据位 */
    bt_uart_handle.Init.StopBits     = UART_STOPBITS_1;              /* 停止位 */
    bt_uart_handle.Init.Parity       = UART_PARITY_NONE;             /* 校验位 */
    bt_uart_handle.Init.Mode         = UART_MODE_TX_RX;              /* 收发模式 */
    bt_uart_handle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;          /* 无硬件流控 */
    bt_uart_handle.Init.OverSampling = UART_OVERSAMPLING_16;         /* 过采样 */
    HAL_UART_Init(&bt_uart_handle);                                  /* 使能BT */
}

void bt_rx_clear(void)
{
    memset(bt_uart_rx_buf, 0, sizeof(bt_uart_rx_buf));              //清空接收缓冲区
    bt_uart_rx_len = 0;                                             //接收计数器清零
}

void BT_IRQHandler(void)
{
    uint8_t receive_data = 0;   
    if(__HAL_UART_GET_FLAG(&bt_uart_handle, UART_FLAG_RXNE) != RESET){      //获取接收RXNE标志位是否被置位
        if(bt_uart_rx_len >= sizeof(bt_uart_rx_buf))                        //如果接收的字符数大于接收缓冲区大小,
            bt_uart_rx_len = 0;                                             //则将接收计数器清零
        HAL_UART_Receive(&bt_uart_handle, &receive_data, 1, 1000);          //接收一个字符
        bt_uart_rx_buf[bt_uart_rx_len++] = receive_data;                    //将接收到的字符保存在接收缓冲区
    }

    if (__HAL_UART_GET_FLAG(&bt_uart_handle, UART_FLAG_IDLE) != RESET)      //获取接收空闲中断标志位是否被置位
    {
        printf("recv: %s\r\n", bt_uart_rx_buf);                             //将接收到的数据打印出来
        control_led();                                                      //检测是否有LED关键词
        bt_rx_clear();
        __HAL_UART_CLEAR_IDLEFLAG(&bt_uart_handle);                         //清除UART总线空闲中断
    }
}

通过这几个函数,我们就可以读取蓝牙返回的数据,并保存在数组 bt_uart_rx_buf 里。

如果需要通过串口向蓝牙模块发送数据,可以使用下面函数:

void bt_send(char *fmt, ...)
{
    va_list ap;
    uint16_t len;

    va_start(ap, fmt);
    vsprintf((char *)bt_uart_tx_buf, fmt, ap);
    va_end(ap);

    len = strlen((const char *)bt_uart_tx_buf);
    HAL_UART_Transmit(&bt_uart_handle, bt_uart_tx_buf, len, HAL_MAX_DELAY);
}

其实是否向蓝牙模块发送数据并不影响我们的实现效果,留着的目的一方面为了让大家复习一下,另一方面可以看出蓝牙模块是否在正常工作。

至此,蓝牙模块的初始化、发送、接收部分就做好了。

3.4 LED控制

检测蓝牙串口是否接收到 LED 关键词,如果有就反转 LED 灯状态。

void control_led()
{
    if(strstr((const char *)bt_uart_rx_buf, "green") != NULL)           //如果接收到关键词"green"
        LED1_TOGGLE();                                                  // 翻转LED1
    if(strstr((const char *)bt_uart_rx_buf, "yellow") != NULL)          //如果接收到关键词"yellow"
        LED2_TOGGLE();                                                  // 翻转LED2
    if(strstr((const char *)bt_uart_rx_buf, "red") != NULL)             //如果接收到关键词"red"
        LED3_TOGGLE();                                                  // 翻转LED3
}

3.5 主函数

在 main 函数里,我们可以先调用 bt_init() 函数进行初始化,然后调用 bt_send() 函数发送数据。

int main(void)
{
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    usart_init(115200);                         /* 串口1波特率设为115200 */
    bt_init(9600);                              /* 串口2波特率设为9600 */
    led_init();

    printf("蓝牙控制灯……\r\n");

    while(1)
    {
        bt_send("bt send\r\n");
        delay_ms(1000);
    }
}

4. 运行过程

将硬件连好,把串口插到电脑 USB 口。

接着我们打开电脑串口软件。设置串口助手波特率 115200(你们不一定要用我这款,随便的串口助手都可以),选择串口号,最后打开串口开始准备接收数据。

这个串口工具接收的是 MCU 串口 1 的数据,也就是 log 。蓝牙接收到数据后,我们使用串口 1 打印到下面的串口助手里。

烧录代码,串口输出如下:

然后打开手机蓝牙助手准备开始调试,(如果有提示下载弹窗的话,点击「下载好了」即可),点击蓝牙模块开始连接。没有蓝牙助手的同学,可以在前文找到下载地址。

img img

到这里,我们就完成了 MCU 通过蓝牙将数据透传到手机 APP(蓝牙助手)。

当然,我们也可以通过手机 APP 向蓝牙发送数据,MCU 接收到透传的数据之后通过串口助手打印在电脑上。

比如我们给蓝牙模块发送数据 green 、yellow、red。

可以看到串口助手成功接收到了 green 、yellow、red,这些数据。

我们的三个小灯也打开了。(我的小绿灯不是很亮,用旧了,嘻嘻)

再次发送关键词即可关对应的灯。当然,一次发送 「green yellow red」,就可以控制三个小灯一起反转。

总结

祝贺大家成功点灯!当然,除了控制灯的开关,蓝牙串口还可以应用于更广泛的场景,如个人电子设备、智能家居控制、健康医疗设备等等。随着技术的不断进步,蓝牙技术将持续演进,并在更多领域发挥作用。希望本文能够为你提供了一个初步的了解,并激发你进一步深入研究和应用蓝牙技术的兴趣。感谢各位看官,love and peace!

另外,想进大厂的同学,一定要好好学算法,这是面试必备的。这里准备了一份 BAT 大佬总结的 LeetCode 刷题宝典,很多人靠它们进了大厂。

刷题 | LeetCode算法刷题神器,看完 BAT 随你挑!

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

推荐阅读:

  • 程序员必备编程资料大全
  • 程序员必备软件资源

欢迎关注我的博客:良许嵌入式教程网,满满都是干货!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1442029.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微软Windows生态是怎么打造成功的?

(1)2015年Windows10:兼容性 我不得不再次佩服一下微软,Windows10是2015年出品的,但是仍然能正常运行绝大多数的Windows95软件,不用做任何的适配修改,连重新编译都不用,运行照样正常。…

AcWing 1224 交换瓶子(简单图论)

[题目概述] 有 N 个瓶子,编号 1∼N,放在架子上。 比如有 5 个瓶子: 2 1 3 5 4 要求每次拿起 2 个瓶子,交换它们的位置。 经过若干次后,使得瓶子的序号为: 1 2 3 4 5 对于这么简单的情况,显然&a…

mysql8.0 正值表达式Regular expressions (sample database classicmodels _No.5)

mysql8.0 正值表达式Regular expressions 准备工作,可以去下载 classicmodels 数据库资源如下 [ 点击:classicmodels] (https://download.csdn.net/download/tomxjc/88685970) 也可以去我的博客资源下载 https://download.csdn.net/download/tomxjc/8…

Eclipse导入maven项目或者创建maven项目时,报错Could not calculate build plan: Plugin

问题&#xff1a;Eclipse导入maven项目或者创建maven项目时,报错Could not calculate build plan: Plugin 1.上述问题大概是项目不能加载此maven插件&#xff0c;在pom文件中添加依赖项 <dependency><groupId>org.apache.maven.plugins</groupId><artifa…

JetpackCompose之状态管理

JetPack Compose系列&#xff08;13&#xff09;—状态管理 State 即&#xff0c;状态。官方的解释是&#xff1a; State in an application is any value that can change over time. And ****event can notify a part of a program that something has happened. 可以这样…

发廊理发店微信小程序展示下单前端静态模板源码

模板描述&#xff1a;剪发小程序前端源码&#xff0c;一共五个页面&#xff0c;包括店铺、理发师、订单、我的等页面 注&#xff1a;该源码是前端静态模板源码&#xff0c;没有后台和API接口

代码随想录算法训练营第四十六天(动态规划篇)|01背包(滚动数组方法)

01背包&#xff08;滚动数组方法&#xff09; 学习资料&#xff1a;代码随想录 (programmercarl.com) 题目链接&#xff08;和上次一样&#xff09;&#xff1a;题目页面 (kamacoder.com) 思路 使用一维滚动数组代替二维数组。二维数组的解法记录在&#xff1a;代码随想录算…

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

题目描述 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 题目示例 输入&#xff1a;inorder [9,3,15,20,7], postorder [9,15,7,20,3] 输出&a…

嵌入式学习之Linux入门篇笔记——18,makefile基本语法(下)

配套视频学习链接&#xff1a;http://【【北京迅为】嵌入式学习之Linux入门篇】 https://www.bilibili.com/video/BV1M7411m7wT/?p4&share_sourcecopy_web&vd_sourcea0ef2c4953d33a9260910aaea45eaec8 1.wildcard 函数 格式&#xff1a;$&#xff08;wildcard PAT…

Ivanti Pulse Connect Secure VPN SSRF(CVE-2023-46805)漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

maven-install-plugin:2.4:install (default-cli) on project ability-dispatch:

IDEA&#xff0c;instal时报错 &#xff0c;错误 信息如下&#xff1a; Failed to execute goal org.apache.maven.plugins:maven-install-plugin:2.4:install (default-cli) on project ability-dispatch: The packaging for this project did not assign a file to the buil…

使用 devc++ 开发 easyx 实现 Direct2D 交互

代码为 codebus 另一先生的 文案 EasyX 的三种绘图抗锯齿方法 - CodeBus 这里移植到 devc 移植操作如下&#xff1a; 调用dev 的链接库方式&#xff1a; project -> project option -> 如图所示 稍作修改的代码。 #include <graphics.h> #include <d2d1.…

Onerugged三防平板厂家丨三年质保承诺丨三防平板PAD

行业领先产品——Onerugged三防平板。凭借着十年的经验&#xff0c;我们深知终端设备在各个行业中的重要性&#xff0c;因此致力于为用户提供高可靠性的解决方案。 Onerugged三防平板以其卓越的性能和全方位的保护功能&#xff0c;在市场上脱颖而出。首先&#xff0c;它拥有IP…

路由引入路由过滤排错

目录 排错网络拓扑图 排错需求 故障排错 故障一 故障二 故障三 排错网络拓扑图 排错需求 按照图示配置 IP 地址&#xff0c;总部和分支 A、分支 B 各自使用 loopback 口模拟业务网段公司业务流分为 A 流和 B 流&#xff0c;网段如图所示总部内部配置 OSPF 互通&#xff0…

Linux笔记之expect和bash脚本监听输出并在匹配到指定字符串时发送中断信号

Linux笔记之expect和bash脚本监听输出并在匹配到指定字符串时发送中断信号 code review! 文章目录 Linux笔记之expect和bash脚本监听输出并在匹配到指定字符串时发送中断信号1.expect2.bash 1.expect 在Expect脚本中&#xff0c;你可以使用expect来监听程序输出&#xff0c;…

[红日靶机渗透] ATKCK红队评估实战靶场三

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【ATK&CK红队评估实战靶场】 【VulnHub靶场复现】【面试分析】 &#x1f…

K210如何下载程序

一、打开资料包里提供的K-Flash程序烧录软件 二、选择串口 三、选择波特率 四、选择In-Chip&#xff0c;烧录到Flash芯片里面&#xff0c;重新上电还会运行程序 五、如果选择In - Memory&#xff0c;这次可以运行&#xff0c;但下次重新上电就不会保持这次的程序了。 六、选择固…

第三百一十回

我们在上一章回中介绍了"再谈ListView中的分隔线"&#xff0c;本章回中将介绍showMenu的用法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在第一百六十三回中介绍了showMenu相关的内容&#xff0c;它主要用来显示移动PopupMenu在页面中的位置…

C语言每日一题(52)单值二叉树

力扣网 965 单值二叉树 题目描述 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;[1,1,1,1,1,null,1] 输出&#xff1a;t…

【黑马程序员】程序的内存模型

文章目录 内存分区模型分区意义代码区全局区特点代码示例 栈区特点代码示例 堆区特点代码示例 new 操作符 20240209 内存分区模型 分区意义 不同区域存放的数据&#xff0c;赋予不同的生命周期&#xff0c;给我们更大的灵活编程 代码区 处于程序未执行之前 程序编译后生成的…