当前位置: 首页 > news >正文

【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
【完】

相关文章:

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

前言 在初步学习esp32蓝牙的过程中&#xff0c;借鉴了官方的GATT Server Service Table Example&#xff0c;可以在readme中看到&#xff0c;此demo是采用低功耗蓝牙的通用属性服务器来创建订阅服务和特性。如果你接触过MQTT&#xff0c;你会发现GATT Server这一特性和MQTT的订…...

《Vue Router实战教程》5.嵌套路由

欢迎观看《Vue Router 实战&#xff08;第4版&#xff09;》视频课程 嵌套路由 一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下&#xff0c;URL 的片段通常对应于特定的嵌套组件结构&#xff0c;例如&#xff1a; 通过 Vue Router&#xff0c;你可以使用嵌套路由配置…...

小白学习java第12天:IO流之转换流

我们可能会遇到这样情况就是&#xff1a;你在读取那个文件编码类型是GBK&#xff0c;而是进行读取的的时候使用的UTF-8&#xff0c;这就会导致乱码&#xff0c;因为你没办法保证别人是用什么类型进行编写的&#xff0c;因此我们就需要转换流进行处理这种情况&#xff01; 下面…...

BERT - 直接调用transformers.BertModel, BertTokenizerAPI不进行任何微调

本节代码将使用 transformers 库加载预训练的BERT模型和分词器&#xff08;Tokenizer&#xff09;&#xff0c;并处理文本输入。 1. 加载预训练模型和分词器 from transformers import BertTokenizer, BertModelmodel_path "/Users/azen/Desktop/llm/models/bert-base-…...

如何在 Spring Boot 项目中使用 MyBatis 进行批量操作以提升性能?

MyBatis 提供了 ExecutorType.BATCH 类型&#xff0c;允许将多个 SQL 语句进行组合&#xff0c;最后统一执行&#xff0c;从而减少数据库的访问频率&#xff0c;提升性能。 以下是如何在 Spring Boot 项目中使用 MyBatis 进行批量操作的关键点&#xff1a; 1. 配置 MyBatis 使…...

传统门店VS智慧门店:电能物联网平台在连锁行业的节能应用

前言 随着连锁零售行业门店的规模化发展&#xff0c;能源消耗成为企业成本管控与可持续发展的重要课题。在当今快节奏的商业环境中&#xff0c;连锁门店的管理和运营变得越来越具有挑战性。能源数据是连锁门店的管理中重要组成部分&#xff0c;为了提高门店的能源利用效率和管…...

[ctfshow web入门] RCE 或(or)、异或(xor)、非(not)绕过

代码 这是一个python语言的&#xff0c;使用或(or)、异或(xor)、非(not)防火墙 这将根据命令提供加密后的指令&#xff0c;用法 rce_xor(list_cmd)、rce_or(list_cmd)、rce_not(list_cmd) 用来生成加密后的指令&#xff0c;这个指令是类如下面这样的&#xff0c;这些指令可以用…...

C++ 虚函数:深入理解多态的核心机制

C 虚函数&#xff1a;深入理解多态的核心机制 在 C 里&#xff0c;虚函数是实现 多态&#xff08;Polymorphism&#xff09; 的关键机制之一。透彻理解虚函数的概念、实现方式以及使用场景&#xff0c;对编写高效且可扩展的 C 代码起着至关重要的作用。本文会详细介绍 C 虚函数…...

速盾:高防CDN节点对收录有影响吗?

引言 搜索引擎收录是网站运营中至关重要的环节&#xff0c;它直接影响着网站的曝光度和流量。近年来&#xff0c;随着网络安全威胁的增加&#xff0c;许多企业开始采用高防CDN&#xff08;内容分发网络&#xff09;来保护其网站免受DDoS攻击和其他形式的网络攻击。然而&#x…...

按规则批量修改文件扩展名、删除扩展名或添加扩展名

文件的扩展名是多种多样的&#xff0c;有些不同文件的扩展名之间相互是可以直接转换的。我们工作当中最常见的就是 doc 与 docx、xls 与 xlsx、jpg 与 jpeg、html 与 htm 等等&#xff0c;这些格式在大部分场景下都是可以相互转换 能直接兼容的。我们今天要介绍的就是如何按照一…...

