esp32笔记[6]-蓝牙(BLE)控制小灯

发布时间 2023-07-19 22:46:25作者: qsBye

摘要

基于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的数据层级结构图:
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);
}

效果

第一次发送 效果 第二次发送 效果