В начало | Зарегистрироваться | Заказать наши киты почтой
 
 
 
 

Обмен данными между смартфоном и ESP32. Часть 2. Android. BLE. GATT

📆24 августа 2023   ✒️ayan   🔎795   💬0  

Рассматриваемая сегодня тема — установление соединения и двусторонний обмен информацией между ESP32 и Android.

Видео-демонстрация проекта



Приложение для ESP32

Приведённые ниже библиотеки обеспечивают следующий функционал устройства.

До установления соединения:
а) Передача в эфир широковещательных пакетов, содержащих флаги, RSSI, UUID сервиса и имя устройства.
б) Ответы на запросы сканирования.

После установления соединения:
а) Включение или выключение светодиода на пине GPIO2, в зависимости от значения (1 или 0, соответственно) характеристики, модифицируемой смартфоном.
б) Опрос АЦП с периодичностью 200 мс и последующая передача на Android четырёх байтов, содержащих тысячи, сотни, десятки и единицы текущего значения напряжения.

led.h


#ifndef LED_H_
#define LED_H_

#include "driver/gpio.h"

#define LED   2

void ledInit();

#endif

led.c


#include "led.h"

void ledInit()
{
  gpio_set_direction(LED, GPIO_MODE_OUTPUT);
  gpio_set_level(LED, 0);    
}

adc.h


#ifndef LED_H_
#define LED_H_

#include "driver/gpio.h"

#define LED   2

void ledInit();

#endif

adc.c


#include "adc.h"

adc_oneshot_unit_handle_t adc_handle;
int adcValue = 0;

void adcInit()
{
  adc_oneshot_unit_init_cfg_t init_config = 
  {
    .unit_id = ADC_UNIT_1,
    .ulp_mode = ADC_ULP_MODE_DISABLE,
  };
  ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle));    
}

void adcGetValue()
{
  ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_5, &adcValue));
}

server.h


#ifndef SERVER_H_
#define SERVER_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.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 "server.h"
#include "esp_gatt_common_api.h"
#include "led.h"

#define GATTS_TABLE_TAG               "MY_SERVER"

#define PROFILE_NUM                   1
#define PROFILE_APP_IDX               0
#define ESP_APP_ID                    0x55
#define SAMPLE_DEVICE_NAME            "MY_SERVER"
#define SVC_INST_ID                   0

#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)

#define CONFIG_SET_RAW_ADV_DATA

enum
{
  IDX_SVC,
  IDX_CHAR_A,
  IDX_CHAR_VAL_A,
  IDX_CHAR_CFG_A,

  IDX_CHAR_B,
  IDX_CHAR_VAL_B,

  IDX_CHAR_C,
  IDX_CHAR_VAL_C,

  HRS_IDX_NB,
};

typedef struct 
{
  uint8_t   *prepare_buf;
  int       prepare_len;
} prepare_type_env_t;

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;
};

extern uint16_t my_handle_table[HRS_IDX_NB];
extern struct gatts_profile_inst my_profile_tab[PROFILE_NUM];

void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);

#endif 

server.c


#include "server.h"

static uint8_t adv_config_done = 0;

uint16_t my_handle_table[HRS_IDX_NB];

static prepare_type_env_t prepare_write_env;

#ifdef CONFIG_SET_RAW_ADV_DATA
static uint8_t raw_adv_data[] = 
{
  0x02, 0x01, 0x06,
  0x02, 0x0a, 0xeb,
  0x03, 0x03, 0xFF, 0x00,
  0x0a, 0x09, 'M', 'Y', '_', 'S', 'E', 'R', 'V', 'E', 'R'
};
static uint8_t raw_scan_rsp_data[] = 
{
  0x02, 0x01, 0x06,
  0x02, 0x0a, 0xeb,
  0x03, 0x03, 0xFF,0x00
};

#else
static uint8_t service_uuid[16] = 
{
  0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};

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),
};

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 */

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 my_profile_tab[PROFILE_NUM] = 
{
  [PROFILE_APP_IDX] = 
  {
    .gatts_cb = gatts_profile_event_handler,
    .gatts_if = ESP_GATT_IF_NONE,
  },
};

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 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_write_notify   = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t heart_measurement_ccc[2]      = {0x00, 0x00};
static const uint8_t char_value[4]                 = {0x11, 0x22, 0x33, 0x44};

static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{
  [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}},

  [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}},

  [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}},

  [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}},

  [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}},

  [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}},

  [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}},

  [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}},
};