在Java项目中,引入【全局异常处理器】

目录 一.为什么引入全局异常处理器&#xff08;目前项目碰到了什么问题&#xff09;&#xff1f; 1.问题描述 2.与预期的差别 3.解决方案 二.解决上述问题 1.定义【业务异常类】 2.在serviceImpl层&#xff0c;手动抛出【违反唯一性约束】这个异常 3.定义【全局异常处理…...

计算机网络-TCP协议详解

TCP协议详解 2. TCP协议详解2.1 TCP协议概述2.1.1 TCP的历史背景2.1.2 TCP的设计目标2.1.3 TCP的基本特性2.1.4 TCP与其他传输协议的比较2.1.5 TCP的应用场景 2.2 TCP头部结构2.2.1 TCP头部格式2.2.2 TCP头部字段详解源端口号和目的端口号&#xff08;各16位&#xff09;序列号…...

[蓝桥杯 2023 省 A] 平方差

P9231 [蓝桥杯 2023 省 A] 平方差 题目描述 给定 L , R L,R L,R&#xff0c;问 L ≤ x ≤ R L \leq x \leq R L≤x≤R 中有多少个数 x x x 满足存在整数 y , z y,z y,z 使得 x y 2 − z 2 xy^2-z^2 xy2−z2。 输入格式 输入一行包含两个整数 L , R L,R L,R&#xff…...

隐私通信新时代:磐石云AXB平台如何重塑企业安全防线?

在数据泄露频发的当下&#xff0c;企业如何守护用户隐私&#xff1f;磐石云AXB隐私号平台以四大技术革新&#xff0c;构建通信安全堡垒。 技术突破&#xff1a; 动态号码隔离&#xff0c;隐私0泄露 AXB模式下&#xff0c;A与B的真实号码全程隐藏&#xff0c;仅通过虚拟号X中转…...

什么是八步工作法?

八步工作法&#xff0c;顾名思义&#xff0c;就是把一项工作拆分成八个步骤来完成。它的核心目的是让工作变得更有条理&#xff0c;更高效&#xff0c;避免忙而无序&#xff0c;做到事事有着落&#xff0c;件件有结果。这个方法在很多企业和单位中都有应用&#xff0c;尤其适合…...

日事清团队协作软件:智能制定计划,实时追踪任务进展,文件共享无缝协作,知识库一键沉淀成果​

我们总是有微细目标&#xff0c;日事清可以帮助团队轻松共同制定计划、同步工作进展、共享工作资料、沉淀工作成果。 日事清具体如何帮助团队&#xff1f;你可以先了解以下这些基本功能模块。 从【计划】开始 在日事清中&#xff0c;【计划】是协同办公的开始&#xff0c;邀请…...

全球变暖(蓝桥杯 2018 年第九届省赛)

题目描述 你有一张某海域 NN 像素的照片&#xff0c;. 表示海洋、 # 表示陆地&#xff0c;如下所示&#xff1a; ....... .##.... .##.... ....##. ..####. ...###. .......其中 "上下左右" 四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。 由…...

国际物流怎么找客户?选择适合自己的企业拓客平台

在国际物流行业&#xff0c;获客一直是企业发展的核心难题。无论是跨境电商、传统外贸&#xff0c;还是国际货代&#xff0c;找到精准的客户资源并高效转化&#xff0c;是决定企业能否抢占市场蓝海的关键。今天&#xff0c;我们就来聊聊如何选择一个真正适合的国际物流拓客平台…...

驱动-内核空间和用户空间数据交换

内核空间与用户控件数据交换 前面了解的字符设备中对 file_operations 结构体的进行了填充&#xff0c; 该 结构体的每一个成员都对应着一个系统调用&#xff0c; 例如 read、 write 等&#xff0c; 在字符设备相关的文章中有实验过对 调用函数进行了标志打印&#xff0c; 并没…...

智膳优选 | AI赋能的智慧食堂管理专家 —— 基于飞书多维表格和扣子(Coze)的智能解决方案

