esp32笔记[6]-蓝牙(BLE)控制小灯
摘要
基于esp32实现低功耗蓝牙(BLE)通信,通过BLE控制板载小灯亮灭.
平台信息
- 主控:ESP32 (注意:ESP32-S2 没有蓝牙)
- LED:GPIO2(高电平有效)
- 开发平台:ArduinoIDE
ESP32 BLE
[https://www.521u.com/read/1706805994698373180.html]
[https://www.jianshu.com/p/31cbfdda362c]
ESP32:蓝牙BLE控制M3508电机
Getting Started with Bluetooth Low Energy
[https://github.com/HuXioAn/ESP32-M3508-BLE]
下面简要介绍几个蓝牙BLE概念。
- GAP
GAP定义了设备的广播行为,例如手机可以扫描到很多蓝牙BLE设备便是靠GAP。GAP把设备分成两种:中心设备(Central)、外围设备(Peripheral),外围设备对外不断广播,中心设备扫描、接收广播。发现后进而建立连接,再利用下文的GATT协议进行数据传输。
举例来说,我们的手机往往扮演中心设备的角色,而智能手环、智能家居、蓝牙耳机、电动牙刷等设备则是外围设备。
- GATT
利用GAP发现并连接相应设备后,就可以开始传输数据了。蓝牙BLE的数据传输建立在GATT协议上,它定义了BLE设备之间如何传输数据。GATT把设备分为Client和Server,其中命令与请求由Client主动发起,Server被动接受。
请注意:GATT的Client、Server身份与GAP的中心、外围设备没有任何关系,它们可以任意搭配,甚至可以既是Server又是Client。
GATT Server的数据层级结构图:
BLE发送长度限制: 20 Byte
一般限制长度会变成20,主要原因:core spec里面定义了ATT的默认MTU为23个bytes,除去ATT的opcode一个字节以及ATT的handle 2个字节之后,剩下的20个字节便是留给GATT的了。考虑到有些Bluetooth smart设备功能弱小,不敢太奢侈的使用内存空间,因此core spec规定每一个设备都必须支持MTU为23。在两个设备连接初期,大家都像新交的朋友一样,不知对方底细,因此严格的按照套路来走,即最多一次发20个字节,是最保险的。由于ATT的最大长度为512byte,因此一般认为MTU的最大长度为512个byte就够了。所以ATT的MTU的最大长度可视为512个bytes。BLE设备是开启notify之后,会自动发数据出来给客户端的,这些数据的长度有长有短,代表着不同的含义(比如说电池电量、采集的数据等)。
实现
代码
/*
功能:
- 测试蓝牙控制
硬件信息:
- esp32
- WS2812-DIN:GPIO38
- LED:GPIO2(HIGH)
依赖:
- Adafruit_NeoPixel
- ESP32 BLE Arduino
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
#define WS2812_DIN 38
#define PIN WS2812_DIN // On Trinket or Gemma, suggest changing this to 1
//#define WS2812 1 //板载WS2812
#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels
#define LED 2
/*开始全局变量*/
//简短消息结构体
typedef struct _frame_type3{
uint8_t header;//0x3C,<
uint8_t type;//'1':命令,'2':数据
uint16_t frame_id;//帧序列号
uint8_t payload[32];//简短消息
uint16_t total_frame;//分包总数
uint8_t footer;//0x3E,>
}frame_type3;//39 Byte
frame_type3 frame_temp;//待分类
frame_type3 frame_cmd;
frame_type3 frame_data[1024];
//串口接收相关
uint16_t usart_buffer_pos=0;
uint8_t usart_buffer[256];
//WS2812
#ifdef WS2812
Adafruit_NeoPixel pixels(1, PIN, NEO_GRB + NEO_KHZ800);
#endif
//LED
uint8_t led_status=0;//0:off,1:on
/*结束全局变量*/
/*开始函数原型*/
void ws2812(uint8_t red,uint8_t green,uint8_t blue);
void ws2812_decode(uint8_t * cmd);
void cmd_decode(uint8_t *cmds);
void move_forward(uint8_t time_second);//保持前进second秒
void move_backward(uint8_t time_second);//保持后退second秒
void move_left(uint8_t time_second);//保持左转second秒
void move_right(uint8_t time_second);//保持右转second秒
void process_usart_buffer(void);//处理串口接收数据
void led_toggle(void);//切换LED灯状态
void led_decode(uint8_t * cmd);//解析led相关命令
/*结束函数原型*/
#define BLE 1
#define DEBUG 1
uint16_t ble_rx_buffer_pos = 0;
uint8_t ble_rx_buffer[256];
uint8_t ble_tx_buffer[256];
#ifdef BLE
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
BLEServer *p_ble_server = NULL;
BLECharacteristic *p_ble_tx_characteristic;
bool device_connected = false;
bool old_device_connected = false;
class BLE_Server_Callbacks: public BLEServerCallbacks {
//函数(方法)名称不能修改
void onConnect(BLEServer* p_ble_server) {
device_connected = true;
#ifdef DEBUG
Serial.printf("BLE Connected!\n");
#endif
};
//函数(方法)名称不能修改
void onDisconnect(BLEServer* p_ble_server) {
device_connected = false;
#ifdef DEBUG
Serial.printf("BLE disconnected!\n");
#endif
}
};
class BLE_Callbacks: public BLECharacteristicCallbacks {
//函数(方法)名称不能修改
void onWrite(BLECharacteristic *p_ble_characteristic) {
std::string ble_rx_value = p_ble_characteristic->getValue();
if (ble_rx_value.length() > 0) {
//复制接收区到缓存区
memcpy((char*)(ble_rx_buffer + ble_rx_buffer_pos),ble_rx_value.data(),ble_rx_value.length());
ble_rx_buffer_pos += ble_rx_value.length();
#ifdef DEBUG
Serial.printf("BLE Rec:");
for (int i = 0; i < ble_rx_value.length(); i++){
Serial.printf("%c",ble_rx_value.data()[i]);
}
Serial.printf("\n");
#endif
//判断缓存区溢出
if(ble_rx_buffer_pos > 256){
memset(ble_rx_buffer,0,256);
}
//解析命令
uint16_t _left_=0;
uint16_t _right_=0;
//-寻找最近的 <
char* leftPtr = strchr((char*)ble_rx_buffer, '<');
if (leftPtr != nullptr) {
//_left_ = static_cast<uint16_t>(leftPtr - (char*)ble_rx_buffer);
}
//-寻找最近的 >
char* rightPtr = strchr((char*)ble_rx_buffer, '>');
if (rightPtr != nullptr) {
//_right_ = static_cast<uint16_t>(rightPtr - (char*)ble_rx_buffer);
}
#ifdef DEBUG
Serial.printf("BLE rx buffer:");
for (int i = 0; i < ble_rx_buffer_pos; i++) {
Serial.printf("%c", ble_rx_buffer[i]);
}
Serial.printf("\n");
#endif
memcpy(usart_buffer,ble_rx_buffer,256);
process_usart_buffer();
} //end if (ble_rx_value.length() > 0)
}//end onWrite
};
#endif
void setup() {
//初始化串行监视器以进行调试
Serial.begin(115200);
#ifdef WS2812
//初始化WS2812
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
//配置ws2812初始颜色
pixels.clear(); // Set all pixel colors to 'off'
pixels.setPixelColor(0, pixels.Color(150, 0, 0));
pixels.show(); // Send the updated pixel colors to the hardware.
delay(DELAYVAL); // Pause before next pass through loop
#endif
//LED
#ifdef LED
led_toggle();
#endif
//蓝牙ble相关
#ifdef BLE
//-创建ble设备
BLEDevice::init("ESP BLE Uart");
//-创建ble服务端
p_ble_server = BLEDevice::createServer();
p_ble_server->setCallbacks(new BLE_Server_Callbacks());
//-创建ble服务
BLEService *p_ble_service = p_ble_server->createService(SERVICE_UUID);
//-创建ble特征
p_ble_tx_characteristic = p_ble_service->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY
);
p_ble_tx_characteristic->addDescriptor(new BLE2902());
BLECharacteristic * p_ble_rx_characteristic = p_ble_service->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE
);
p_ble_rx_characteristic->setCallbacks(new BLE_Callbacks());
//-打开ble服务
p_ble_service->start();
//-ble开始广播
p_ble_server->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
#endif
}
void loop() {
//接收数据
if(Serial.available()>0){
while(Serial.available()>0){
usart_buffer[usart_buffer_pos]=Serial.read();
delay(2);
usart_buffer_pos++;
}
#ifdef DEBUG
//转发数据
Serial.printf("Rec:");
for (int i = 0; i < usart_buffer_pos; i++) {
Serial.printf("%c", usart_buffer[i]);
}
Serial.printf("\n");
#endif
//解析命令
//cmd_decode(usart_buffer);
process_usart_buffer();
//复制到蓝牙发送缓存区
memcpy(ble_tx_buffer,usart_buffer,usart_buffer_pos+1);
#ifdef DEBUG
Serial.printf("BLE buffer:");
for (int i = 0; i < usart_buffer_pos; i++) {
Serial.printf("%c", ble_tx_buffer[i]);
}
Serial.printf("\n");
#endif
//重置接收
usart_buffer_pos=0;
memset(usart_buffer,0,256);
}
#ifdef BLE
//设备连接上了
if(device_connected){
//发送数据
if(strlen((char*)ble_tx_buffer) != 0){
//一次最多发送20 Bytes
p_ble_tx_characteristic->setValue((uint8_t *)ble_tx_buffer, 20);
p_ble_tx_characteristic->notify();
delay(10);
p_ble_tx_characteristic->setValue((uint8_t *)(ble_tx_buffer+20), 19);
p_ble_tx_characteristic->notify();
delay(10);
}
else{
//心跳包
p_ble_tx_characteristic->setValue((uint8_t *)"ACK\n", 5);
p_ble_tx_characteristic->notify();
delay(5000);
}
//清空缓存区
memset(ble_tx_buffer,0,256);
}
//断开连接
if (!device_connected && old_device_connected) {
delay(500); //给蓝牙协议栈足够的时间准备
p_ble_server->startAdvertising(); // restart advertising
Serial.printf("ble restart advertising...\n");
old_device_connected = device_connected;
}
#endif
}
/*
@功能:设置ws2812颜色
*/
void ws2812(uint8_t red, uint8_t green, uint8_t blue) {
#ifdef DEBUG
Serial.printf("Setting ws2812 color: (%d, %d, %d)\n", red, green, blue);
#endif
#ifdef WS2812
pixels.clear(); // Set all pixel colors to 'off'
pixels.setPixelColor(0, pixels.Color(red, green, blue));
pixels.show(); // Send the updated pixel colors to the hardware.
delay(DELAYVAL); // Pause before next pass through loop
#endif
}
/*
@功能:分离第一个有效命令
*/
void cmd_decode(uint8_t *cmds) {
char *semicolon_pos = strstr((char *)cmds, ";");
if (semicolon_pos != NULL) {
char cmd_length = semicolon_pos - (char*)cmds + 1;
uint8_t cmd[cmd_length];
memcpy(cmd, cmds, cmd_length);
cmd[cmd_length - 1] = '\0';
if (strncmp((char *)cmd, "ws2812", 6) == 0) {
ws2812_decode(cmd);
}
else if(strncmp((char *)cmd,"forward",7)==0){
move_forward(1);
}
else if(strncmp((char *)cmd,"backward",8)==0){
move_backward(1);
}
else if(strncmp((char *)cmd,"left",4)==0){
move_left(1);
}
else if(strncmp((char *)cmd,"right",5)==0){
move_right(1);
}
else if(strncmp((char *)cmd,"led",3)==0){
led_decode(cmd);
}
}
}
/*
@功能:解析ws2812命令
*/
void ws2812_decode(uint8_t *cmd) {
uint8_t red, green, blue;
if (strncmp((char *)cmd, "ws2812 red", 11) == 0) {
red = 255;
green = 0;
blue = 0;
} else if (strncmp((char *)cmd, "ws2812 green", 13) == 0) {
red = 0;
green = 255;
blue = 0;
} else if (strncmp((char *)cmd, "ws2812 blue", 12) == 0) {
red = 0;
green = 0;
blue = 255;
} else if (strncmp((char *)cmd, "ws2812 white", 13) == 0) {
red = 255;
green = 255;
blue = 255;
} else if (strncmp((char *)cmd, "ws2812 #", 8) == 0) {
sscanf((char *)cmd, "ws2812 #%2hhx%2hhx%2hhx;", &red, &green, &blue);
} else {
// 无效命令
return;
}
ws2812(red, green, blue);
}
/*
@功能:保持前进second秒
*/
void move_forward(uint8_t time_second){
#ifdef DEBUG
Serial.printf("move_forward\n");
#endif
}
/*
@功能:保持后退second秒
*/
void move_backward(uint8_t time_second){
#ifdef DEBUG
Serial.printf("move_backward\n");
#endif
}
/*
@功能:保持左转second秒
*/
void move_left(uint8_t time_second){
#ifdef DEBUG
Serial.printf("move_left\n");
#endif
}
/*
@功能:保持右转second秒
*/
void move_right(uint8_t time_second){
#ifdef DEBUG
Serial.printf("move_right\n");
#endif
}
/*
@功能:判断命令/数据
*/
void process_usart_buffer() {
// 复制usart_buffer中前usart_buffer_pos个字节到frame_temp
memcpy(&frame_temp, usart_buffer, sizeof(frame_type3));
// 检查header和footer是否匹配
if (frame_temp.header == 0x3C && frame_temp.footer == 0x3E) {
// 判断type
if (frame_temp.type == '1') {
// 如果是命令,则复制frame_temp到frame_cmd
memcpy(&frame_cmd, &frame_temp, sizeof(frame_type3));
cmd_decode(frame_cmd.payload);
//清空BLE缓存
#ifdef BLE
memset(ble_rx_buffer,0,256);
ble_rx_buffer_pos = 0;
#ifdef DEBUG
Serial.printf("Process:");
for (int i = 0; i < 255; i++) {
Serial.printf("%c", usart_buffer[i]);
}
Serial.printf("\n");
#endif //DEBUG
//重置接收缓存
usart_buffer_pos=0;
memset(usart_buffer,0,256);
#endif //BLE
} else if (frame_temp.type == '2') {
// 如果是数据,则将frame_temp的数据追加到frame_data后面
memcpy(&frame_data[frame_temp.frame_id], &frame_temp, sizeof(frame_type3));
//清空BLE缓存
#ifdef BLE
memset(ble_rx_buffer,0,256);
ble_rx_buffer_pos = 0;
#ifdef DEBUG
Serial.printf("Process:");
for (int i = 0; i < 255; i++) {
Serial.printf("%c", usart_buffer[i]);
}
Serial.printf("\n");
#endif //DEBUG
//重置接收缓存
usart_buffer_pos=0;
memset(usart_buffer,0,256);
#endif //BLE
}
}
}
/*
@功能:切换led状态
*/
void led_toggle(void){
pinMode(LED,OUTPUT);
led_status=!led_status;
digitalWrite(LED,led_status);
}
/*
@功能:解析led相关命令
*/
void led_decode(uint8_t * cmd){
if (strstr((char *)cmd, "toggle") != NULL) {
led_toggle();
}
else if(strstr((char *)cmd, "on") != NULL){
pinMode(LED,OUTPUT);
digitalWrite(LED,HIGH);
}
else if(strstr((char *)cmd, "off") != NULL){
pinMode(LED,OUTPUT);
digitalWrite(LED,LOW);
}
}
构造命令帧
/*
命令解析
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
uint8_t index_of_semicolon=0;
//串口接收相关
uint16_t usart_buffer_pos=0;
uint8_t usart_buffer[256];
//简短消息结构体
typedef struct _frame_type3{
uint8_t header;//0x3C,<
uint8_t type;//'1':命令,'2':数据
uint16_t frame_id;//帧序列号
uint8_t payload[32];//简短消息
uint16_t total_frame;//分包总数
uint8_t footer;//0x3E,>
}frame_type3;//39 Byte
frame_type3 frame_temp; // 待分类
frame_type3 frame_cmd;
frame_type3 frame_data[1024];
#define MY_STRING "led off;"//需要生成的命令
//#define my_string "backward;"
//#define my_string "ws2812 #000000;"
void ws2812(uint8_t red,uint8_t green,uint8_t blue);
void ws2812_decode(uint8_t * cmd);
void cmd_decode(uint8_t *cmds);
void move_forward(uint8_t time_second);//保持前进second秒
void move_backward(uint8_t time_second);//保持后退second秒
void move_left(uint8_t time_second);//保持左转second秒
void move_right(uint8_t time_second);//保持右转second秒
void process_usart_buffer(void);//处理串口接收数据
void create_command_frame(void);//debug 封装命令进入帧
int main(void) {
//cmd_decode(my_string);
create_command_frame();
printf("frame:");
for (int i = 0; i < usart_buffer_pos; i++) {
printf("%c", usart_buffer[i]);
}
printf("\n");
printf("frame_hex:");
for (int i = 0; i < usart_buffer_pos; i++) {
printf("%02x ", usart_buffer[i]);
}
printf("\n");
process_usart_buffer();
return 0;
}
void ws2812(uint8_t red, uint8_t green, uint8_t blue) {
// 执行ws2812设置颜色命令的函数实现
// 这里只是一个示例,您需要根据具体的硬件和库函数进行相应的操作
// 以下代码仅供参考
// pixels.setPixelColor(0, pixels.Color(red, green, blue)); // red:0~255,green:0~255,blue:0~255
printf("Setting ws2812 color: (%d, %d, %d)\n", red, green, blue);
}
void cmd_decode(uint8_t *cmds) {
// 分离第一个有效命令的函数实现
// 这里只是一个示例,您需要根据具体的命令格式进行分离
// 以下代码仅供参考
uint8_t *semicolon_pos = strstr((char *)cmds, ";");
if (semicolon_pos != NULL) {
uint8_t cmd_length = semicolon_pos - cmds + 1;
uint8_t cmd[cmd_length];
memcpy(cmd, cmds, cmd_length);
cmd[cmd_length - 1] = '\0';
if (strncmp((char *)cmd, "ws2812", 6) == 0) {
ws2812_decode(cmd);
}
else if(strncmp((char *)cmd,"forward",7)==0){
move_forward(1);
}
else if(strncmp((char *)cmd,"backward",8)==0){
move_backward(1);
}
else if(strncmp((char *)cmd,"left",4)==0){
move_left(1);
}
else if(strncmp((char *)cmd,"right",5)==0){
move_right(1);
}
}
}
void ws2812_decode(uint8_t *cmd) {
// 解析ws2812命令的函数实现
// 这里只是一个示例,您需要根据具体的命令格式进行解析
// 以下代码仅供参考
uint8_t red, green, blue;
if (strncmp((char *)cmd, "ws2812 red", 11) == 0) {
red = 255;
green = 0;
blue = 0;
} else if (strncmp((char *)cmd, "ws2812 green", 13) == 0) {
red = 0;
green = 255;
blue = 0;
} else if (strncmp((char *)cmd, "ws2812 blue", 12) == 0) {
red = 0;
green = 0;
blue = 255;
} else if (strncmp((char *)cmd, "ws2812 white", 13) == 0) {
red = 255;
green = 255;
blue = 255;
} else if (strncmp((char *)cmd, "ws2812 #", 8) == 0) {
sscanf((char *)cmd, "ws2812 #%2hhx%2hhx%2hhx;", &red, &green, &blue);
} else {
// 无效命令
return;
}
ws2812(red, green, blue);
}
void move_forward(uint8_t time_second){
printf("move_forward\n");
}
void move_backward(uint8_t time_second){
printf("move_backward\n");
}
void move_left(uint8_t time_second){
printf("move_left\n");
}
void move_right(uint8_t time_second){
printf("move_right\n");
}
void process_usart_buffer() {
// 复制usart_buffer中前usart_buffer_pos个字节到frame_temp
memcpy(&frame_temp, usart_buffer, sizeof(frame_type3));
// 检查header和footer是否匹配
if (frame_temp.header == 0x3C && frame_temp.footer == 0x3E) {
// 判断type
if (frame_temp.type == '1') {
// 如果是命令,则复制frame_temp到frame_cmd
memcpy(&frame_cmd, &frame_temp, sizeof(frame_type3));
cmd_decode(frame_cmd.payload);
} else if (frame_temp.type == '2') {
// 如果是数据,则将frame_temp的数据追加到frame_data后面
memcpy(&frame_data[frame_temp.frame_id], &frame_temp, sizeof(frame_type3));
}
}
}
void create_command_frame(void) {
frame_temp.header = 0x3C;
frame_temp.type = '1';
frame_temp.frame_id = 0; // 设置帧序列号
frame_temp.total_frame = 1; // 设置分包总数
frame_temp.footer = 0x3E;
// 将MY_STRING封装进frame_temp的payload中
memset(frame_temp.payload, 0, sizeof(frame_temp.payload));
strncpy((char*)frame_temp.payload, MY_STRING, sizeof(frame_temp.payload) - 1);
// 将frame_temp拷贝到usart_buffer中
memcpy(usart_buffer, &frame_temp, sizeof(frame_temp));
usart_buffer_pos = sizeof(frame_temp);
}
效果
第一次发送 | 效果 | 第二次发送 | 效果 |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」