【ESP32S3】GATT Server service table传送数据到调试助手

前言

在初步学习esp32蓝牙的过程中,借鉴了官方的GATT Server Service Table Example,可以在readme中看到,此demo是采用低功耗蓝牙的通用属性服务器来创建订阅服务和特性。如果你接触过MQTT,你会发现GATT Server这一特性和MQTT的订阅服务方面特别相似。只不过GATT server的服务模型是基于服务表(service table),而MQTT的服务模型则是基于主题(topic)的。
具体的GATT server代码以及有关函数解释可以直接去看官方文档,我相信比起本人来讲,官方的描述肯定更好。
所以就还是直接放代码吧。

主要内容

本人在这个demo里面分别添加了控制led开关、读随机值、获取芯片内部温度以及读取MPU6050加速度计和陀螺仪值的功能。
前面两个功能的参考链接是ESP32+idf开发—蓝牙通信入门之ble数据收发(notify)
框架依旧是ESP-IDF
好了
先看看工程的框架
工程框架
其实也没什么框架可言, 可以看到我把服务处理的初始化和寄存器读写之类的一些杂七杂八函数放在了别的文件里,而具体的蓝牙操作函数是放在demo文件里(这个demo工程直接从官方welcome界面导入就好了)。
然后就可以开始加功能叻!
定义相关蓝牙参数、数据的结构体和i2c的句柄;

#define GATTS_TABLE_TAG "GATTS_TABLE_DEMO 0325"#define PROFILE_NUM                 1
#define PROFILE_APP_IDX             0
#define ESP_APP_ID                  0x55
#define SAMPLE_DEVICE_NAME          "mwtESP_GATTS_DEMO"
#define SVC_INST_ID                 0/* The max length of characteristic value. When the GATT client performs a write or prepare write operation,
*  the data length must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.
*/
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 500
#define PREPARE_BUF_MAX_SIZE        1024
#define CHAR_DECLARATION_SIZE       (sizeof(uint8_t))#define ADV_CONFIG_FLAG             (1 << 0)
#define SCAN_RSP_CONFIG_FLAG        (1 << 1)// mpu6050 send buffer size
#define SENSOR_DATA_SIZE (3 * sizeof(float) + 3 * sizeof(float) + sizeof(float))
bool is_bt_connected = true; // 标记蓝牙是否连接static uint8_t adv_config_done       = 0;uint16_t heart_rate_handle_table[HRS_IDX_NB];typedef struct {uint8_t                 *prepare_buf;int                     prepare_len;
} prepare_type_env_t;static prepare_type_env_t prepare_write_env;// 定义 I2C 总线句柄和设备句柄。
i2c_master_bus_handle_t bus_handle = NULL;
i2c_master_dev_handle_t dev_handle = NULL;

处理蓝牙数据格式以及设备uid之类的配置;