智膳优选 | AI赋能的智慧食堂管理专家 基于飞书多维表格和扣子&#xff08;Coze&#xff09;的智能解决方案 数据驱动餐饮管理&#xff0c;让每一餐都是营养与经济的完美平衡&#xff01; “智膳优选”通过整合飞书与Coze&#xff0c;将数据智能引入校园餐饮管理&#xff0…...

FCOS目标检测

一、模型框架 FCOS采用的网络架构和RetinaNet一样&#xff0c;都是采用FPN架构&#xff0c;如图2所示&#xff0c;每个特征图后是检测器&#xff0c;检测器包含3个分支&#xff1a;classification&#xff0c;regression和center-ness。 对于特征图Fi∈RHWC&#xff0c;其相对…...

Linux中动态加载两个同名so(dlopen动态链接库)

// 当前路径下 ./test1.c int Func1(int a, int b) { return ab; } //编译生成so gcc -fPIC -shared -o libTest.so test1.c // 当前路径的test2文件夹中 ./test2/test2.c int Func1(int a, int b) { return a-b; } //编译生成同名so gcc -fPIC -shared -o …...

直播电商革命:东南亚市场的“人货场”重构方程式

一、人设经济3.0&#xff1a;从流量收割到情感基建 东南亚直播战场正经历从"叫卖式促销"到"沉浸式信任"的质变&#xff0c;新加坡市场成为最佳观察样本&#xff1a; 数据印证趋势&#xff1a;Shopee直播用户日均停留28分钟&#xff0c;超短视频平台&#…...

【CF】Day30——Codeforces Round 824 (Div. 2) C + Codeforces Round 825 (Div. 2) BC1

C. Phase Shift 题目&#xff1a; 思路&#xff1a; 好题&#xff0c;值得多看 这题我们看题目就能想到一个很显然的做法&#xff0c;那就是贪心地把每一个字母换成最前面的没使用过的字母 但是这样直接写是有问题的&#xff0c;因为题目说了最后要让所有的字母成一个换&…...

STM32 模块化开发指南 · 第 2 篇 如何编写高复用的外设驱动模块(以 UART 为例)

本文是《STM32 模块化开发实战指南》的第 2 篇,聚焦于“串口驱动模块的设计与封装”。我们将从一个最基础的裸机 UART 初始化开始,逐步实现:中断支持、环形缓冲收发、模块接口抽象与测试策略,构建一个可移植、可扩展、可复用的 UART 驱动模块。 一、模块化 UART 的设计目标…...

SSRF打靶总结

文章目录 一. PortSwigger1、本地服务器的基本SSRF2、基本的目标不是漏洞机3、Referer标头的外带SSRF4、简单黑名单的SSRF黑名单绕过思路&#xff1a; 5、重定向的SSRF6. 简单的白名单SSRF白名单绕过思路&#xff1a; 二、BWAPP1. SSRF 文件包含漏洞 | 内网探测2. XXE -> S…...

第五章:5.1 ESP32物联网应用 - MQTT协议深度教程

一、MQTT协议简介 1.1 发布/订阅模式 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级物联网通信协议&#xff0c;采用发布/订阅模式&#xff1a; 发布者&#xff08;Publisher&#xff09;&#xff1a;发送消息到指定主题&#xff08;如&…...

c++知识点

高级模板技术45&#xff1a; 模板元编程&#xff1a;这是一种在编译期进行计算和代码生成的技术。通过模板的递归展开、特化等操作&#xff0c;可以实现一些复杂的功能&#xff0c;例如编译期的计算、类型安全的容器等。例如&#xff0c;使用模板元编程可以实现一个编译期计算斐…...

【ChCore Lab 01】Bomb Lab 拆炸弹实验(ARM汇编逆向工程)

文章目录 1. 前言2. 实验代码版本问题3. 关于使用问题4. 宏观分析5. read_line 函数介绍6. phase_0 函数6.1. read_int 函数6.2. 回到 phase_0 函数继续分析6.3. 验证结果 7. phase_1 函数7.2. 验证结果 8. phase_2 函数8.1. read_8_numbers 函数8.2. 回到 phase_2 函数继续分析…...

QStackedWidget讲解