void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
  switch (event) 
  {
    #ifdef CONFIG_SET_RAW_ADV_DATA
      case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
      adv_config_done &= (~ADV_CONFIG_FLAG);
      if (adv_config_done == 0)
      {
        esp_ble_gap_start_advertising(&adv_params);
      }
    break;    
    case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
      adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
      if (adv_config_done == 0)
      {
        esp_ble_gap_start_advertising(&adv_params);
      }
    break;
    #else
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
      adv_config_done &= (~ADV_CONFIG_FLAG);
      if (adv_config_done == 0)
      {
        esp_ble_gap_start_advertising(&adv_params);
      }
    break;    
    case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
      adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
      if (adv_config_done == 0)
      {
        esp_ble_gap_start_advertising(&adv_params);
      }
    break;
    #endif
    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
      if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) 
      {
        ESP_LOGE(GATTS_TABLE_TAG, "advertising start failed");
      }
      else
      {
        ESP_LOGI(GATTS_TABLE_TAG, "advertising start successfully");
      }
    break;    
    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
      if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) 
      {
        ESP_LOGE(GATTS_TABLE_TAG, "Advertising stop failed");
      }
      else 
      {
        ESP_LOGI(GATTS_TABLE_TAG, "Stop adv successfully\n");
      }
    break;    
    case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
      ESP_LOGI(GATTS_TABLE_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
      param->update_conn_params.status,
      param->update_conn_params.min_int,
      param->update_conn_params.max_int,
      param->update_conn_params.conn_int,
      param->update_conn_params.latency,
      param->update_conn_params.timeout);
    break;
    default:
    break;
  }
}

void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
{
  ESP_LOGI(GATTS_TABLE_TAG, "prepare write, handle = %d, value len = %d", param->write.handle, param->write.len);
  esp_gatt_status_t status = ESP_GATT_OK;
  if (prepare_write_env->prepare_buf == NULL) 
  {
    prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
    prepare_write_env->prepare_len = 0;
    if (prepare_write_env->prepare_buf == NULL) 
    {
      ESP_LOGE(GATTS_TABLE_TAG, "%s, Gatt_server prep no mem", __func__);
      status = ESP_GATT_NO_RESOURCES;
    }
  } 
  else 
  {
    if(param->write.offset > PREPARE_BUF_MAX_SIZE) 
    {
      status = ESP_GATT_INVALID_OFFSET;
    } 
    else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) 
    {
      status = ESP_GATT_INVALID_ATTR_LEN;
    }
  }
  if (param->write.need_rsp)
  {
    esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
    if (gatt_rsp != NULL)
    {
      gatt_rsp->attr_value.len = param->write.len;
      gatt_rsp->attr_value.handle = param->write.handle;
      gatt_rsp->attr_value.offset = param->write.offset;
      gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
      memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
      esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
      if (response_err != ESP_OK)
      {
        ESP_LOGE(GATTS_TABLE_TAG, "Send response error");
      }
      free(gatt_rsp);
    }
    else
    {
      ESP_LOGE(GATTS_TABLE_TAG, "%s, malloc failed", __func__);
    }
  }  
  if (status != ESP_GATT_OK)
  {
    return;
  }
  memcpy(prepare_write_env->prepare_buf + param->write.offset, param->write.value, param->write.len);
  prepare_write_env->prepare_len += param->write.len;
}