#define CONFIG_SET_RAW_ADV_DATA
#ifdef CONFIG_SET_RAW_ADV_DATA
static uint8_t raw_adv_data[] = {/* Flags */0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,/* TX Power Level */0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xEB,/* Complete 16-bit Service UUIDs */0x03, ESP_BLE_AD_TYPE_16SRV_CMPL, 0xFF, 0x00,/* Complete Local Name */0x12, ESP_BLE_AD_TYPE_NAME_CMPL,'m', 'w', 't','E', 'S', 'P', '_', 'G', 'A', 'T', 'T', 'S', '_', 'D', 'E', 'M', 'O'
};static uint8_t raw_scan_rsp_data[] = {/* Flags */0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,/* TX Power Level */0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xEB,/* Complete 16-bit Service UUIDs */0x03, ESP_BLE_AD_TYPE_16SRV_CMPL, 0xFF, 0x00
};#else
static uint8_t service_uuid[16] = {/* LSB <--------------------------------------------------------------------------------> MSB *///first uuid, 16bit, [12],[13] is the value0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};/* The length of adv data must be less than 31 bytes */
static esp_ble_adv_data_t adv_data = {.set_scan_rsp        = false,.include_name        = true,.include_txpower     = true,.min_interval        = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec.max_interval        = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec.appearance          = 0x00,.manufacturer_len    = 0,    //TEST_MANUFACTURER_DATA_LEN,.p_manufacturer_data = NULL, //test_manufacturer,.service_data_len    = 0,.p_service_data      = NULL,.service_uuid_len    = sizeof(service_uuid),.p_service_uuid      = service_uuid,.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};// scan response data
static esp_ble_adv_data_t scan_rsp_data = {.set_scan_rsp        = true,.include_name        = true,.include_txpower     = true,.min_interval        = 0x0006,.max_interval        = 0x0010,.appearance          = 0x00,.manufacturer_len    = 0, //TEST_MANUFACTURER_DATA_LEN,.p_manufacturer_data = NULL, //&test_manufacturer[0],.service_data_len    = 0,.p_service_data      = NULL,.service_uuid_len    = sizeof(service_uuid),.p_service_uuid      = service_uuid,.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
#endif /* CONFIG_SET_RAW_ADV_DATA */

esp的广播设备参数以及gatts规范的参数;

static esp_ble_adv_params_t adv_params = {.adv_int_min         = 0x20, // 广播时间间隔的最小值.adv_int_max         = 0x40, // 广播时间间隔的最大值.adv_type            = ADV_TYPE_IND,.own_addr_type       = BLE_ADDR_TYPE_PUBLIC,.channel_map         = ADV_CHNL_ALL,.adv_filter_policy   = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};struct gatts_profile_inst {esp_gatts_cb_t gatts_cb;uint16_t gatts_if;uint16_t app_id;uint16_t conn_id;uint16_t service_handle;esp_gatt_srvc_id_t service_id;uint16_t char_handle;esp_bt_uuid_t char_uuid;esp_gatt_perm_t perm;esp_gatt_char_prop_t property;uint16_t descr_handle;esp_bt_uuid_t descr_uuid;
};

定义并初始化 gatts_profile_inst 类型的数组,用于管理 BLE 的 GATT 服务实例。
初始状态下,gatts_if 被设置为无效值 ESP_GATT_IF_NONE,等待蓝牙协议栈分配有效值;在此之前需要先声明gatts_profile_event_handler函数

static void gatts_profile_event_handler(esp_gatts_cb_event_t event,esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = {[PROFILE_APP_IDX] = {.gatts_cb = gatts_profile_event_handler,.gatts_if = ESP_GATT_IF_NONE,       /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */},
};

原来的代码保留,添加前面说的四个服务;

/* Service */
static const uint16_t GATTS_SERVICE_UUID_TEST      = 0x00FF;
static const uint16_t GATTS_CHAR_UUID_TEST_A       = 0xFF01;
static const uint16_t GATTS_CHAR_UUID_TEST_B       = 0xFF02;
static const uint16_t GATTS_CHAR_UUID_TEST_C       = 0xFF03;
static const uint16_t GATTS_CHAR_UUID_TEST_LED     = 0xFF04; // 加灯
static const uint16_t GATTS_CHAR_UUID_TEST_TEMP    = 0xFF05; // 加芯片内部温度
static const uint16_t GATTS_CHAR_UUID_TEST_RAND    = 0xFF06; // 加随机值
static const uint16_t GATTS_CHAR_UUID_MPU_SENSER   = 0xFF07; // 加sensorstatic const uint16_t primary_service_uuid         = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid   = ESP_GATT_UUID_CHAR_DECLARE;
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
static const uint8_t char_prop_read                =  ESP_GATT_CHAR_PROP_BIT_READ;
static const uint8_t char_prop_write               = ESP_GATT_CHAR_PROP_BIT_WRITE;static const uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY; // add for temperature,rand,sensorstatic const uint8_t char_prop_read_write_notify   = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
/*** heart_measurement_ccc变量,用于控制某个特征值(Characteristic)的通知(Notification)或指示(Indication)功能。* 通知(Notification):允许从设备(Peripheral)主动向主设备(Central)发送数据,而无需主设备请求。* 指示(Indication):类似于通知,但需要主设备确认接收到数据。* {0x00, 0x00} 表示既不启用通知也不启用指示。{0x01, 0x00} 表示启用通知。{0x02, 0x00} 表示启用指示。*/
static const uint8_t heart_measurement_ccc[2]      = {0x00, 0x00};
static const uint8_t char_value[4]                 = {0x11, 0x22, 0x33, 0x44};
static const uint8_t led_value[1] = {0x00};
static const uint8_t temp_value[1] = {0x00};
static const uint8_t rand_value[1] = {0x00};
static const uint8_t sensor_value[SENSOR_DATA_SIZE] = {0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

然后把要订阅的服务添加到表里;

/* Full Database Description - Used to add attributes into the database */
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{// Service Declaration[IDX_SVC] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},/* Characteristic Declaration */[IDX_CHAR_A] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},/* Characteristic Value */[IDX_CHAR_VAL_A] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},/* Client Characteristic Configuration Descriptor */[IDX_CHAR_CFG_A] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},/* Characteristic Declaration */[IDX_CHAR_LED] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},/* Characteristic Value */[IDX_CHAR_VAL_LED] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_LED, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(led_value), (uint8_t *)led_value}},/* Characteristic Declaration */[IDX_CHAR_TEMP] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}},/* Characteristic Value */[IDX_CHAR_VAL_TEMP] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_TEMP, ESP_GATT_PERM_READ, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(temp_value), (uint8_t *)temp_value}},/* Client Characteristic Configuration Descriptor */[IDX_CHAR_CFG_TEMP] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},/* Characteristic Declaration srand*/[IDX_CHAR_RAND] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}},/* Characteristic Value srand*/[IDX_CHAR_VAL_RAND] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_RAND, ESP_GATT_PERM_READ, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(rand_value), (uint8_t *)rand_value}},/* Client Characteristic Configuration Descriptor srand*/[IDX_CHAR_CFG_RAND] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},/* Characteristic Declaration sensor*/[IDX_CHAR_SENSOR] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}},/* Characteristic Value sensor*/[IDX_CHAR_VAL_SENSOR] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_MPU_SENSER, ESP_GATT_PERM_READ, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(sensor_value), (uint8_t *)sensor_value}},/* Client Characteristic Configuration Descriptor sensor*/[IDX_CHAR_CFG_SENSOR] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, sizeof(uint16_t), sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},/* Characteristic Declaration */[IDX_CHAR_B] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},/* Characteristic Value */[IDX_CHAR_VAL_B] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},/* Characteristic Declaration */[IDX_CHAR_C] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_write}},/* Characteristic Value */[IDX_CHAR_VAL_C] ={{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
};

添加读取芯片内部温度、随机值和sensor函数的任务句柄、标志位和处理函数;

TaskHandle_t *temp_task_handle = NULL;  // TEMP 特征值的任务句柄
TaskHandle_t *rand_task_handle = NULL;  // RAND 特征值的任务句柄
TaskHandle_t sensor_task_handle = NULL; // SENSOR 特征值的任务句柄
volatile bool temp_notify_flag = false;
volatile bool rand_notify_flag = false;
volatile bool sensor_notify_flag = false;static void get_rand_value(void *arg)
{// srand(time(NULL));while (1){if (rand_notify_flag == true){uint8_t randv = (uint8_t)esp_random();// 更新 BLE 特征值esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_RAND], 1, &randv);// 发送通知esp_ble_gatts_send_indicate(heart_rate_profile_tab[0].gatts_if, heart_rate_profile_tab[0].conn_id,heart_rate_handle_table[IDX_CHAR_VAL_RAND], 1, &randv, false);}else{vTaskDelete(NULL);}vTaskDelay(pdMS_TO_TICKS(2000));}
}
static void get_temp_value(void *arg)
{float temp_value = 0.0f;  // 存储温度值uint8_t temp_uint8 = 0;   // 转换为整数后发送while (1){if (temp_notify_flag == true){esp_err_t ret = get_current_temperature(&temp_value);if (ret != ESP_OK) {ESP_LOGE(GATTS_TABLE_TAG, "Failed to get temperature value");vTaskDelay(pdMS_TO_TICKS(2000));continue;}temp_uint8 = (uint8_t)(temp_value + 0.5f);  // 四舍五入// 更新 BLE 特征值esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_TEMP], 1, &temp_uint8);// 发送通知esp_ble_gatts_send_indicate(heart_rate_profile_tab[0].gatts_if,heart_rate_profile_tab[0].conn_id,heart_rate_handle_table[IDX_CHAR_VAL_TEMP],1,&temp_uint8,false);ESP_LOGI(GATTS_TABLE_TAG, "Sent temperature value: %d", temp_uint8);}else{vTaskDelete(NULL);  // 删除任务}vTaskDelay(pdMS_TO_TICKS(2000));}
}static void get_mpu6050_value(void *arg)
{// 初始化 I2C 总线和设备if(bus_handle == NULL){esp_err_t ret = i2c_master_init(&bus_handle, &dev_handle);if(ret != ESP_OK){ESP_LOGE(GATTS_TABLE_TAG, "I2C initialized failed");return;}ESP_LOGI(GATTS_TABLE_TAG, "I2C initialized successfully");}// 初始化 MPU6050 任务mpu6050_task_init(dev_handle);SensorData_t received_data;static SensorData_t last_received_data = {0}; // 上一次的有效数据uint8_t sensor_data_buffer[SENSOR_DATA_SIZE];while (1){if (sensor_notify_flag == true){// 从队列中接收数据if (xQueueReceive(sensor_data_queue, &received_data, pdMS_TO_TICKS(2000)) == pdPASS){memcpy(&last_received_data, &received_data, sizeof(SensorData_t)); // 更新有效数据}else{ESP_LOGW(GATTS_TABLE_TAG, "No new data in queue, sending last valid data");}// 序列化数据memcpy(sensor_data_buffer, &last_received_data.accel, 3 * sizeof(float));memcpy(sensor_data_buffer + 3 * sizeof(float), &last_received_data.gyro, 3 * sizeof(float));memcpy(sensor_data_buffer + 6 * sizeof(float), &last_received_data.temp, sizeof(float));// 更新 BLE 特征值esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_SENSOR], SENSOR_DATA_SIZE, sensor_data_buffer);esp_ble_gatts_send_indicate(heart_rate_profile_tab[0].gatts_if,heart_rate_profile_tab[0].conn_id,heart_rate_handle_table[IDX_CHAR_VAL_SENSOR],SENSOR_DATA_SIZE,sensor_data_buffer,false);}else{ESP_LOGI(GATTS_TABLE_TAG, "Stopping MPU6050 tasks and freeing resources");// 如果蓝牙已断开,则释放 I2C 资源if (!is_bt_connected){if (dev_handle != NULL){ESP_ERROR_CHECK(i2c_master_bus_rm_device(dev_handle));dev_handle = NULL;}if (bus_handle != NULL){ESP_ERROR_CHECK(i2c_del_master_bus(bus_handle));bus_handle = NULL;}ESP_LOGI(GATTS_TABLE_TAG, "I2C resources released after Bluetooth disconnection");}// 删除当前任务vTaskDelete(NULL);}vTaskDelay(pdMS_TO_TICKS(2000));}
}

然后我们就需要在gatts_profile_event_handler函数里面添加相应的功能处理函数了,刚开始能够看到里面有一大堆case,有的case涉及到广播、连接、断连、设置MTU以及请求数据读写等内容,但是我们自己写上去的功能怎么添加?在哪里添加什么?不要着急,先了解一下这个函数。
首先看到static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)这行,需要知道,BLE GATT Server框架是基于回调事件触发处理函数的,从第一个参数点进去可以看到esp_gatts_api.h文件里有一系列用于处理回调的标志位,它被用枚举形式装起来了:

/// GATT Server callback function events
typedef enum {ESP_GATTS_REG_EVT                 = 0,       /*!< When register application id, the event comes */ESP_GATTS_READ_EVT                = 1,       /*!< When gatt client request read operation, the event comes */ESP_GATTS_WRITE_EVT               = 2,       /*!< When gatt client request write operation, the event comes */ESP_GATTS_EXEC_WRITE_EVT          = 3,       /*!< When gatt client request execute write, the event comes */ESP_GATTS_MTU_EVT                 = 4,       /*!< When set mtu complete, the event comes */ESP_GATTS_CONF_EVT                = 5,       /*!< When receive confirm, the event comes */ESP_GATTS_UNREG_EVT               = 6,       /*!< When unregister application id, the event comes */ESP_GATTS_CREATE_EVT              = 7,       /*!< When create service complete, the event comes */ESP_GATTS_ADD_INCL_SRVC_EVT       = 8,       /*!< When add included service complete, the event comes */ESP_GATTS_ADD_CHAR_EVT            = 9,       /*!< When add characteristic complete, the event comes */ESP_GATTS_ADD_CHAR_DESCR_EVT      = 10,      /*!< When add descriptor complete, the event comes */ESP_GATTS_DELETE_EVT              = 11,      /*!< When delete service complete, the event comes */ESP_GATTS_START_EVT               = 12,      /*!< When start service complete, the event comes */ESP_GATTS_STOP_EVT                = 13,      /*!< When stop service complete, the event comes */ESP_GATTS_CONNECT_EVT             = 14,      /*!< When gatt client connect, the event comes */ESP_GATTS_DISCONNECT_EVT          = 15,      /*!< When gatt client disconnect, the event comes */ESP_GATTS_OPEN_EVT                = 16,      /*!< When connect to peer, the event comes */ESP_GATTS_CANCEL_OPEN_EVT         = 17,      /*!< When disconnect from peer, the event comes */ESP_GATTS_CLOSE_EVT               = 18,      /*!< When gatt server close, the event comes */ESP_GATTS_LISTEN_EVT              = 19,      /*!< When gatt listen to be connected the event comes */ESP_GATTS_CONGEST_EVT             = 20,      /*!< When congest happen, the event comes *//* following is extra event */ESP_GATTS_RESPONSE_EVT            = 21,      /*!< When gatt send response complete, the event comes */ESP_GATTS_CREAT_ATTR_TAB_EVT      = 22,      /*!< When gatt create table complete, the event comes */ESP_GATTS_SET_ATTR_VAL_EVT        = 23,      /*!< When gatt set attr value complete, the event comes */ESP_GATTS_SEND_SERVICE_CHANGE_EVT = 24,      /*!< When gatt send service change indication complete, the event comes */
} esp_gatts_cb_event_t;

通过查看官方文档可以发现请求读和请求写,还有MTU设置,蓝牙连接/断连这部分可以在本次demo中用到

    ESP_GATTS_READ_EVT                = 1,       /*!< When gatt client request read operation, the event comes */ESP_GATTS_WRITE_EVT               = 2,       /*!< When gatt client request write operation, the event comes */...ESP_GATTS_MTU_EVT                 = 4,       /*!< When set mtu complete, the event comes */...ESP_GATTS_CONNECT_EVT             = 14,      /*!< When gatt client connect, the event comes */ESP_GATTS_DISCONNECT_EVT          = 15,      /*!< When gatt client disconnect, the event comes */

在这个函数下找到case ESP_GATTS_WRITE_EVT:往里面添加我们的任务

case ESP_GATTS_WRITE_EVT:if (!param->write.is_prep){// the data length of gattc write  must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.ESP_LOGI(GATTS_TABLE_TAG, "GATT_WRITE_EVT, handle = %d, value len = %d, value :", param->write.handle, param->write.len);ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, param->write.value, param->write.len);if (heart_rate_handle_table[IDX_CHAR_VAL_LED] == param->write.handle){ESP_LOGI(GATTS_TABLE_TAG, "write:0x%x", param->write.value[0]);if (param->write.value[0] == 0x00){led_off(); // 收到数据0x00关灯ESP_LOGI(GATTS_TABLE_TAG, "recv led off\n");}else if (param->write.value[0] == 0x01){led_on(); // 收到数据0x01开灯ESP_LOGI(GATTS_TABLE_TAG, "recv led on\n");}}if (heart_rate_handle_table[IDX_CHAR_CFG_TEMP] == param->write.handle && param->write.len == 2){uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0];if (descr_value == 0x0001){ESP_LOGI(GATTS_TABLE_TAG, "temp notify enable");xTaskCreate(get_temp_value, "get temp value", 8192, NULL, 10, temp_task_handle);temp_notify_flag=true;}else if (descr_value == 0x0002){ESP_LOGI(GATTS_TABLE_TAG, "temp indicate enable");uint8_t indicate_data[15];for (int i = 0; i < sizeof(indicate_data); ++i){indicate_data[i] = i % 0xff;}// if want to change the value in server database, call:// esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_A], sizeof(indicate_data), indicate_data);// the size of indicate_data[] need less than MTU sizeesp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],sizeof(indicate_data), indicate_data, true);}else if (descr_value == 0x0000){ESP_LOGI(GATTS_TABLE_TAG, "temp notify/indicate disable ");temp_notify_flag=false;//vTaskDelete(pTask);}else{ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value");ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, param->write.value, param->write.len);}}if (heart_rate_handle_table[IDX_CHAR_CFG_RAND] == param->write.handle && param->write.len == 2){uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0];if (descr_value == 0x0001){ESP_LOGI(GATTS_TABLE_TAG, "rand notify enable");xTaskCreate(get_rand_value, "get rand value", 8192, NULL, 10, rand_task_handle);rand_notify_flag=true;}else if (descr_value == 0x0002){ESP_LOGI(GATTS_TABLE_TAG, "rand indicate enable");uint8_t indicate_data[15];for (int i = 0; i < sizeof(indicate_data); ++i){indicate_data[i] = i % 0xff;}// if want to change the value in server database, call:// esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_A], sizeof(indicate_data), indicate_data);// the size of indicate_data[] need less than MTU sizeesp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],sizeof(indicate_data), indicate_data, true);}else if (descr_value == 0x0000){ESP_LOGI(GATTS_TABLE_TAG, "rand notify/indicate disable ");rand_notify_flag=false;//vTaskDelete(pTask);}else{ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value");ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, param->write.value, param->write.len);}}if (heart_rate_handle_table[IDX_CHAR_CFG_SENSOR] == param->write.handle && param->write.len == 2){uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0];if (descr_value == 0x0001){ESP_LOGI(GATTS_TABLE_TAG, "mup6050 notify enable");xTaskCreate(get_mpu6050_value, "get mpu6050 value", 8192, NULL, 10, &sensor_task_handle);sensor_notify_flag=true;}else if (descr_value == 0x0002){ESP_LOGI(GATTS_TABLE_TAG, "mup6050 indicate enable");uint8_t indicate_data[15];for (int i = 0; i < sizeof(indicate_data); ++i){indicate_data[i] = i % 0xff;}// if want to change the value in server database, call:// esp_ble_gatts_set_attr_value(heart_rate_handle_table[IDX_CHAR_VAL_A], sizeof(indicate_data), indicate_data);// the size of indicate_data[] need less than MTU sizeesp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A],sizeof(indicate_data), indicate_data, true);}else if (descr_value == 0x0000){ESP_LOGI(GATTS_TABLE_TAG, "mup6050 notify/indicate disable ");sensor_notify_flag=false;//vTaskDelete(pTask);}else{ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value");ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, param->write.value, param->write.len);}}/* send response when param->write.need_rsp is true*/if (param->write.need_rsp){esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);}}else{/* handle prepare write */example_prepare_write_event_env(gatts_if, &prepare_write_env, param);}break;

感觉还是蓝牙连接和断连这个比较用得到,因为我要删掉一些i2c的资源,不释放的话缓存就要就爆了:

        case ESP_GATTS_CONNECT_EVT:ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, param->connect.remote_bda, 6);esp_ble_conn_update_params_t conn_params = {0};memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));/* For the iOS system, please refer to Apple official documents about the BLE connection parameters restrictions. */conn_params.latency = 0;conn_params.max_int = 0x20;    // max_int = 0x20*1.25ms = 40msconn_params.min_int = 0x10;    // min_int = 0x10*1.25ms = 20msconn_params.timeout = 400;    // timeout = 400*10ms = 4000msheart_rate_profile_tab[0].conn_id = param->connect.conn_id;is_bt_connected = true; // 蓝牙已连接flag//start sent the update connection parameters to the peer device.esp_ble_gap_update_conn_params(&conn_params);break;case ESP_GATTS_DISCONNECT_EVT:ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);is_bt_connected = false; // 蓝牙已断开flagclean_resources();// 重新广播esp_err_t adv_ret = esp_ble_gap_start_advertising(&adv_params);if (adv_ret != ESP_OK) {ESP_LOGE(GATTS_TABLE_TAG, "Failed to start advertising, error code: %s", esp_err_to_name(adv_ret));} else {ESP_LOGI(GATTS_TABLE_TAG, "Advertising restarted successfully.");}break;

clean_resources()部分内容

static void clean_resources(void)
{// 停止 MPU6050 任务if (sensor_task_handle != NULL) {vTaskDelete(sensor_task_handle);sensor_task_handle = NULL;}// 删除队列if (sensor_data_queue != NULL) {vQueueDelete(sensor_data_queue);sensor_data_queue = NULL;}// 如果蓝牙已断开,则释放 I2C 资源if ((is_bt_connected == false) && (sensor_notify_flag == false)){i2c_master_deinit(&bus_handle, &dev_handle);ESP_LOGI(GATTS_TABLE_TAG, "I2C resources released after Bluetooth disconnection");}
}

然后就是app_main()函数:

void app_main(void)
{esp_err_t ret;led_init();ret = temperature_sensor_init();if (ret != ESP_OK) {ESP_LOGE(GATTS_TABLE_TAG, "Failed to initialize temperature sensor");return;}/* Initialize NVS. */ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}ESP_ERROR_CHECK(ret);ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));// 初始化蓝牙esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();ret = esp_bt_controller_init(&bt_cfg);if (ret) {ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));return;}// 使能蓝牙,使用BLE模式ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);if (ret) {ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));return;}// 初始化蓝牙栈/*蓝牙栈bluedroid stack包括了BT和BLE使用的基本的define和API初始化蓝牙栈以后并不能直接使用蓝牙功能,还需要用FSM管理蓝牙连接情况*/ret = esp_bluedroid_init();if (ret) {ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));return;}// 使能蓝牙栈ret = esp_bluedroid_enable();if (ret) {ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));return;}// 建立蓝牙的FSM(有限状态机)// 这里使用回调函数来控制每个状态下的响应,需要将其在GATT和GAP层的回调函数注册/*gatts_event_handler和gap_event_handler处理蓝牙栈可能发生的所有情况,达到FSM的效果*/ret = esp_ble_gatts_register_callback(gatts_event_handler);if (ret){ESP_LOGE(GATTS_TABLE_TAG, "gatts register error, error code = %x", ret);return;}ret = esp_ble_gap_register_callback(gap_event_handler);if (ret){ESP_LOGE(GATTS_TABLE_TAG, "gap register error, error code = %x", ret);return;}ret = esp_ble_gatts_app_register(ESP_APP_ID);if (ret){ESP_LOGE(GATTS_TABLE_TAG, "gatts app register error, error code = %x", ret);return;}/*设置了MTU的值(经过MTU交换,从而设置一个PDU中最大能够交换的数据量)。例如:主设备发出一个1000字节的MTU请求,但是从设备回应的MTU是500字节,那么今后双方要以较小的值500字节作为以后的MTU。即主从双方每次在做数据传输时不超过这个最大数据单元。*/esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);if (local_mtu_ret){ESP_LOGE(GATTS_TABLE_TAG, "set local  MTU failed, error code = %x", local_mtu_ret);}
}