简介 QStackedWidget 类在一次仅显示1个窗口的地方提供一个窗口栈。 头文件:#include qmake:QT widgets 基类:QFrame 属性 count : const int currentIndex : int 公有槽函数 void setCurrentIndex(int index) void setCurrentWidget(QWidget *widget)信号 void current…...

宝马集团加速 ERP 转型和上云之旅

宝马集团&#xff08;BMW Group&#xff09;作为全球领先的豪华汽车和摩托车制造商&#xff0c;致力于构建更加智能、绿色、人性化的出行体验。为了支持其全球化、数字化业务战略&#xff0c;宝马集团正在进行大规模的 IT 体系升级和 ERP 云转型。该项目以“RISE with SAP S/4H…...

AutoEval:现实世界中通才机器人操作策略的自主评估

25年3月来自 UC Berkeley 和 Nvidia 的论文“AutoEval: Autonomous Evaluation of Generalist Robot Manipulation Policies in the Real World”。 可规模化且可复现的策略评估一直是机器人学习领域长期存在的挑战。评估对于评估进展和构建更优策略至关重要&#xff0c;但在现…...

ARM Cortex M内存屏障指令__dsb( )和__isb( )

ARM Cortex-M 系列处理器中的 __dsb() 和 __isb() 是内存屏障指令&#xff0c;用于确保内存操作的顺序性和可见性&#xff0c;尤其在涉及外设、多核/多线程、自修改代码或关键系统配置时至关重要。 一&#xff0c;详细说明和典型应用场景 1. __dsb()&#xff08;Data Synchron…...

deepseek热度已过?

DeepSeek的热度并没有消退&#xff0c;以下是具体表现&#xff1a; 用户使用量和下载量方面 • 日活跃用户量增长&#xff1a;DeepSeek已经成为目前最快突破3000万日活跃用户量的应用程序。 • 应用商店下载量&#xff1a;1月26日&#xff0c;DeepSeek最新推出的AI聊天机器人…...

使用 Datadog 和 Slack Alerts 监控 minikube

为什么要监控 minikube 集群&#xff1f;这是一个不错的练习&#xff0c;可以让你了解 DataDog 的设置过程并探索 K8s 指标产品。 本文将分享我的以下经验&#xff1a; 设置最新的 minikube部署示例应用程序创建 DataDog&#xff08;试用&#xff09;帐户使用 Helm 安装 Data…...

深入 Redis 持久化:从原理到企业级应用的全景图

&#x1f9e0; 什么是 Redis 持久化&#xff1f;为什么需要&#xff1f; Redis 是内存型数据库&#xff0c;默认所有数据都存在内存中&#xff0c;一旦断电&#xff0c;数据就会消失。为了避免重要数据丢失&#xff0c;Redis 提供了持久化机制&#xff0c;用于将内存中的数据保…...

NET模式下如何配置虚拟机的IP地址为静态的

1.查看网关&#xff1a; 2.找到虚拟机的网络配置文件 cd ./etc/sysconfig/network-scripts/ vim ifcfg-ens33 3.修改配置 BROWSER_ONLY"no" IPADDR192.168.122.120 NETMASK255.255.255.0 GATEWAY192.168.122.2 DNS18.8.8.8 4.重启网路服务 sudo systemctl rest…...

VMWare Workstation Pro17.6最新版虚拟机详细安装教程(附安装包教程)

目录 前言 一、VMWare虚拟机下载 二、VMWare虚拟机安装 三、运行虚拟机 前言 VMware 是全球领先的虚拟化技术与云计算解决方案提供商&#xff0c;通过软件模拟计算机硬件环境&#xff0c;允许用户在一台物理设备上运行多个独立的虚拟操作系统或应用。其核心技术可提升硬件…...

磐石云智能语音客服系统——技术革新引领服务新体验

在人工智能技术飞速发展的今天&#xff0c;企业对于智能化客户服务的需求日益增长。磐石云智能语音客服系统凭借其前沿技术架构与深度场景适配能力&#xff0c;正在重新定义人机交互的边界。本文将深入解析该系统如何通过技术创新实现服务效率与体验的双重突破。 一、意图识别…...

什么是iPaaS?