void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
{
  if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC && prepare_write_env->prepare_buf)
  {
    esp_log_buffer_hex(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
  }
  else
  {
    ESP_LOGI(GATTS_TABLE_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
  }
  if (prepare_write_env->prepare_buf) 
  {
    free(prepare_write_env->prepare_buf);
    prepare_write_env->prepare_buf = NULL;
  }
  prepare_write_env->prepare_len = 0;
}

void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
  switch (event) 
  {
    case ESP_GATTS_REG_EVT:
      {
        esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
        if (set_dev_name_ret)
        {
          ESP_LOGE(GATTS_TABLE_TAG, "set device name failed, error code = %x", set_dev_name_ret);
        }
        #ifdef CONFIG_SET_RAW_ADV_DATA
        esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
        if (raw_adv_ret)
        {
          ESP_LOGE(GATTS_TABLE_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
        }
        adv_config_done |= ADV_CONFIG_FLAG;
        esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
        if (raw_scan_ret)
        {
          ESP_LOGE(GATTS_TABLE_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
        }
        adv_config_done |= SCAN_RSP_CONFIG_FLAG;
        #else
        esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
        if (ret)
        {
          ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret);
        }
        adv_config_done |= ADV_CONFIG_FLAG;        
        ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
        if (ret)
        {
          ESP_LOGE(GATTS_TABLE_TAG, "config scan response data failed, error code = %x", ret);
        }
        adv_config_done |= SCAN_RSP_CONFIG_FLAG;
        #endif
        esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
        if (create_attr_ret)
        {
          ESP_LOGE(GATTS_TABLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
        }
      }
    break;    
    case ESP_GATTS_READ_EVT:
      ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT");
    break;    
    case ESP_GATTS_WRITE_EVT:
      if (!param->write.is_prep)
      {
        gpio_set_level(LED, param->write.value[0]);
        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 (my_handle_table[IDX_CHAR_CFG_A] == 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, "notify enable");
            uint8_t notify_data[15];
            esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, my_handle_table[IDX_CHAR_VAL_A],
            sizeof(notify_data), notify_data, false);
          }
          else if (descr_value == 0x0002)
          {
            ESP_LOGI(GATTS_TABLE_TAG, "indicate enable");
            uint8_t indicate_data[15];            
            esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, my_handle_table[IDX_CHAR_VAL_A],
            sizeof(indicate_data), indicate_data, true);
          }
          else if (descr_value == 0x0000)
          {
            ESP_LOGI(GATTS_TABLE_TAG, "notify/indicate disable ");
          }
          else
          {
            ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value");
            esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len);
          }

        }
        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
      {
        example_prepare_write_event_env(gatts_if, &prepare_write_env, param);
      }
    break;    
    case ESP_GATTS_EXEC_WRITE_EVT:
      ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT");
      example_exec_write_event_env(&prepare_write_env, param);
    break;    
    case ESP_GATTS_MTU_EVT:
      ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
    break;    
    case ESP_GATTS_CONF_EVT:
      ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONF_EVT, status = %d, attr_handle %d", param->conf.status, param->conf.handle);
    break;    
    case ESP_GATTS_START_EVT:
      ESP_LOGI(GATTS_TABLE_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
    break;    
    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));      
      conn_params.latency = 0;
      conn_params.max_int = 0x20; 
      conn_params.min_int = 0x10; 
      conn_params.timeout = 400;  
      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);
      esp_ble_gap_start_advertising(&adv_params);
    break;    
    case ESP_GATTS_CREAT_ATTR_TAB_EVT:
    {
      if (param->add_attr_tab.status != ESP_GATT_OK)
      {
        ESP_LOGE(GATTS_TABLE_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
      }
      else if (param->add_attr_tab.num_handle != HRS_IDX_NB)
      {
        ESP_LOGE(GATTS_TABLE_TAG, "create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, HRS_IDX_NB);
      }
      else 
      {
        ESP_LOGI(GATTS_TABLE_TAG, "create attribute table successfully, the number handle = %d\n",param->add_attr_tab.num_handle);
        memcpy(my_handle_table, param->add_attr_tab.handles, sizeof(my_handle_table));
        esp_ble_gatts_start_service(my_handle_table[IDX_SVC]);
      }
      break;
    }
    case ESP_GATTS_STOP_EVT:
    case ESP_GATTS_OPEN_EVT:
    case ESP_GATTS_CANCEL_OPEN_EVT:
    case ESP_GATTS_CLOSE_EVT:
    case ESP_GATTS_LISTEN_EVT:
    case ESP_GATTS_CONGEST_EVT:
    case ESP_GATTS_UNREG_EVT:
    case ESP_GATTS_DELETE_EVT:
    default:
    break;
  }
}


void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
  if (event == ESP_GATTS_REG_EVT) 
  {
    if (param->reg.status == ESP_GATT_OK) 
    {
      my_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
    } 
    else 
    {
      ESP_LOGE(GATTS_TABLE_TAG, "reg app failed, app_id %04x, status %d",
      param->reg.app_id,
      param->reg.status);
      return;
    }
  }
  do 
  {
    int idx;
    for (idx = 0; idx < PROFILE_NUM; idx++) 
    {
      if (gatts_if == ESP_GATT_IF_NONE || gatts_if == my_profile_tab[idx].gatts_if) 
      {
        if (my_profile_tab[idx].gatts_cb) 
        {
          my_profile_tab[idx].gatts_cb(event, gatts_if, param);
        }
      }
    }
  } while (0);
}

main.c


#include "server.h"
#include "led.h"
#include "adc.h"