至于为什么led和temperature的初始化要放在这而mpu6050的初始化是放在get_mpu6050_value()函数里,这是程序跑崩了带给我的教训… …
和前两个不一样,mpu6050获取数据要先配置i2c,断开蓝牙连接后要释放i2c的句柄和资源,还有就是我想让用户使用到0xFF07这个服务才配置i2c资源,断开蓝牙连接之后(不是程序退出的时候!只是蓝牙断连!!!)才释放i2c资源,如果你想让用户一连上蓝牙就让程序配置i2c和初始化可以把get_mpu6050_value()函数里;

// 初始化 I2C 总线和设备if(bus_handle == NULL){esp_err_t ret = i2c_master_init(&bus_handle, &dev_handle);if(ret != ESP_OK){ESP_LOGE(GATTS_TABLE_TAG, "I2C initialized failed");return;}ESP_LOGI(GATTS_TABLE_TAG, "I2C initialized successfully");}// 初始化 MPU6050 任务mpu6050_task_init(dev_handle);

这部分的代码放到ESP_GATTS_CONNECT_EVT里面(我是不建议了,即使bus_handle 和dev_handle是全局变量,但是对开发者不太友好,后续要改代码的时候跳这跳那的,况且用户连接蓝牙之后不一定会用到读mpu sensor这个服务,开了也是浪费空间)。
接下来是led部分的代码,这部分内容我直接copy的上面那条参考链接里的代码