在当今数字化时代&#xff0c;企业面临着日益复杂的IT环境和不断增长的业务需求。随着云计算、微服务、物联网等技术的快速发展&#xff0c;企业需要更加高效、灵活且安全的方式来进行数据集成和应用集成。集成平台即服务&#xff08;iPaaS&#xff09;应运而生&#xff0c;成为…...

Vue3 中 Pinia 持久化的全面解析和最佳实践

Vue3 中 Pinia 持久化的全面解析 一、Pinia 简介​ Pinia 是 Vue 的新一代状态管理库&#xff0c;它提供了简洁的 API&#xff0c;支持 Composition API&#xff0c;并且拥有良好的代码拆分和热更新能力。相比于 Vuex&#xff0c;Pinia 的代码结构更加扁平&#xff0c;易于理…...

蓝桥杯最后一天警告!!!

1.万能头文件 #include <bits/stdc.h> 2.一道题实在一点都不会&#xff0c;直接碰运气骗分 #include <bits/stdc.h> using namespace std;int main() {srand(time(0));printf("%d",rand()%101);//生成一个1到10之间的随机整数&#xff0c;并输出print…...

el-time-picker标签的使用

需求&#xff1a; 实现培训日期&#xff0c;用户可以选择某一天的日期&#xff0c;这个比较简单 <el-form-item label"培训日期" prop"startTime"><el-date-picker clearablev-model"form.startTime"type"date"placeholder…...

Mysql--基础知识点--85.1--Innodb自适应哈希索引

1. 自适应哈希索引的用途 InnoDB 的自适应哈希索引&#xff08;Adaptive Hash Index, AHI&#xff09;是 MySQL 数据库引擎中一项智能优化查询性能的功能。其核心作用如下&#xff1a; 加速等值查询 哈希索引通过哈希函数将键映射到固定位置&#xff0c;实现 O(1) 时间复杂度的…...

Matlab 考虑电机激励力的整车垂向七自由度的被动悬架和LQR控制

1、内容简介 Matlab 200-考虑电机激励力的整车垂向七自由度的被动悬架和LQR控制 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略...

统一功能处理

SpringBoot统一功能处理 本节目表 1.掌握拦截器的使用&#xff0c;及其原理 2.学习统一数据返回格式和统一异常处理的操作 3.了解一些Spirng的源码 文章目录 SpringBoot统一功能处理前言一、拦截器是什么&#xff1f;二、使用步骤1.定义拦截器2.注册配置拦截器 三&#xff0c;适…...

LibreOffice Writer使用01去除单词拼写判断的红色下划线

这个软件还是非常有特色的&#xff0c;因为大家需要office的全部功能&#xff0c;常常忽略了这个软件的使用体验。 csdn不是特别稳定&#xff0c;linux也没有什么比较好的md编辑器&#xff0c;所以我选择这个软件来记录我的临时博客&#xff0c;原因无他&#xff0c;它可以保存…...

JAVA基础 - 高效管理线程隔离数据结构ThreadLocalMap

欢迎光临小站&#xff1a;致橡树 ThreadLocalMap 是 ThreadLocal 的核心底层数据结构&#xff0c;负责在每个线程中存储与 ThreadLocal 实例绑定的数据。它的设计目标是高效管理线程隔离数据&#xff0c;同时尽量减少内存泄漏风险。以下是其核心实现细节。 数据结构与设计目标…...

每日一题(小白)暴力娱乐篇25

由题意直知&#xff0c;要求将给定的字符串转为符合格式的字符串。首先我们来思考一下例如02/02/02这样的数字日月肯定不用进行修改&#xff0c;修改后需要加上对应的年份的前两位&#xff0c;题目要求1960年1月1日~2059年12年12月31日&#xff0c;&#xff08;对1960年至2059年…...

地表水-地下水耦合建模全景解析暨SWAT-MODFLOW地表与地下协同模拟及多情景专题应用

第一、模型原理与层次结构 1.1流域水循环与SWAT模型 1.2 地下水模拟与MODFLOW模型 1.3 SWAT-MODFLOW地表-地下耦合模型 1.4 QSWATMOD 插件与功能介绍 1.5 模型实现所需软件平台 第二、QGIS软件 2.1 QGIS平台 2.2 QGIS安装 2.3 QGIS界面认识 2.4 QGIS常见数据格式 2.…...