小项目:蓝牙模块点亮RGB三色灯
2025-02-08 来源:jianshu
在之前的教程中,我们学习了蓝牙模块的原理,并动手写了驱动,实现了串口的接收和发送。本次我们就来教大家如何使用蓝牙串口控制灯。这是一个简单的示例,展示了如何将蓝牙通信与硬件控制相结合,实现远程控制的功能。你也可以扩展这个示例,添加更多的指令和功能,以满足自己的需求。
1. 源码下载及前置阅读
本文所涉及的源码及安装包如下(由于平台限制,请点击以下链接阅读原文下载):
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-08 | LED | STM32 | USB 转 TTL |
|---|---|---|---|
| VCC | 3.3 | ||
| RXD | A2 | ||
| TXD | A3 | ||
| GND | G | ||
| R | A5 | ||
| Y | A6 | ||
| G | A7 | ||
| GND | G | ||
| A10 | TX | ||
| A9 | RX | ||
| G | GND |
烧录的时候接线如下表,如果不会烧录的话可以看我之前的文章 STM32下载程序的五种方法:https://www.lxlinux.net/e/stm32/five-ways-to-flash-program-to-stm32.html。
| ST-Link V2 | STM32 |
|---|---|
| SWCLK | SWCLK |
| SWDIO | SWDIO |
| GND | GND |
| 3.3V | 3V3 |
接好如下图:
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: %srn', 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('蓝牙控制灯……rn');
while(1)
{
bt_send('bt sendrn');
delay_ms(1000);
}
}
4. 运行过程
将硬件连好,把串口插到电脑 USB 口。
接着我们打开电脑串口软件。设置串口助手波特率 115200(你们不一定要用我这款,随便的串口助手都可以),选择串口号,最后打开串口开始准备接收数据。
这个串口工具接收的是 MCU 串口 1 的数据,也就是 log 。蓝牙接收到数据后,我们使用串口 1 打印到下面的串口助手里。
烧录代码,串口输出如下:

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


到这里,我们就完成了 MCU 通过蓝牙将数据透传到手机 APP(蓝牙助手)。
当然,我们也可以通过手机 APP 向蓝牙发送数据,MCU 接收到透传的数据之后通过串口助手打印在电脑上。
比如我们给蓝牙模块发送数据 green 、yellow、red。

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

我们的三个小灯也打开了。(我的小绿灯不是很亮,用旧了,嘻嘻)
再次发送关键词即可关对应的灯。当然,一次发送 「green yellow red」,就可以控制三个小灯一起反转。

总结
祝贺大家成功点灯!当然,除了控制灯的开关,蓝牙串口还可以应用于更广泛的场景,如个人电子设备、智能家居控制、健康医疗设备等等。随着技术的不断进步,蓝牙技术将持续演进,并在更多领域发挥作用。希望本文能够为你提供了一个初步的了解,并激发你进一步深入研究和应用蓝牙技术的兴趣。
上一篇:MQ-2烟雾传感器详解
下一篇:人体红外感应模块详解
- u-blox推出JODY-B1汽车蓝牙模块 支持多设备同时连接
- 单片机蓝牙模块的使用,以及配置控制舵机的源码
- 意法半导体与高通合作开发的Wi-Fi/蓝牙模块交钥匙方案正式量产及重要应用案例成功落地
- 芯科科技突破性超低功耗Wi-Fi 6和低功耗蓝牙5.4模块加速设备部署
- 基于Nordic nRF54L系列的低功耗蓝牙模块助力下一代物联网应用性能升级
- OBD车载诊断蓝牙模块方案
- 贸泽开售英飞凌CYW20822 AIROC低功耗蓝牙模块,为多项应用提供高效无线连接
- 英飞凌推出低成本低功耗长距离蓝牙模块CYW20822-P4TAI040
- 超低功耗B26蓝牙模块支持自定义连接和广播间隔
- 超智算智能算力中心揭牌暨AI算力设备点亮仪式成功举行
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 蓝牙信道探测技术原理与开发套件实践
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析