led.c

#include <stdio.h>
#include "led.h"
#include<driver/gpio.h>void led_init()
{gpio_config_t cfg={.pin_bit_mask = LED_PIN_SEL,.mode=GPIO_MODE_OUTPUT,.intr_type=GPIO_INTR_DISABLE,.pull_up_en=GPIO_PULLUP_DISABLE,.pull_down_en=GPIO_PULLDOWN_DISABLE,};ESP_ERROR_CHECK(gpio_config(&cfg));
}void led_on()
{ESP_ERROR_CHECK(gpio_set_level(LED_PIN,1));
}void led_off()
{ESP_ERROR_CHECK(gpio_set_level(LED_PIN,0));
}

led.h 灯的引脚我是改成了我板子上的0x38

#ifndef __LED_H__
#define __LED_H__#define LED_PIN GPIO_NUM_38
#define LED_PIN_SEL  (1ULL<<LED_PIN)
void led_init();
void led_on();
void led_off();
#endif

internal_temperature.c

这部分代码是用官方demo的

#include "internal_temperature.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/temperature_sensor.h"static const char *TAG = "INTERNAL_TEMP";
static temperature_sensor_handle_t temp_sensor = NULL;// 初始化温度传感器
esp_err_t temperature_sensor_init(void)
{ESP_LOGI(TAG, "Install temperature sensor, expected temp range: 10~50 ℃");temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(10, 50);esp_err_t ret = temperature_sensor_install(&temp_sensor_config, &temp_sensor);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to install temperature sensor");return ret;}ESP_LOGI(TAG, "Enable temperature sensor");ret = temperature_sensor_enable(temp_sensor);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to enable temperature sensor");return ret;}return ESP_OK;
}// 获取当前温度值
esp_err_t get_current_temperature(float *temperature)
{if (!temp_sensor) {ESP_LOGE(TAG, "Temperature sensor not initialized");return ESP_ERR_INVALID_STATE;}esp_err_t ret = temperature_sensor_get_celsius(temp_sensor, temperature);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to read temperature");return ret;}return ESP_OK;
}

internal_temperature.h