TaskHandle_t myTaskHandle = NULL;
uint8_t notifyData[15] = {0};

void myTask(void *arg)
{
  while(1)
  {       
    adcGetValue();
    uint8_t thouthands = (uint8_t)(adcValue / 1000);
    uint8_t hundreds = (uint8_t)((adcValue - thouthands * 1000) / 100);
    uint8_t tens = (uint8_t)((adcValue - thouthands * 1000 - hundreds * 100) / 10);
    uint8_t units = (uint8_t)(adcValue - thouthands * 1000 - hundreds * 100 - tens * 10);
    notifyData[0] = thouthands;
    notifyData[1] = hundreds;
    notifyData[2] = tens;
    notifyData[3] = units;
    esp_ble_gatts_send_indicate(my_profile_tab[PROFILE_APP_IDX].gatts_if, 0, my_handle_table[IDX_CHAR_VAL_A], sizeof(notifyData), notifyData, false);
    vTaskDelay(200 / portTICK_PERIOD_MS);
  }
}

void app_main(void)
{
  ledInit();
  adcInit();
  xTaskCreate(myTask, "My_Task", 4096, NULL, 10, &myTaskHandle);
  esp_err_t ret;
  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;
  }
  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;
  }
  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;
  }
  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;
  }
  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);
  }    
}

Android-приложение


AndroidManifest

включает разрешения на работу с Bluetooth

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.Video"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

определяет параметры следующих элементов графического интерфейса:
• Кнопки сканирования широковещательных устройств, соединения и разъединения с сервером, включения и выключения светодиода.
• Индикатор (ProgressBar) текущего значения напряжения.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/scanBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="scan"
        android:text="scan"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.049"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.046" />

    <Button
        android:id="@+id/connectBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="connect"
        android:text="connect"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.046" />

    <Button
        android:id="@+id/disconnectBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="disconnect"
        android:text="disconnect"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.046" />

    <Button
        android:id="@+id/ledOnbtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="ledOn"
        android:text="on"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.95"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.046" />

    <Button
        android:id="@+id/ledOffbtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="ledOff"
        android:text="off"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.95"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.046" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:layout_width="300dp"
        android:layout_height="30dp"
        android:max="4095"
        android:progressBackgroundTint="@color/light_gray"
        android:progressTint="@color/purple_500"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.495"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.186" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

реализует следующие функции:
а) Поиск BLE-устройства с заданным именем.
б) Соединение с вышеуказанным устройством.
в) Запись по нажатию кнопок «ON» или «OFF» значения 1 или 0 характеристики, ответственной за состояние светодиода.
г) Сборка полученных байтов с тысячами, сотнями, десятками и единицами в единое значение напряжение с отражением его на индикаторе.

