机械臂之蓝牙代码优化

183 阅读6分钟

前言

这篇是自制机械臂项目的蓝牙代码优化。想尝试做一个机械臂的可以参考我上篇文章: 《如何做个机械臂》
蓝牙代码主要优化的部分是对蓝牙收到'写'特征(Characteristic)的逻辑处理和删除部分不需要的代码,比如自定义广播,长数据的分包处理,数据存储处理等。目前的蓝牙所承载的功能是与网页进行数据交互,所以并不要太过复杂的逻辑控制(比如引入很多FreeRTOS的信号量),写代码我还是觉得越简单越好。完整文件可见github

先看结果

首先看下修改后的性能,现在可以每隔15ms(优化之前是低于200ms会发生阻塞报错)向单片机发送条蓝牙消息而不会发生阻塞。每秒66条的传输,对于后续的引入贝塞尔曲线来优化电机运动姿态应该是足够了。

主要修改内容

修改后的主要业务逻辑代码如下:

case ESP_GATTS_WRITE_EVT: {
        //ESP_LOGI(GATTS_TAG, "蓝牙写入事件 GATT_WRITE_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
        esp_gatt_status_t status = ESP_GATT_OK;

        //ESP_LOGW(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
        //esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);

        // 将接收到的蓝牙数据做处理,提取出想要格式的帧id和帧数据
        unsigned int frameId = 0;
        int frameData[8];
        for(int i = 0; i < param->write.len; i++) {
          // printf("遍历蓝牙%d值: %d", i, param->write.value[i]);
          if(i < 4) {
            frameId = frameId * 16 * 16 + param->write.value[i];
            // ESP_LOGI(GATTS_TAG, "----蓝牙 16进制 转换帧id:  %u", frameId);
          }else {
            frameData[i - 4] = param->write.value[i];
          }
        }
       // printf("frameId%u值: %d, --- %d  \n", frameId, frameData[0], frameData[7]);
        
        // ESP_LOGI(EXAMPLE_TAG, "-----发送can数据指令------");
        emitMsg(frameId, frameData);
        // ESP_LOGI(EXAMPLE_TAG, "-----发送can数据指令结束-----");

        // 客户端的请求是需要需要回复时候
        if(param->write.need_rsp) {
          esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
        }
        break;

功能就是接收到网页传来的信息之后,对信息进行解析,并组装成小米电机需要的帧格式,然后调用TWAI的消息发送函数(这块调用twai的消息调用,感觉可以使用观察者模式进行优化,以后考虑再改)。

遇到的问题以及解决方法

在优化之前,蓝牙部分有两个很突出的问题:

  1. 单片机经常会报错:BTC stack overflow。
  2. 蓝牙传输稍微快一些就会报错。

首先简单看下什么是BTC,以下是蓝牙层次关系图,来自ESP32蓝牙架构

蓝牙层次关系图.png

BTC 层主要负责向应⽤层提供接⼝⽀持、处理基于 GATT 的规范、处理杂项等,并向应⽤ 层提供以“esp”为前缀的接⼝。所有的 API 都在 ESP_API 层,开发者应当使⽤“esp”为前缀 的蓝⽛ API(特殊的除外)。

简单来说就是,用户调用btc(蓝牙控制层)提供的接口来设置蓝牙信号的广播、连接和编写服务等。

问题1
大概知道BTC是什么了,那如何解决,有两种:
①. 给相关的内存分配大一些的空间,算是适当解决了问题,但治标不治本:
btc栈大小.png ②. 优化代码逻辑,估计是自己当初改官网代码改出了些问题。通过这次代码优化,已经不再出现蓝牙栈溢出问题了。

问题2
错误截图图下所示:

蓝牙报错.png

分析原因:报错内容提示是上一条的蓝牙请求还在处理中,所以本次就被阻塞了。

如何解决:检查蓝牙服务端(单片机)的代码,检查是否在哪里花了超时的时间。看了示例项目的readme解析之后,发现是原示例项目对收到的消息做了复杂的处理,比如indicate指示的处理,长消息的处理等,代码如下:

case ESP_GATTS_WRITE_EVT: {
    ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
    if (!param->write.is_prep){
        ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
        esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
        if (gl_profile_tab[PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2){
            uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
            if (descr_value == 0x0001){
                if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
                    ESP_LOGI(GATTS_TAG, "notify enable");
                    uint8_t notify_data[15];
                    for (int i = 0; i < sizeof(notify_data); ++i)
                    {
                        notify_data[i] = i%0xff;
                    }
                    //the size of notify_data[] need less than MTU size
                    esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
                                            sizeof(notify_data), notify_data, false);
                }
            }else if (descr_value == 0x0002){
                if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
                    ESP_LOGI(GATTS_TAG, "indicate enable");
                    uint8_t indicate_data[15];
                    for (int i = 0; i < sizeof(indicate_data); ++i)
                    {
                        indicate_data[i] = i%0xff;
                    }
                    //the size of indicate_data[] need less than MTU size
                    esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
                                            sizeof(indicate_data), indicate_data, true);
                }
            }
            else if (descr_value == 0x0000){
                ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
            }else{
                ESP_LOGE(GATTS_TAG, "unknown descr value");
                esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
            }

        }
    }
    example_write_event_env(gatts_if, &a_prepare_write_env, param);
    break;
}

直接将这些不需要的代码删掉即可(example_write_event_env(gatts_if, &a_prepare_write_env, param);函数是处理长消息的)。

性能优化

① 降低蓝牙消息的连接周期

  // 从机连接的最小连接间隔 0x0006 * 1.25ms = 7.5ms    slave connection min interval, Time = min_interval * 1.25 msec
    .min_interval = 0x0006, 
    // 从机连接的最大连接间隔 0x0010 * 1.25ms = 16 * 1.25 = 20ms   slave connection max interval, Time = max_interval * 1.25 msec
    .max_interval = 0x000A, 

通过豆包解读下max_interval是什么意思:

max_interval.png

② 修改‘写’命令调用的方式:直接调用characteristic.writeValue是需要等待蓝牙的response。看过蓝牙文档之后,可以选择不要需要蓝牙的回复,调用characteristic.writeValueWithoutResponse方法就可以了。

function rotateMotor({motorId, limit_spd, loc_ref}) {
  // motorId, limit_spd;
  // let a =  loc_ref * 0.017;
  // debugger;
  let rad = loc_ref * Math.PI / 180
  // let cmdArr = Loc_Director({motorId, limit_spd, loc_ref: loc_ref * 0.017});
  let cmdArr = Loc_Director({motorId, limit_spd, loc_ref: rad});
  console.log('---cmdArr: ', cmdArr);
  for(let i = 0; i < cmdArr.length; i++) {
    setTimeout(() => {
      try {  
        // BleCharacteristic.writeValue(cmdArr[i]);
        BleCharacteristic.writeValueWithoutResponse(cmdArr[i]);
        console.log("---指令发送成功---", cmdArr[i]);
        recordCmd.value.push({type: 'send', data: cmdArr[i], status: 'success'});
      } catch (error) {
        console.log("---指令发送失败---", cmdArr[i]);
        recordCmd.value.push({type: 'send', data: cmdArr[i], status: 'fail'});
      }
    }, 15);
  }
}

具体能提升多少性能暂不清楚,看下豆包的解释:

write性能对比.png

文献