导言:
在现代智能家居系统中,远程控制设备变得越来越普遍和重要。本文将介绍如何利用STM32F103C8T6单片机和蓝牙模块HC-05实现远程控制LED灯的功能。通过这个简单的项目,可以学会如何将嵌入式系统与蓝牙通信技术相结合,实现远程控制的应用。
目录
准备工作:
在开始之前,确保已经准备好以下材料:
- STM32F103C8T6开发板
- HC-05蓝牙模块
- LED灯
- 杜邦线等连接线
- USB转串口模块(用于调试)
硬件设计:
本次设计使用HC-05(JDY-31)无线蓝牙模块实现单片机和手机的无线通信,将其正常工作的频段2.4 GHz ISM,GFSK作为它的调制方式。主控芯片STM32F103 C8T6单片机通过串口连接HC-05,安卓手机端自带蓝牙,通过手机App与单片机蓝牙设备建立配对,蓝牙模块将接收的数据传送给单片机,单片机处理后控制电机的运转和桶盖的开关。工作原理如图3所示。
图3蓝牙工作原理
HC-05蓝牙串口模块介绍:
HC-05是一款常用的蓝牙串口模块,用于在微控制器和其他设备之间建立蓝牙串口通信连接。下面我将详细介绍HC-05蓝牙模块的接口设计,以便将其与STM32F103C8T6微控制器进行通信。
HC-05蓝牙串口模块
引脚:
标号 | PIN | 引脚说明 |
---|---|---|
1 | STATE | 状态引出引脚(未连接时输出低电平,连接时输出高电平) |
2 | RXD | 接收端 |
3 | TXD | 发送端 |
4 | GND | 模块供电负极 |
5 | VCC | 模块供电正极 |
6 | EN | 使能端,需要进入命令模式时接3.3V |
注:或者也可以直接去优信买JDY-31模块,相比HC05更加便宜,使用也没什么区别并且资料全套。
手机蓝牙APP:
这几个都可以,手机应用商店直接搜索就行。
物理连接:
电源供应:HC-05通常需要3.3V电源供应。你可以使用STM32F103C8T6的一块3.3V输出引脚连接到HC-05的VCC引脚,或者使用一个3.3V的稳压芯片。
串口通信:HC-05通过串口与STM32通信。它包含了TX(发送)和RX(接收)引脚,分别用于发送和接收数据。你可以将HC-05的TX引脚连接到STM32的一个USART接收引脚(比如USART1的RX(PA10)引脚),并将HC-05的RX引脚连接到STM32的一个USART发送引脚(比如USART1的TX(PA9)引脚)。
接地:HC-05的GND与STM32的GND相连接
通信协议:
波特率设置:HC-05支持多种波特率,通常默认波特率为9600bps。你可以通过AT指令将其更改为其他波特率,以便与STM32的USART通信波特率匹配。
数据格式:通常情况下,HC-05使用8位数据位、无校验位和1位停止位的数据格式。
AT指令:
HC-05也可以使用AT指令进行配置。在配置之前,首先需要让模块进入配置模式。一般HC-05有一个小按钮。按住小按钮再给蓝牙模块上电,蓝牙模块进入配置模式,此时模块上自带的LED会慢速闪烁。进入配置模式后,就可以用AT指令来配置我们的HC-05了。配置时,用USB转TTL连接HC-05,用串口调试助手发送AT指令进行配置。需要注意的是,HC-05配置模式的波特率固定为38400,如果你给HC-05发送指令,没有收到回复,记得检查一下串口调试助手的波特率是否正确。下面列举一些配置时常用的AT指令
AT
检查HC-05模块连接是否正常,HC-05收到后会回复“OK”
AT+NAME=名字
配置HC-05的名字,配置成功后会返回“OK”
AT+NAME?
询问HC-05的名字。发送后会收到“+NAME:“名字””,换行加“OK”
AT+PSWD=密码
配置HC-05密码,配对时需要用到。配置成功后,会收到“OK”
AT+PSWD?
询问HC-05配对密码。发送后会收到“+PSWD:991102”,换行加“OK”
AT+UART=波特率,停止位,校验
设置HC-05的波特率,其中停止位0表示一位停止位,为1表示两位停止位。校验位为0表示无校验,为1表示奇校验,为2表示偶校验。比如设置115200的波特率,一位停止位,无校验。发送“AT+UART=115200,0,0”即可。配置成功后会返回“OK”
AT+UART?
询问HC-05波特率。发送后会收到“+UART:波特率,停止位,校验”,换行加“OK”
配置完成功后,断电重新上电,HC-05按照配置好的名字,配对密码和波特率开始工作。此时LED快闪。
蓝牙测试软件:
推荐一个特别好用的蓝牙测试软件,将蓝牙模块与HC05连接好后,插上电脑可以一键直接获得当前这个蓝牙模块的信息,需要的可以直接去我的资源里自取。
资源链接:
推荐资料:
软件设计:
手机APP和蓝牙通过串口接发通信:
- 机寻找蓝牙,并填写配对码
- SPP蓝牙串口连接对应蓝牙
- 发送数据,串口接收,串口发送,手机接收
使用HC-05控制mcu
stm32f103c8t6自带一个led灯,使用PC13引脚就行了,
切记尽量避免使用PB3、PB4,因为PB3和PB4在默认情况下是做JTAG调试用的。如果需要将其当普通GPIO使用,需要关闭JTAG调试功能,否则会发现普通的GPIO初始化程序无法正常使用PB3和PB4
代码实现:
led.c
#include "stm32f10x.h" // Device header #include "LED.h" // Device header void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, ENABLE); //使能PA,PD端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8 GPIO_ResetBits(GPIOA,GPIO_Pin_8); //PA.8 输出高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED1-->PD.2 端口配置, 推挽输出 GPIO_Init(GPIOB, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz GPIO_SetBits(GPIOB,GPIO_Pin_8); //PD.2 输出高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //LED1-->PD.2 端口配置, 推挽输出 GPIO_Init(GPIOB, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz GPIO_SetBits(GPIOB,GPIO_Pin_9); //PD.2 输出高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //LED1-->PD.2 端口配置, 推挽输出 GPIO_Init(GPIOC, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz GPIO_SetBits(GPIOC,GPIO_Pin_13); //PD.2 输出高 }
led.h
#ifndef __LED_H #define __LED_H #include "sys.h" // Device header #define LED0 PCout(13) // PA8 #define LED1 PBout(8) // PB8 #define LED2 PBout(9) // PB9 #define BUZ PAout(8) // PA8 void LED_Init(void);//初始化 #endif
usart1.c
#include "stm32f10x.h" // 包含 STM32F10x 系列芯片的头文件 #include <stdio.h> // 包含标准输入输出头文件 #include <stdarg.h> // 包含可变参数列表的头文件 #include "serial.h" // 包含串口相关的头文件 uint8_t Serial_RxData; // 定义一个无符号8位整型变量 `Serial_RxData`,用于存储串口接收到的数据 uint8_t Serial_RxFlag; // 定义一个无符号8位整型变量 `Serial_RxFlag`,用于表示串口接收标志位 // 串口初始化函数 void Serial_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 使能 USART1 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能 GPIOA 时钟 GPIO_InitTypeDef GPIO_InitStructure; // 定义一个 GPIO 初始化结构体变量 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // GPIO 模式为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // GPIO 引脚为 PA9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO 速度为 50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // GPIO 模式为上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // GPIO 引脚为 PA10 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO 速度为 50MHz GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA USART_InitTypeDef USART_InitStructure; // 定义一个 USART 初始化结构体变量 USART_InitStructure.USART_BaudRate = 9600; // 波特率为 9600 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控制 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 收发模式 USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1 个停止位 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8 位数据位 USART_Init(USART1, &USART_InitStructure); // 初始化 USART1 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能 USART1 接收中断 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置 NVIC 中断优先级 NVIC_InitTypeDef NVIC_InitStructure; // 定义一个 NVIC 初始化结构体变量 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // USART1 中断通道 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级为 1 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级为 1 NVIC_Init(&NVIC_InitStructure); // 初始化 NVIC USART_Cmd(USART1, ENABLE); // 使能 USART1 } // 发送一个字节数据到串口 void Serial_SendByte(uint8_t Byte) { USART_SendData(USART1, Byte); // 发送数据到 USART1 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成 } // 发送一串数据到串口 void Serial_SendArray(uint8_t *Array, uint16_t Length) { uint16_t i; for (i = 0; i < Length; i++) // 遍历数组 { Serial_SendByte(Array[i]); // 逐个发送数组中的数据 } } // 发送一个字符串到串口 void Serial_SendString(char *String) { uint8_t i; for (i = 0; String[i] != '\0'; i++) // 遍历字符串 { Serial_SendByte(String[i]); // 逐个发送字符串中的字符 } } // 计算 X 的 Y 次方 uint32_t Serial_Pow(uint32_t X, uint32_t Y) { uint32_t Result = 1; while (Y--) // Y 次方计算 { Result *= X; } return Result; } // 发送一个数字到串口,指定长度 void Serial_SendNumber(uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i++) // 遍历指定长度 { Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); // 发送数字的每一位 } } // 重定向标准输出函数 int fputc(int ch, FILE *f) { Serial_SendByte(ch); // 发送字符到串口 return ch; } // 格式化发送字符串到串口 void Serial_Printf(char *format, ...) { char String[100]; // 定义一个字符数组用于存储格式化后的字符串 va_list arg; va_start(arg, format); // 初始化可变参数列表 vsprintf(String, format, arg); // 格式化字符串 va_end(arg); // 结束可变参数列表 Serial_SendString(String); // 发送格式化后的字符串到串口 } // 获取串口接收标志位 uint8_t Serial_GetRxFlag(void) { if (Serial_RxFlag == 1) // 如果串口接收标志位为 1 { Serial_RxFlag = 0; // 清零串口接收标志位 return 1; // 返回 1 } return 0; // 否则返回 0 } // 获取串口接收到的数据 uint8_t Serial_GetRxData(void) { return Serial_RxData; // 返回串口接收到的数据 } // USART1 中断处理函数 void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) // 如果 USART1 接收中断标志位为 SET { Serial_RxData = USART_ReceiveData(USART1); // 获取接收到的数据 Serial_SendByte(Serial_RxData); // 发送接收到的数据到串口 Serial_RxFlag = 1; // 置位串口接收标志位 USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除 USART1 接收中断标志位 } }
#include "stm32f10x.h"
:包含 STM32F10x 系列芯片的头文件。#include <stdio.h>
:包含标准输入输出头文件。#include <stdarg.h>
:包含可变参数列表的头文件。#include "serial.h"
:包含串口相关的头文件。uint8_t Serial_RxData;
:定义一个无符号8位整型变量Serial_RxData
,用于存储串口接收到的数据。uint8_t Serial_RxFlag;
:定义一个无符号8位整型变量Serial_RxFlag
,用于表示串口接收标志位。void Serial_Init(void)
:串口初始化函数。void Serial_SendByte(uint8_t Byte)
:发送一个字节数据到串口的函数。void Serial_SendArray(uint8_t *Array, uint16_t Length)
:发送一串数据到串口的函数。void Serial_SendString(char *String)
:发送一个字符串到串口的函数。uint32_t Serial_Pow(uint32_t X, uint32_t Y)
:计算 X 的 Y 次方的函数。void Serial_SendNumber(uint32_t Number, uint8_t Length)
:发送一个数字到串口,指定长度的函数。int fputc(int ch, FILE *f)
:重定向标准输出函数。void Serial_Printf(char *format, ...)
:格式化发送字符串到串口的函数。uint8_t Serial_GetRxFlag(void)
:获取串口接收标志位的函数。uint8_t Serial_GetRxData(void)
:获取串口接收到的数据的函数。void USART1_IRQHandler(void)
:USART1 中断处理函数。
usart2.h
#ifndef __SERIAL_H #define __SERIAL_H #include <stdio.h> #include "LED.h" // Device header #include "sys.h" // Device header #include "HC05.h" // Device header #include "Delay.h" void Serial_Init(void); void Serial_SendByte(uint8_t Byte); void Serial_SendArray(uint8_t *Array, uint16_t Length); void Serial_SendString(char *String); void Serial_SendNumber(uint32_t Number, uint8_t Length); void Serial_Printf(char *format, ...); uint8_t Serial_GetRxFlag(void); uint8_t Serial_GetRxData(void); #endif
HC05.c
#include "stm32f10x.h" // Device header #include "Serial.h" #include "HC05.h" // Device header uint8_t RxSTA = 1; // 定义串口接收状态变量,初始为1,表示准备接收数据 char RxData[100] = "None"; // 定义接收数据缓冲区,初始值为"None" void HC05_GetData(char *Buf) { uint32_t count = 0, a = 0; // 定义计数器和数据索引变量 while (count < 10000) // 进入循环等待接收数据 { if (Serial_GetRxFlag() == 1) // 如果串口接收标志为1,表示有数据接收到 { Buf[a] = Serial_GetRxData(); // 将接收到的数据存储在缓冲区Buf中 a ++; // 数据索引自增 count = 0; // 重置计数器 RxSTA = 0; // 将接收状态置为0,表示接收到数据 } count ++; // 计数器自增 } } void HC05_Init() { Serial_Init(); // 初始化串口通信 } void HC05_EnterAT() { GPIO_SetBits(GPIOA, GPIO_Pin_0); // 将GPIOA的第0引脚置为高电平,进入AT模式 } void HC05_ExitAT() { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 将GPIOA的第0引脚置为低电平,退出AT模式 } void HC05_SendString(char *Buf) { Serial_Printf(Buf); // 向HC05模块发送字符串 } void HC05_proc() { HC05_GetData(RxData); // 获取HC05模块接收到的数据 if (RxSTA == 0) // 如果接收状态为0,表示接收到了数据 { OLED_Clear(); // 清空OLED显示屏 OLED_ShowString(1, 1, "RxData:"); // 在OLED上显示"RxData:" OLED_ShowString(2, 1, RxData); // 在OLED上显示接收到的数据 if(strstr((const char*)RxData, "led on") != 0) // 如果接收到的数据包含"led on" { LED0 = 0; // 控制LED0点亮 LED1 = 0; // 控制LED1点亮 BUZ=1; // 控制蜂鸣器响 Delay_ms(100); // 延时100毫秒 BUZ=0; // 关闭蜂鸣器 } if(strstr((const char*)RxData, "led off") != 0) // 如果接收到的数据包含"led off" { LED0 = 1; // 控制LED0熄灭 LED1 = 1; // 控制LED1熄灭 BUZ=1; // 控制蜂鸣器响 Delay_ms(100); // 延时100毫秒 BUZ=0; // 关闭蜂鸣器 } memset(RxData,0,100); // 清空接收数据缓冲区 RxSTA = 1; // 将接收状态置为1,表示准备接收新的数据 } }
HC05.h
#ifndef __HC05_H #define __HC05_H #include "LED.h" // Device header #include "sys.h" // Device header #include "OLED.h" #include "string.h" // Device header extern char RxData[100]; extern uint8_t RxSTA; void HC05_Init(); void HC05_EnterAT(); void HC05_ExitAT(); void HC05_SendString(char *Buf); void HC05_GetData(char *Buf); void HC05_proc(void); #endif
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "HC05.h" #include "sys.h" // Device header #include "serial.h" // Device header #include "string.h" // Device header #include "ESP01.h" #include "usart2.h" int main(void) { OLED_Init(); // 初始化OLED显示屏 LED_Init(); // 初始化LED指示灯 HC05_Init(); // 初始化HC05蓝牙模块 // uart2_init(115200); //串口初始化为115200 // ESP01_Init(); // 初始化ESP01模块 OLED_ShowString(1, 1, "RxData:"); // 在OLED上显示"RxData:" OLED_ShowString(2, 1, RxData); // 在OLED上显示RxData变量内容 BUZ=1; // 控制蜂鸣器响 Delay_ms(100); // 延时100毫秒 BUZ=0; // 关闭蜂鸣器 while (1) { HC05_proc(); // 处理HC05模块接收到的数据 // ESP01_proc(); // 处理ESP01模块接收到的数据 } }
最终实现:
在APP中发送字符串“led on”可以打开LED灯,发送字符串“led off”可以关闭LED灯