package com.punchthrough.video;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {
    private String TAG = "MainActivity";
    public static final UUID SERVICE_UUID = UUID.fromString("000000ff-0000-1000-8000-00805f9b34fb");
    public static final UUID CHARACTERISTIC_UUID = UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb");
    public static final UUID DESCRIPTOR_UUID = UUID.fromString("0002902-0000-1000-8000-00805f9b34fb");
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
    BluetoothDevice device;
    BluetoothGatt bluetoothGatt;
    Button scanButton, connectBtn, disconnectBtn, ledOnBtn, ledOffBtn;
    ProgressBar progressBar;
    String[] names = new String[]{"MY_SERVER"};
    List<ScanFilter> filters = null;
    BluetoothGattCharacteristic gattCharacteristic;
    BluetoothGattDescriptor descriptor;
    private final static int REQUEST_ENABLE_BT = 1;
    private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;


    ScanSettings scanSettings = new ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
            .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
            .setReportDelay(0L)
            .build();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scanButton = findViewById(R.id.scanBtn);
        connectBtn = findViewById(R.id.connectBtn);
        connectBtn.setEnabled(false);
        disconnectBtn = findViewById(R.id.disconnectBtn);
        disconnectBtn.setEnabled(false);
        disconnectBtn.setVisibility(View.INVISIBLE);
        ledOnBtn = findViewById(R.id.ledOnbtn);
        ledOnBtn.setEnabled(false);
        ledOffBtn = findViewById(R.id.ledOffbtn);
        ledOffBtn.setVisibility(View.INVISIBLE);
        progressBar = findViewById(R.id.progressBar);

        if (names != null) {
            filters = new ArrayList<>();
            for (String name : names) {
                ScanFilter filter = new ScanFilter.Builder()
                        .setDeviceName(name)
                        .build();
                filters.add(filter);
            }
        }

        if (adapter != null && !adapter.isEnabled()) {
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
            }
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
        }

        if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            final AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("This app needs location access");
            builder.setMessage("Please grant location access so this app can detect peripherals.");
            builder.setPositiveButton(android.R.string.ok, null);
            builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
                @Override
                public void onDismiss(DialogInterface dialog) {
                    requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
                }
            });
            builder.show();
        }
    }

    public void scan(View view) {
        if (scanner != null) {
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
            }
            scanner.startScan(filters, scanSettings, scanCallback);
            Toast.makeText(this, "scanning...", Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(this, "could not get scanner object", Toast.LENGTH_LONG).show();
        }
    }

    public void connect(View view) {
        Toast.makeText(this, "connecting...", Toast.LENGTH_LONG).show();
        if (scanner != null) {
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
            }
            bluetoothGatt = device.connectGatt(MainActivity.this, false, btleGattCallback);
            ledOnBtn.setEnabled(true);
            ledOffBtn.setEnabled(true);
        } else {
            Toast.makeText(this, "could not connect", Toast.LENGTH_LONG).show();
        }
    }

    public void disconnect(View view) {
        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
        }

        bluetoothGatt.disconnect();

        ledOnBtn.setEnabled(false);
        ledOffBtn.setEnabled(false);
        connectBtn.setEnabled(false);
        progressBar.setProgress(0);
    }

    public void ledOn(View view) {
        byte[] value = new byte[1];
        value[0] = (0x01);
        BluetoothGattService mCustomService = bluetoothGatt.getService(UUID.fromString("000000ff-0000-1000-8000-00805f9b34fb"));
        if(mCustomService == null){
            Log.i(TAG, "Custom BLE Service not found");
            return;
        }
        BluetoothGattCharacteristic characteristic = mCustomService.getCharacteristic(UUID.fromString("0000ff03-0000-1000-8000-00805f9b34fb"));
        characteristic.setValue(value);

        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
        }
        bluetoothGatt.writeCharacteristic(characteristic);

        ledOnBtn.setVisibility(View.INVISIBLE);
        ledOffBtn.setVisibility(View.VISIBLE);
    }

    public void ledOff(View view) {
        byte[] value = new byte[1];
        value[0] = (0x00);
        BluetoothGattService mCustomService = bluetoothGatt.getService(UUID.fromString("000000ff-0000-1000-8000-00805f9b34fb"));
        if(mCustomService == null){
            Log.i(TAG, "Custom BLE Service not found");
            return;
        }
        BluetoothGattCharacteristic characteristic = mCustomService.getCharacteristic(UUID.fromString("0000ff03-0000-1000-8000-00805f9b34fb"));
        characteristic.setValue(value);

        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
        }
        bluetoothGatt.writeCharacteristic(characteristic);

        ledOnBtn.setVisibility(View.VISIBLE);
        ledOffBtn.setVisibility(View.INVISIBLE);
    }

    private final ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            device = result.getDevice();
            Toast.makeText(MainActivity.this, "device with address" + "\n" + device.getAddress() + "\n" + "found", Toast.LENGTH_LONG).show();
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
            }
            scanner.stopScan(scanCallback);

            connectBtn.setEnabled(true);
            disconnectBtn.setEnabled(true);
            ledOnBtn.setEnabled(true);
        }
    };

    private final BluetoothGattCallback btleGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
            System.out.println(newState);
            switch (newState) {
                case 0:
                    MainActivity.this.runOnUiThread(new Runnable() {
                        public void run() {
                            Toast.makeText(MainActivity.this, "device disconnected", Toast.LENGTH_SHORT).show();
                            connectBtn.setVisibility(View.VISIBLE);
                            disconnectBtn.setVisibility(View.INVISIBLE);
                        }
                    });
                    break;
                case 2:
                    MainActivity.this.runOnUiThread(new Runnable() {
                        public void run() {
                            Toast.makeText(MainActivity.this, "device connected", Toast.LENGTH_SHORT).show();
                            connectBtn.setVisibility(View.INVISIBLE);
                            disconnectBtn.setVisibility(View.VISIBLE);
                        }
                    });

                    if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
                    }
                    bluetoothGatt.discoverServices();

                    break;
                default:
                    MainActivity.this.runOnUiThread(new Runnable() {
                        public void run() {
                            Log.i(TAG, "we encounterned an unknown state, uh oh\n");
                        }
                    });
                    break;
            }
        }

        @Override
        public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
            MainActivity.this.runOnUiThread(new Runnable() {
                public void run() {
                    Log.i(TAG, "device services have been discovered\n");
                }
            });
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
            }
            gattCharacteristic = gatt.getService(SERVICE_UUID).getCharacteristic(CHARACTERISTIC_UUID);
            gatt.setCharacteristicNotification(gattCharacteristic, true);

            descriptor = gattCharacteristic.getDescriptor(DESCRIPTOR_UUID);
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);

            gatt.writeDescriptor(descriptor);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {

            byte[] data = characteristic.getValue();

            MainActivity.this.runOnUiThread(new Runnable() {
                @SuppressLint("SetTextI18n")
                public void run() {
                    progressBar.setProgress(data[0] * 1000 + data[1] * 100 + data[2] * 10 + data[3]);
                }
            });
        }
    };
}