#ifndef INTERNAL_TEMPERATURE_H
#define INTERNAL_TEMPERATURE_H#include <stdint.h>
#include "esp_err.h"/*** @brief 初始化温度传感器** @return esp_err_t ESP_OK success,others is fail*/
esp_err_t temperature_sensor_init(void);/*** @brief 获取当前温度值** @param temperature 指向存储温度值的变量(单位:摄氏度)* @return esp_err_t ESP_OK 表示成功,其他值表示失败*/
esp_err_t get_current_temperature(float *temperature);#endif // INTERNAL_TEMPERATURE_H

mpu6050.c

然后是mpu6050部分的代码,这部分代码是从以前做过的AT32F403A部分移植过来的,寄存器读写部分的函数也是找官方文档复制过来的,哈哈;

#include "mpu6050.h"#include <time.h> 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"static const char *TAG = "MPU6050";
QueueHandle_t sensor_data_queue = NULL;
/*** @brief Read a sequence of bytes from a MPU6050 sensor registers*/
static esp_err_t mpu6050_register_read(i2c_master_dev_handle_t dev_handle, uint8_t reg_addr, uint8_t *data, size_t len)
{return i2c_master_transmit_receive(dev_handle, &reg_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
}/*** @brief Write a byte to a MPU6050 sensor register*/
static esp_err_t mpu6050_register_write_byte(i2c_master_dev_handle_t dev_handle, uint8_t reg_addr, uint8_t data)
{uint8_t write_buf[2] = {reg_addr, data};return i2c_master_transmit(dev_handle, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
}int16_t MPU6050_Byte_to_HalfWord(uint8_t DataL, uint8_t DataH)
{int16_t Data;Data = (DataH << 8) | DataL;return Data;
}/*** @brief i2c master initialization*/
esp_err_t i2c_master_init(i2c_master_bus_handle_t *bus_handle, i2c_master_dev_handle_t *dev_handle)
{if (*bus_handle != NULL) {ESP_LOGW(TAG, "I2C bus already initialized");return ESP_OK;}i2c_master_bus_config_t bus_config = {.i2c_port = I2C_MASTER_NUM,.sda_io_num = I2C_MASTER_SDA_IO,.scl_io_num = I2C_MASTER_SCL_IO,.clk_source = I2C_CLK_SRC_DEFAULT,.glitch_ignore_cnt = 7,.flags.enable_internal_pullup = true,};// 初始化 I2C 总线//ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, bus_handle));esp_err_t ret = i2c_new_master_bus(&bus_config, bus_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to initialize I2C bus");return ret;}i2c_device_config_t dev_config = {.dev_addr_length = I2C_ADDR_BIT_LEN_7,.device_address = MPU6050_SENSOR_ADDR,.scl_speed_hz = I2C_MASTER_FREQ_HZ,};//ESP_ERROR_CHECK(i2c_master_bus_add_device(*bus_handle, &dev_config, dev_handle));ret = i2c_master_bus_add_device(*bus_handle, &dev_config, dev_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to add I2C device");i2c_del_master_bus(*bus_handle); // 清理已分配的资源*bus_handle = NULL;return ret;}ESP_LOGI(TAG, "I2C bus and device initialized successfully");return ESP_OK;
}void i2c_master_deinit(i2c_master_bus_handle_t *bus_handle, i2c_master_dev_handle_t *dev_handle)
{if (*dev_handle != NULL) {ESP_ERROR_CHECK(i2c_master_bus_rm_device(*dev_handle));*dev_handle = NULL;}if (*bus_handle != NULL) {ESP_ERROR_CHECK(i2c_del_master_bus(*bus_handle));*bus_handle = NULL;}ESP_LOGI(TAG, "I2C resources released successfully");
}esp_err_t mpu6050_init(i2c_master_dev_handle_t dev_handle, MPU6050_Info_t *mpu6050_info)
{if(mpu6050_info->init_flag){ESP_LOGW(TAG,"MPU6050 is already initialized.");return ESP_ERR_INVALID_STATE;}// Wake up the MPU6050 (disable sleep mode)ESP_ERROR_CHECK(mpu6050_register_write_byte(dev_handle, MPU6050_PWR_MGMT_1_REG_ADDR, 0x00));// Set accelerometer full-scale range (±2g)ESP_ERROR_CHECK(mpu6050_register_write_byte(dev_handle, MPU6050_ACCEL_CONFIG, mpu6050_info->accel_fs_sel << 3));// Set gyroscope full-scale range (±250dps)ESP_ERROR_CHECK(mpu6050_register_write_byte(dev_handle, MPU6050_GYRO_CONFIG, mpu6050_info->gyro_fs_sel << 3));// Set DLPF configuration (Low Pass Filter)ESP_ERROR_CHECK(mpu6050_register_write_byte(dev_handle, MPU6050_DLPF_CONFIG, mpu6050_info->lpf_cutoff_freq));// Set sample rate dividerESP_ERROR_CHECK(mpu6050_register_write_byte(dev_handle, MPU6050_SMPLRT_DIV, mpu6050_info->sample_rate));mpu6050_info->init_flag = 1;return ESP_OK;
}void mpu6050_task_init(i2c_master_dev_handle_t dev_handle)
{uint8_t data_who = 0;esp_err_t ret;// 创建队列sensor_data_queue = xQueueCreate(10, sizeof(SensorData_t));if (sensor_data_queue == NULL) {ESP_LOGE(TAG, "Failed to create queue");return;}ret = mpu6050_register_read(dev_handle, MPU6050_WHO_AM_I_REG_ADDR, &data_who, 1);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to read WHO_AM_I register: %s", esp_err_to_name(ret));return;}ESP_LOGI(TAG, "WHO_AM_I = %X", data_who);if(data_who == 0x68){// 启动 MPU6050 任务xTaskCreate(mpu6050_task, "mpu6050_task", 4096, (void *)dev_handle, 5, NULL);}
}void MPU6050_GetACCEL(i2c_master_dev_handle_t dev_handle,int16_t *ACCEL_Array)
{uint8_t data_accel[6] = {0};//ESP_ERROR_CHECK(mpu6050_register_read(dev_handle, MPU6050_ACCEL_XOUT_L, data_accel, 6));esp_err_t ret = mpu6050_register_read(dev_handle, MPU6050_ACCEL_XOUT_L, data_accel, 6);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to read accelerometer data: %s", esp_err_to_name(ret));return;}ACCEL_Array[0] = MPU6050_Byte_to_HalfWord(data_accel[0], data_accel[1]);ACCEL_Array[1] = MPU6050_Byte_to_HalfWord(data_accel[2], data_accel[3]);ACCEL_Array[2] = MPU6050_Byte_to_HalfWord(data_accel[4], data_accel[5]);
}void MPU6050_GetGYRO(i2c_master_dev_handle_t dev_handle,int16_t *GYRO_Array)
{uint8_t data_gyro[6] = {0};//ESP_ERROR_CHECK(mpu6050_register_read(dev_handle, MPU6050_GYRO_XOUT_L, data_gyro, 6));esp_err_t ret = mpu6050_register_read(dev_handle, MPU6050_GYRO_XOUT_L, data_gyro, 6);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to read gyroscope data: %s", esp_err_to_name(ret));return;}GYRO_Array[0] = MPU6050_Byte_to_HalfWord(data_gyro[0], data_gyro[1]);GYRO_Array[1] = MPU6050_Byte_to_HalfWord(data_gyro[2], data_gyro[3]);GYRO_Array[2] = MPU6050_Byte_to_HalfWord(data_gyro[4], data_gyro[5]);
}void MPU6050_GetAccel_Value(i2c_master_dev_handle_t dev_handle,MPU6050_Info_t *mpu6050_t,float *Accel_Value)
{int16_t ACCEL_Array[3] = {0};MPU6050_GetACCEL(dev_handle,ACCEL_Array);switch(mpu6050_t->accel_fs_sel){case ACCEL_FS_SEL_2G:Accel_Value[0] = (float)ACCEL_Array[0] / 16384.0;Accel_Value[1] = (float)ACCEL_Array[1] / 16384.0;Accel_Value[2] = (float)ACCEL_Array[2] / 16384.0;break;case ACCEL_FS_SEL_4G:Accel_Value[0] = (float)ACCEL_Array[0] / 8192.0;Accel_Value[1] = (float)ACCEL_Array[1] / 8192.0;Accel_Value[2] = (float)ACCEL_Array[2] / 8192.0;break;case ACCEL_FS_SEL_8G:Accel_Value[0] = (float)ACCEL_Array[0] / 4096.0;Accel_Value[1] = (float)ACCEL_Array[1] / 4096.0;Accel_Value[2] = (float)ACCEL_Array[2] / 4096.0;break;case ACCEL_FS_SEL_16G:Accel_Value[0] = (float)ACCEL_Array[0] / 2048.0;Accel_Value[1] = (float)ACCEL_Array[1] / 2048.0;Accel_Value[2] = (float)ACCEL_Array[2] / 2048.0;break;default:break;}
}void MPU6050_GetGyro_Value(i2c_master_dev_handle_t dev_handle,MPU6050_Info_t *mpu6050_t,float *Gyro_Value)
{int16_t Gyro_Array[3] = {0};MPU6050_GetGYRO(dev_handle,Gyro_Array);switch(mpu6050_t->gyro_fs_sel){case GYRO_FS_SEL_250DPS:Gyro_Value[0] = (float)Gyro_Array[0] / 131.0;Gyro_Value[1] = (float)Gyro_Array[1] / 131.0;Gyro_Value[2] = (float)Gyro_Array[2] / 131.0;break;case GYRO_FS_SEL_500DPS:Gyro_Value[0] = (float)Gyro_Array[0] / 65.5;Gyro_Value[1] = (float)Gyro_Array[1] / 65.5;Gyro_Value[2] = (float)Gyro_Array[2] / 65.5;break;case GYRO_FS_SEL_1000DPS:Gyro_Value[0] = (float)Gyro_Array[0] / 32.8;Gyro_Value[1] = (float)Gyro_Array[1] / 32.8;Gyro_Value[2] = (float)Gyro_Array[2] / 32.8;break;case GYRO_FS_SEL_2000DPS:Gyro_Value[0] = (float)Gyro_Array[0] / 16.4;Gyro_Value[1] = (float)Gyro_Array[1] / 16.4;Gyro_Value[2] = (float)Gyro_Array[2] / 16.4;break;default:break;}
}float MPU6050_GetTemp_Value(i2c_master_dev_handle_t dev_handle)
{uint8_t data_temp[2] = {0};//ESP_ERROR_CHECK(mpu6050_register_read(dev_handle, MPU6050_TEMP_OUT_L, data_temp, 2));esp_err_t ret = mpu6050_register_read(dev_handle, MPU6050_TEMP_OUT_L, data_temp, 2);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to read temperature data: %s", esp_err_to_name(ret));}int16_t temp_raw = MPU6050_Byte_to_HalfWord(data_temp[0], data_temp[1]);return ((float)temp_raw)/340.0 + 36.53;
}void mpu6050_task(void *pvParameters)
{SensorData_t sensor_data;i2c_master_dev_handle_t dev_handle = (i2c_master_dev_handle_t)pvParameters;// 初始化 MPU6050MPU6050_Info_t mpu6050_info = {.lpf_cutoff_freq = LPF_CUTOFF_FREQ_5HZ,.sample_rate = SAMPLE_RATE_DIV8,.accel_fs_sel = ACCEL_FS_SEL_16G,.gyro_fs_sel = GYRO_FS_SEL_2000DPS,.init_flag = 0};ESP_ERROR_CHECK(mpu6050_init(dev_handle, &mpu6050_info));for (;;){// 读取加速度值MPU6050_GetAccel_Value(dev_handle, &mpu6050_info, sensor_data.accel);// 读取陀螺仪值MPU6050_GetGyro_Value(dev_handle, &mpu6050_info, sensor_data.gyro);// 读取温度值sensor_data.temp = MPU6050_GetTemp_Value(dev_handle);// 将数据发送到队列if (sensor_data_queue != NULL) {xQueueSend(sensor_data_queue, &sensor_data, portMAX_DELAY);}vTaskDelay(pdMS_TO_TICKS(3000));}// // 释放 I2C 资源// ESP_ERROR_CHECK(i2c_master_bus_rm_device(dev_handle));// ESP_ERROR_CHECK(i2c_del_master_bus(NULL));// ESP_LOGI(TAG, "I2C de-initialized successfully");// vTaskDelete(NULL);
}

mpu6050.h

#ifndef __MPU6050_H__
#define __MPU6050_H__#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "freertos/queue.h"#define CONFIG_I2C_MASTER_SCL 4
#define CONFIG_I2C_MASTER_SDA 5
#define CONFIG_I2C_MASTER_FREQUENCY 400000#define I2C_MASTER_SCL_IO           CONFIG_I2C_MASTER_SCL       /*!< GPIO number used for I2C master clock */
#define I2C_MASTER_SDA_IO           CONFIG_I2C_MASTER_SDA       /*!< GPIO number used for I2C master data  */
#define I2C_MASTER_NUM              I2C_NUM_0                   /*!< I2C port number for master dev */
#define I2C_MASTER_FREQ_HZ          CONFIG_I2C_MASTER_FREQUENCY /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE   0                           /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE   0                           /*!< I2C master doesn't need buffer */
#define I2C_MASTER_TIMEOUT_MS       1000#define MPU6050_SENSOR_ADDR         0x68        /*!< Address of the MPU6050 sensor */
#define MPU6050_WHO_AM_I_REG_ADDR   0x75        /*!< Register addresses of the "who am I" register */
#define MPU6050_PWR_MGMT_1_REG_ADDR 0x6B        /*!< Register addresses of the power management register 1*/
#define	MPU6050_PWR_MGMT_2_REG_ADDR	0x6C        /*!< Register addresses of the power management register 2*/
#define MPU6050_RESET_BIT           7#define	MPU6050_SMPLRT_DIV		0x19
#define	MPU6050_DLPF_CONFIG		0x1A
#define	MPU6050_GYRO_CONFIG		0x1B
#define	MPU6050_ACCEL_CONFIG	0x1C#define	MPU6050_ACCEL_XOUT_H	0x3B
#define	MPU6050_ACCEL_XOUT_L	0x3C
#define	MPU6050_ACCEL_YOUT_H	0x3D
#define	MPU6050_ACCEL_YOUT_L	0x3E
#define	MPU6050_ACCEL_ZOUT_H	0x3F
#define	MPU6050_ACCEL_ZOUT_L	0x40#define	MPU6050_TEMP_OUT_H		0x41     
#define	MPU6050_TEMP_OUT_L		0x42#define	MPU6050_GYRO_XOUT_H		0x43
#define	MPU6050_GYRO_XOUT_L		0x44
#define	MPU6050_GYRO_YOUT_H		0x45
#define	MPU6050_GYRO_YOUT_L		0x46
#define	MPU6050_GYRO_ZOUT_H		0x47
#define	MPU6050_GYRO_ZOUT_L		0x48typedef struct 
{float accel[3];float gyro[3];float temp;
} SensorData_t;extern QueueHandle_t sensor_data_queue;/*AFS_SEL*/
typedef enum {ACCEL_FS_SEL_2G = 0,    // ±2gACCEL_FS_SEL_4G,        // ±4gACCEL_FS_SEL_8G,        // ±8gACCEL_FS_SEL_16G        // ±16g
} MPU6050_ACCEL_FS_SEL_t;/*FS_SEL*/
typedef enum {GYRO_FS_SEL_250DPS = 0,  // ±250dpsGYRO_FS_SEL_500DPS,      // ±500dpsGYRO_FS_SEL_1000DPS,     // ±1000dpsGYRO_FS_SEL_2000DPS      // ±2000dps
} MPU6050_GYRO_FS_SEL_t;/*DLPF_CFG*/
typedef enum {LPF_CUTOFF_FREQ_5HZ = 0,    //截止频率5HZ   初始采样率1KHZLPF_CUTOFF_FREQ_10HZ,       //截止频率10HZ   初始采样率1KHZLPF_CUTOFF_FREQ_21HZ,       //截止频率21HZ   初始采样率1KHZLPF_CUTOFF_FREQ_44HZ,       //截止频率44HZ   初始采样率1KHZLPF_CUTOFF_FREQ_94HZ,       //截止频率94HZ   初始采样率1KHZLPF_CUTOFF_FREQ_184HZ,      //截止频率184HZ   初始采样率1KHZLPF_CUTOFF_FREQ_260HZ,      //截止频率260HZ   初始采样率8KHZLPF_CUTOFF_FREQ_3600HZ      //禁用低通滤波器   初始采样率8KHZ
} MPU6050_LPF_CUTOFF_FREQ_t;/*SMPLRT_DIV*/
typedef enum {SAMPLE_RATE_DIV0 = 0,       //0分频SAMPLE_RATE_DIV2,           //2分频SAMPLE_RATE_DIV4,           //4分频SAMPLE_RATE_DIV8,           //8分频SAMPLE_RATE_DIV16,          //16分频SAMPLE_RATE_DIV32,          //32分频SAMPLE_RATE_DIV64,          //64分频SAMPLE_RATE_DIV128          //128分频
} MPU6050_SAMPLE_RATE_t;typedef struct 
{i2c_master_bus_handle_t *bus_handle;		// i2c handlei2c_master_status_t *i2c_status;			// i2c statusMPU6050_LPF_CUTOFF_FREQ_t lpf_cutoff_freq;  // DLPF_CFGMPU6050_SAMPLE_RATE_t sample_rate;          // SMPLRT_DIVMPU6050_ACCEL_FS_SEL_t accel_fs_sel;        // AFS_SELMPU6050_GYRO_FS_SEL_t gyro_fs_sel;          // FS_SELuint8_t init_flag;							// is init?
}MPU6050_Info_t;
esp_err_t i2c_master_init(i2c_master_bus_handle_t *bus_handle, i2c_master_dev_handle_t *dev_handle);
void i2c_master_deinit(i2c_master_bus_handle_t *bus_handle, i2c_master_dev_handle_t *dev_handle);
esp_err_t mpu6050_init(i2c_master_dev_handle_t dev_handle, MPU6050_Info_t *mpu6050_info);
void mpu6050_task_init(i2c_master_dev_handle_t dev_handle);
void mpu6050_task(void *pvParameters);
#endif

然后是gatts_table_creat_demo.h的内容,里面加了几个服务

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h> /* Attributes State Machine */
enum
{IDX_SVC,IDX_CHAR_A,IDX_CHAR_VAL_A,IDX_CHAR_CFG_A,IDX_CHAR_LED,IDX_CHAR_VAL_LED,IDX_CHAR_TEMP,IDX_CHAR_VAL_TEMP,IDX_CHAR_CFG_TEMP,IDX_CHAR_RAND,IDX_CHAR_VAL_RAND,IDX_CHAR_CFG_RAND,IDX_CHAR_SENSOR,IDX_CHAR_VAL_SENSOR,IDX_CHAR_CFG_SENSOR,IDX_CHAR_B,IDX_CHAR_VAL_B,IDX_CHAR_C,IDX_CHAR_VAL_C,HRS_IDX_NB,
};

gatts_table_creat_demo.c用到的头文件如下

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "gatts_table_creat_demo.h"
#include "esp_gatt_common_api.h"
#include "driver/i2c_master.h"#include "esp_random.h"
#include "esp_mac.h"
#include "led.h"
#include "internal_temperature.h"
#include "mpu6050.h"

调试

调试的时候发现,蓝牙dev数据的时候总是告诉我数据太长了,给我缩到了20byte,但是我看到我代码里面已经设置了mtu size为500了啊… …
sensor data too long
设置mtu size;

/*
设置了MTU的值(经过MTU交换,从而设置一个PDU中最大能够交换的数据量)。
例如:主设备发出一个1000字节的MTU请求,但是从设备回应的MTU是500字节,那么今后双方要以较小的值500字节作为以后的MTU。
即主从双方每次在做数据传输时不超过这个最大数据单元。
*/
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret)
{ESP_LOGE(GATTS_TABLE_TAG, "set local  MTU failed, error code = %x", local_mtu_ret);
}

很痛苦,手册上也写了范围是23byte到517byte,到底是为什么呢,难道是没有使能?但是在文档里好像没看到说要使能mtu size config的配置;
文档
后面发现调试助手上可以自己手动修改MTU长度
3
4
心里麻麻的… …
然后去问ai去了:

mtu必须要在蓝牙调试助手(客户端)处修改吗?难道不能在esp32处事先设置mtu的最大值?

ai是这样回复我的:

在 BLE(Bluetooth Low Energy)通信中,MTU(Maximum Transmission
Unit)的协商是由主设备(Central)和从设备(Peripheral)共同完成的。虽然你可以在 ESP32(作为
Peripheral)上设置本地支持的最大 MTU 值,但实际使用的 MTU 值是由主设备发起并协商决定的。因此,ESP32
无法单方面强制使用某个 MTU 值。 以下是详细解释:

  1. MTU 的协商机制 BLE 协议规定,MTU 的协商过程如下: 默认 MTU:在没有进行 MTU 交换时,默认的 MTU 值为 23 字节(包括协议头和有效载荷)。 主设备发起请求:主设备(例如手机或 BLE 调试工具)通过发送 Exchange MTU Request
    消息来请求更大的 MTU 值。 从设备响应:从设备(例如 ESP32)会根据自身支持的最大 MTU 值响应 Exchange MTU
    Response 消息。 最终 MTU 值:最终使用的 MTU 值是双方支持的最小值。
  2. 在 ESP32 上设置本地最大 MTU 尽管主设备发起 MTU 请求,但你可以通过以下方式在 ESP32 上设置本地支持的最大 MTU 值: (1) 使用 esp_ble_gatt_set_local_mtu() 在 ESP-IDF 中,可以调用
    esp_ble_gatt_set_local_mtu() 函数来设置 ESP32 支持的最大 MTU 值。例如:
    esp_ble_gatt_set_local_mtu(517); // 设置本地支持的最大 MTU 值为 517 字节
    这个函数的作用是告诉主设备,ESP32 最多可以支持 517 字节的 MTU。 如果主设备请求的 MTU 值小于或等于 517,则最终使用的
    MTU 值为主设备请求的值。 如果主设备请求的 MTU 值大于 517,则最终使用的 MTU 值为 517。 (2) 监听 MTU 事件 在
    BLE 回调函数中监听 ESP_GATTS_MTU_EVT 事件,确认协商后的 MTU 值:
    case ESP_GATTS_MTU_EVT:
    ESP_LOGI(GATTS_TABLE_TAG, “ESP_GATTS_MTU_EVT, MTU %d”, param->mtu.mtu);
    break;

okay… …那就只能这样了。
这漫长的编译过程… …
5
来看看结果吧,一下log分别提示了广播成功,客户端连接esp32蓝牙成功,修改MTU Size为500,控制板载led灯亮灭、芯片内部温度获取、随机值获取以及mpu6050数据获取;
6
手动修改MTU
7
8
9
10
11
12
最后再看看客户端上的log,前面连接超时是因为手机蓝牙连到了另一个设备,导致esp32一直连不上我的手机;
13
参考链接:
gatt_server_service_table
蓝牙 API
i2c mpu9250
temperature sensor
【完】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/900999.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek :中国 AI 如何用 “小米加步枪” 逆袭硅谷

2025 年春节前夕&#xff0c;人工智能领域诞生了一项重大成果 ——DeepSeek 发布DeepSeek - R1 大模型。这一模型迅速引发广泛关注&#xff0c;在苹果 AppStore 中国区免费榜登顶。 DeepSeek 采用开源策略&#xff0c;依据宽松的 MIT 许可证&#xff0c;公开了模型权重、训练方…

关税扰动下市场波动,如何寻找确定性的长期之锚?

近期的关税纷争&#xff0c;扰动全球资本市场下行。A股市场一度大幅下跌。但随着各大主力下场&#xff0c;有关部委发布有关有力措施&#xff0c;A股逐步稳住阵脚。 4月8日至4月10日&#xff0c;大盘指数连续3天上涨&#xff0c;上涨120多点&#xff0c;展现出较强的抵御关税壁…

NeuroImage:膝关节炎如何影响大脑?静态与动态功能网络变化全解析

膝骨关节炎&#xff08;KOA&#xff09;是导致老年人活动受限和残疾的主要原因之一。这种疾病不仅引起关节疼痛&#xff0c;还会显著影响患者的生活质量。然而&#xff0c;目前对于KOA患者大脑功能网络的异常变化及其与临床症状之间的关系尚不清楚。 2024年4月10日&#xff0c;…

【KWDB 创作者计划】KWDB 数据库全维度解析手册

——从原理到实践&#xff0c;构建下一代数据基础设施 ​第一章&#xff1a;KWDB 设计哲学与技术全景 1.1 为什么需要 KWDB&#xff1f; 在数据爆炸与业务场景碎片化的今天&#xff0c;传统数据库面临三大挑战&#xff1a;​扩展性瓶颈​&#xff08;单机性能天花板&#xff…

一个批量文件Dos2Unix程序(Microsoft Store,开源)

这个程序可以把整个目录的文本文件改成UNIX格式&#xff0c;源码是用C#写的。 目录 一、从Microsoft Store安装 二、从github获取源码 三、功能介绍 3.1 运行 3.2 浏览 3.3 转换 3.4 转换&#xff08;无列表&#xff09; 3.5 取消 3.6 帮助 四、源码解读 五、讨论和…

std::string` 类

以下是对 std::string 类中 修改操作 和 字符串操作 的示例代码&#xff0c;帮助你更好地理解这些函数的使用&#xff1a; 5. 修改操作 (1) operator 用于追加字符串、C 风格字符串或字符。 #include <iostream> #include <string>int main() {std::string str …

《Spring Boot+策略模式:企业级度假订单Excel导入系统的架构演进与技术实现》

前言 在数字化时代背景下&#xff0c;订单管理系统的高效性与灵活性成为企业竞争力的核心要素。本文档详细剖析了一个基于 策略模式 的度假订单导入系统&#xff0c;通过分层架构设计实现了多源异构数据的标准化处理。系统以 Spring Boot 为核心框架&#xff0c;结合 MyBatis …

SSRF漏洞公开报告分析

文章目录 1. SSRF | 获取元数据 | 账户接管2. AppStore | 版本上传表单 | Blind SSRF3. HOST SSRF一、为什么HOST修改不会影响正常访问二、案例 4. Turbonomic 的 终端节点 | SSRF 获取元密钥一、介绍二、漏洞分析 5. POST | Blind SSRF6. CVE-2024-40898利用 | SSRF 泄露 NTL…

告别 ifconfig:为什么现代 Linux 系统推荐使用 ip 命令

告别 ifconfig&#xff1a;为什么现代 Linux 系统推荐使用 ip 命令 ifconfig 指令已经被视为过时的工具&#xff0c;不再是查看和配置网络接口的推荐方式。 与 netstat 被 ss 替代类似。 本文简要介绍 ip addr 命令的使用 简介ip ifconfig 属于 net-tools 包&#xff0c;这个…

VLC快速制作rtsp流媒体服务器

1.安装vlc media player工具 2.打开后点击菜单 媒体->流 3.添加mp4视频&#xff0c;选择串流 4.选择 下一个 5.新目标选择 RTSP&#xff0c;点击添加按钮 6.端口和路径随便填写&#xff0c;如果推流失败就换个端口。一路操作下去 7.点击 流 按钮后&#xff0c;就可以看到下图…

基于 JavaWeb 的 SSM 在线视频教育系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…

RK3568 基于Gstreamer的多媒体调试记录

文章目录 1、环境介绍2、概念理清3、提前准备4、GStreamer编译5、GStreamer基础介绍6、视频播放初体验7、视频硬编码7.1、h2647.2、h265 8、视频硬解码8.1、解码视频并播放解码视频并播放带音频 1、环境介绍 硬件&#xff1a;飞凌ok3568-c开发板 软件&#xff1a;原厂rk356x …

Mac学习使用全借鉴模式

Reference https://zhuanlan.zhihu.com/p/923417581.快捷键 macOS 的快捷键组合很多&#xff0c;相应的修饰键就多达 6 个&#xff08;Windows 系统级就 4 个&#xff09;&#xff1a; Command ⌘ Shift ⇧ Option ⌥ Control ⌃ Caps Lock ⇪ Fn 全屏/退出全屏 command con…

SpringBoot多线程,保证各个子线程和主线程事物一致性

SpringBoot多线程&#xff0c;保证各个子线程和主线程事物一致性 1、第一种写法1.1、TransactionalUntil工具类1.2、service业务类 2、第二种写法2.1、service业务类 1、第一种写法 1.1、TransactionalUntil工具类 import org.springframework.jdbc.datasource.DataSourceTra…

高并发的业务场景下,如何防止数据库事务死锁

一、 一致的锁定顺序 定义: 死锁的常见原因之一是不同的事务以不同的顺序获取锁。当多个事务获取了不同资源的锁,并且这些资源之间发生了互相依赖,就会形成死锁。 解决方法: 确保所有的事务在获取多个锁时,按照相同的顺序请求锁。例如,如果事务A需要锁定表A和表B,事务…

【从0到1学MybatisPlus】MybatisPlus入门

Mybatis-Plus 使用场景 大家在日常开发中应该能发现&#xff0c;单表的CRUD功能代码重复度很高&#xff0c;也没有什么难度。而这部分代码量往往比较大&#xff0c;开发起来比较费时。 因此&#xff0c;目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国…

力扣HOT100之链表: 148. 排序链表

这道题直接用蠢办法来做的&#xff0c;直接先遍历一遍链表&#xff0c;用一个哈希表统计每个值出现的次数&#xff0c;由于std::map<int, int>会根据键进行升序排序&#xff0c;因此我们将节点的值作为键&#xff0c;其在整个链表中的出现次数作为值&#xff0c;当所有元…

Transformer多卡训练初始化分布式环境:(backend=‘nccl‘)

Transformer多卡训练初始化分布式环境:(backend=‘nccl’) dist.init_process_group(backend=nccl)在多卡环境下初始化分布式训练环境,并为每个进程分配对应的 GPU 设备。下面为你逐行解释代码的含义: 1. 初始化分布式进程组 try:dist.init_process_group(backend=nccl) e…

使用Mybatis时在XML中SQL高亮显示的方法

如图所示&#xff0c;上方的SQL代码很像是一个字符串&#xff0c;那么如何把上方的SQL改成和下方一样的SQL,使得IDEA可以识别SQL方言呢&#xff1f; 1.选中SQL中的一部分代码&#xff0c;此时左侧会出现一个黄色的灯泡图案&#xff0c;点击2.选择这个注入语言或者引用

Spring Boot MongoDB自定义连接池配置

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;http://zhangxiaofan.blog.csdn.net/article/details/144341407 一、引言 在 Spring Boot 应用中使用 MongoDB 时&#xff0c;合理配置连接池可以显著提升数据库访问的性能和稳定性。默…