Файлы

🎁esp32-android.zip  23.24 Mb ⇣ 21

Спасибо за внимание!
Продолжение следует.

Камрад, рассмотри датагорские рекомендации

🌼 Полезные и проверенные железяки, можно брать

Опробовано в лаборатории редакции или читателями.




 

Читательское голосование

Нравится

Статью одобрили 9 читателей.

Для участия в голосовании зарегистрируйтесь и войдите на сайт с вашими логином и паролем.
 

Поделись с друзьями!

 

 

Связанные материалы

 

Схема на Датагоре. Новая статья Обмен данными между Android-приложением и nRF52832. Часть 2. BLE... Приветствую вас, уважаемые датагорцы! Сегодня — обмен данными между Android-приложением и nRF52832...
Схема на Датагоре. Новая статья Обмен данными между смартфоном и ESP32. Android. BLE. GAP. Часть 1... Привет, карады-датагорцы! Эта моя статья открывает серию, посвящённую обмену данными между ESP32 и...
Схема на Датагоре. Новая статья Обмен данными между Android-приложением и nRF52832. Часть 3. Широковещание... Всем привет! Во второй части статьи был приведён пример коммуникаций между Android и nRF52832 при...
Схема на Датагоре. Новая статья Обмен данными между Android-приложением и nRF52832. Часть 1. Serial... Здравствуйте! Представляю вашему вниманию проект обмена данными между Android-приложением и SoC...
Схема на Датагоре. Новая статья Ассемблер для микроконтроллера с нуля. Часть 6. Протоколы обмена данными I2C и SPI... В проекте из предыдущей части нашей ассемблерной эпопеи мы подключали к микроконтроллеру светодиод...
Схема на Датагоре. Новая статья Визуализация для микроконтроллера. Часть 4. Android... Вообще то я планировал рассказать сегодня про дисплей на базе ILI9481. Однако, он настолько похож...
Схема на Датагоре. Новая статья На русском: Bluetooth Low Energy (BLE). Bluetooth Core Specification 4.2. GATT... Приветствую всех жителей и гостей Датагор.ру! Давно планировал написать статью по Bluetooth Low...
Схема на Датагоре. Новая статья Обмен данными между Java-приложением и МК. Часть 1. По проводу, USB-UART... Приветствую всех жителей и гостей кибер-города Датагор! Работа устройства на базе микроконтроллера...
Схема на Датагоре. Новая статья Визуализация для микроконтроллера. Часть 5. Графика... В предыдущих частях статьи нами более или менее подробно были рассмотрены основные принципы работы...
Схема на Датагоре. Новая статья Машинка с пропорциональным управлением по Bluetooth из-под Android... Не будем покупать плохие игрушки у Китайцев, а купим у них дешевый конструктор-шасси, несколько...
Схема на Датагоре. Новая статья Обмен данными между Java-приложением и МК. Часть 2. По воздуху, Bluetooth... Привет всем датагорчанам-любителям МК! Сегодня мы рассмотрим двусторонний обмен между...
Схема на Датагоре. Новая статья Электронные часы-термометр с беспроводным датчиком через радиомодуль nRF24L01... Здравствуйте, уважаемые Датагорцы! Представляю вашему вниманию электронные часы с функцией...
 

Комментарии, вопросы, ответы, дополнения, отзывы

 

Добавить комментарий, вопрос, отзыв 💬

Камрады, будьте дружелюбны, соблюдайте правила!

  • Смайлы и люди
    Животные и природа
    Еда и напитки
    Активность
    Путешествия и места
    Предметы
    Символы
    Флаги
 
 
В начало | Зарегистрироваться | Заказать наши киты почтой