添加智能灯固件代码

This commit is contained in:
kerwincui
2021-07-13 17:14:51 +08:00
parent 332f74dd17
commit ecc0b91b8b
2568 changed files with 229441 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
build/
.settings
.config
*.o
*.pyc
*.d
*.old
*.cproject
# vscode setting
.vscode/
# eclipse setting
.settings
# MacOS directory files
.DS_Store
# Doc build artifacts
docs/*/_build/
docs/*/doxygen-warning-log.txt
docs/*/sphinx-warning-log.txt
docs/*/sphinx-warning-log-sanitized.txt
docs/*/xml/
docs/*/xml_in/
docs/*/man/
docs/doxygen_sqlite3.db
# Example project files
examples/**/sdkconfig
examples/**/sdkconfig.old
examples/**/build
# Unit test app files
tools/unit-test-app/sdkconfig
tools/unit-test-app/sdkconfig.old
tools/unit-test-app/build
tools/unit-test-app/builds
tools/unit-test-app/output
# ESP-IDF library
build

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(wumei-open)
target_add_binary_data(${CMAKE_PROJECT_NAME}.elf "main/client.crt" TEXT)
target_add_binary_data(${CMAKE_PROJECT_NAME}.elf "main/client.key" TEXT)

View File

@@ -0,0 +1,2 @@
PROJECT_NAME := empty_project
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,5 @@
#### ESP-IDF空项目提供基本的框架。<br />

View File

@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS "include" )

View File

@@ -0,0 +1,19 @@
menu "Bus Options"
menu "I2C Bus Options"
config I2C_BUS_DYNAMIC_CONFIG
bool "enable dynamic configuration"
default y
help
If enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer,
hence multiple devices with different configs on a single bus can be supported.
config I2C_MS_TO_WAIT
int "mutex block time"
default 200
range 50 5000
help
task block time when try to take the bus, unit:milliseconds
endmenu
endmenu

View File

@@ -0,0 +1,7 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := .

View File

@@ -0,0 +1,487 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "i2c_bus.h"
#define I2C_ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define I2C_ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define I2C_BUS_FLG_DEFAULT (0)
#define I2C_BUS_MASTER_BUF_LEN (0)
#define I2C_BUS_MS_TO_WAIT CONFIG_I2C_MS_TO_WAIT
#define I2C_BUS_TICKS_TO_WAIT (I2C_BUS_MS_TO_WAIT/portTICK_RATE_MS)
#define I2C_BUS_MUTEX_TICKS_TO_WAIT (I2C_BUS_MS_TO_WAIT/portTICK_RATE_MS)
typedef struct {
i2c_port_t i2c_port; /*!<I2C port number */
bool is_init; /*if bus is initialized*/
i2c_config_t conf_active; /*!<I2C active configuration */
SemaphoreHandle_t mutex; /* mutex to achive thread-safe*/
int32_t ref_counter; /*reference count*/
} i2c_bus_t;
typedef struct {
uint8_t dev_addr; /*device address*/
i2c_config_t conf; /*!<I2C active configuration */
i2c_bus_t *i2c_bus; /*!<I2C bus*/
} i2c_bus_device_t;
static const char *TAG = "i2c_bus";
static i2c_bus_t s_i2c_bus[I2C_NUM_MAX];
#define I2C_BUS_CHECK(a, str, ret) if(!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
return (ret); \
}
#define I2C_BUS_CHECK_GOTO(a, str, lable) if(!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
goto lable; \
}
#define I2C_BUS_INIT_CHECK(is_init, ret) if(!is_init) { \
ESP_LOGE(TAG,"%s:%d (%s):i2c_bus has not inited", __FILE__, __LINE__, __FUNCTION__); \
return (ret); \
}
#define I2C_BUS_MUTEX_TAKE(mutex, ret) if (!xSemaphoreTake(mutex, I2C_BUS_MUTEX_TICKS_TO_WAIT)) { \
ESP_LOGE(TAG, "i2c_bus take mutex timeout, max wait = %d ms", I2C_BUS_MUTEX_TICKS_TO_WAIT); \
return (ret); \
}
#define I2C_BUS_MUTEX_TAKE_MAX_DELAY(mutex, ret) if (!xSemaphoreTake(mutex, portMAX_DELAY)) { \
ESP_LOGE(TAG, "i2c_bus take mutex timeout, max wait = %d ms", portMAX_DELAY); \
return (ret); \
}
#define I2C_BUS_MUTEX_GIVE(mutex, ret) if (!xSemaphoreGive(mutex)) { \
ESP_LOGE(TAG, "i2c_bus give mutex failed"); \
return (ret); \
}
static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf);
static esp_err_t i2c_driver_deinit(i2c_port_t port);
static esp_err_t i2c_bus_write_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data);
static esp_err_t i2c_bus_read_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data);
inline static bool i2c_config_compare(i2c_port_t port, const i2c_config_t *conf);
/**************************************** Public Functions (Application level)*********************************************/
i2c_bus_handle_t i2c_bus_create(i2c_port_t port, const i2c_config_t *conf)
{
I2C_BUS_CHECK(port < I2C_NUM_MAX, "I2C port error", NULL);
I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", NULL);
I2C_BUS_CHECK(conf->mode == I2C_MODE_MASTER, "i2c_bus only supports master mode", NULL);
if (s_i2c_bus[port].is_init) {
/**if i2c_bus has been inited and configs not changed, return the handle directly**/
if (i2c_config_compare(port, conf)) {
ESP_LOGW(TAG, "i2c%d has been inited, return handle directly, ref_counter=%d", port, s_i2c_bus[port].ref_counter);
return (i2c_bus_handle_t)&s_i2c_bus[port];
}
} else {
s_i2c_bus[port].mutex = xSemaphoreCreateMutex();
I2C_BUS_CHECK(s_i2c_bus[port].mutex != NULL, "i2c_bus xSemaphoreCreateMutex failed", NULL);
s_i2c_bus[port].ref_counter = 0;
}
esp_err_t ret = i2c_driver_reinit(port, conf);
I2C_BUS_CHECK(ret == ESP_OK, "init error", NULL);
s_i2c_bus[port].conf_active = *conf;
s_i2c_bus[port].i2c_port = port;
return (i2c_bus_handle_t)&s_i2c_bus[port];
}
esp_err_t i2c_bus_delete(i2c_bus_handle_t *p_bus)
{
I2C_BUS_CHECK(p_bus != NULL && *p_bus != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG);
i2c_bus_t *i2c_bus = (i2c_bus_t *)(*p_bus);
I2C_BUS_INIT_CHECK(i2c_bus->is_init, ESP_FAIL);
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, ESP_ERR_TIMEOUT);
/** if ref_counter == 0, de-init the bus**/
if ((i2c_bus->ref_counter) > 0) {
ESP_LOGW(TAG, "i2c%d is also handled by others ref_counter=%u, won't be de-inited", i2c_bus->i2c_port, i2c_bus->ref_counter);
return ESP_OK;
}
esp_err_t ret = i2c_driver_deinit(i2c_bus->i2c_port);
I2C_BUS_CHECK(ret == ESP_OK, "deinit error", ret);
vSemaphoreDelete(i2c_bus->mutex);
*p_bus = NULL;
return ESP_OK;
}
uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num)
{
I2C_BUS_CHECK(bus_handle != NULL, "Handle error", 0);
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle;
I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0);
uint8_t device_count = 0;
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, 0);
for (uint8_t dev_address = 1; dev_address < 127; dev_address++) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_address << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "found i2c device address = 0x%02x", dev_address);
if (buf != NULL && device_count < num) {
*(buf + device_count) = dev_address;
}
device_count++;
}
i2c_cmd_link_delete(cmd);
}
I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, 0);
return device_count;
}
uint32_t i2c_bus_get_current_clk_speed(i2c_bus_handle_t bus_handle)
{
I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", 0);
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle;
I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0);
return i2c_bus->conf_active.master.clk_speed;
}
uint8_t i2c_bus_get_created_device_num(i2c_bus_handle_t bus_handle)
{
I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", 0);
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle;
I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0);
return i2c_bus->ref_counter;
}
i2c_bus_device_handle_t i2c_bus_device_create(i2c_bus_handle_t bus_handle, uint8_t dev_addr, uint32_t clk_speed)
{
I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", NULL);
I2C_BUS_CHECK(clk_speed <= 400000, "clk_speed must <= 400000", NULL);
i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle;
I2C_BUS_INIT_CHECK(i2c_bus->is_init, NULL);
i2c_bus_device_t *i2c_device = calloc(1, sizeof(i2c_bus_device_t));
I2C_BUS_CHECK(i2c_device != NULL, "calloc memory failed", NULL);
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, NULL);
i2c_device->dev_addr = dev_addr;
i2c_device->conf = i2c_bus->conf_active;
/*if clk_speed == 0, current active clock speed will be used, else set a specified value*/
if (clk_speed != 0) {
i2c_device->conf.master.clk_speed = clk_speed;
}
i2c_device->i2c_bus = i2c_bus;
i2c_bus->ref_counter++;
I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, NULL);
return (i2c_bus_device_handle_t)i2c_device;
}
esp_err_t i2c_bus_device_delete(i2c_bus_device_handle_t *p_dev_handle)
{
I2C_BUS_CHECK(p_dev_handle != NULL && *p_dev_handle != NULL, "Null Device Handle", ESP_ERR_INVALID_ARG);
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)(*p_dev_handle);
I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT);
i2c_device->i2c_bus->ref_counter--;
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL);
free(i2c_device);
*p_dev_handle = NULL;
return ESP_OK;
}
uint8_t i2c_bus_device_get_address(i2c_bus_device_handle_t dev_handle)
{
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", NULL_I2C_DEV_ADDR);
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle;
return i2c_device->dev_addr;
}
esp_err_t i2c_bus_read_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data)
{
return i2c_bus_read_reg8(dev_handle, mem_address, data_len, data);
}
esp_err_t i2c_bus_read_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t *data)
{
return i2c_bus_read_reg8(dev_handle, mem_address, 1, data);
}
esp_err_t i2c_bus_read_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t *data)
{
uint8_t byte = 0;
esp_err_t ret = i2c_bus_read_reg8(dev_handle, mem_address, 1, &byte);
*data = byte & (1 << bit_num);
*data = (*data != 0) ? 1 : 0;
return ret;
}
esp_err_t i2c_bus_read_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t *data)
{
uint8_t byte = 0;
esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte);
if (ret != ESP_OK) {
return ret;
}
uint8_t mask = ((1 << length) - 1) << (bit_start - length + 1);
byte &= mask;
byte >>= (bit_start - length + 1);
*data = byte;
return ret;
}
esp_err_t i2c_bus_write_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t data)
{
return i2c_bus_write_reg8(dev_handle, mem_address, 1, &data);
}
esp_err_t i2c_bus_write_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data)
{
return i2c_bus_write_reg8(dev_handle, mem_address, data_len, data);
}
esp_err_t i2c_bus_write_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t data)
{
uint8_t byte = 0;
esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte);
if (ret != ESP_OK) {
return ret;
}
byte = (data != 0) ? (byte | (1 << bit_num)) : (byte & ~(1 << bit_num));
return i2c_bus_write_byte(dev_handle, mem_address, byte);
}
esp_err_t i2c_bus_write_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t data)
{
uint8_t byte = 0;
esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte);
if (ret != ESP_OK) {
return ret;
}
uint8_t mask = ((1 << length) - 1) << (bit_start - length + 1);
data <<= (bit_start - length + 1); // shift data into correct position
data &= mask; // zero all non-important bits in data
byte &= ~(mask); // zero all important bits in existing byte
byte |= data; // combine data with existing byte
return i2c_bus_write_byte(dev_handle, mem_address, byte);
}
/**
* @brief I2C master send queued commands.
* This function will trigger sending all queued commands.
* The task will be blocked until all the commands have been sent out.
* If I2C_BUS_DYNAMIC_CONFIG enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer,
* hence multiple devices with different configs on a single bus can be supported.
* @note
* Only call this function in I2C master mode
*
* @param i2c_num I2C port number
* @param cmd_handle I2C command handler
* @param ticks_to_wait maximum wait ticks.
* @param conf pointer to I2C parameter settings
* @return esp_err_t
*/
inline static esp_err_t i2c_master_cmd_begin_with_conf(i2c_port_t i2c_num, i2c_cmd_handle_t cmd_handle, TickType_t ticks_to_wait, const i2c_config_t *conf)
{
esp_err_t ret;
#ifdef CONFIG_I2C_BUS_DYNAMIC_CONFIG
/*if configs changed, i2c driver will reinit with new configuration*/
if (conf != NULL && false == i2c_config_compare(i2c_num, conf)) {
ret = i2c_driver_reinit(i2c_num, conf);
I2C_BUS_CHECK(ret == ESP_OK, "reinit error", ret);
s_i2c_bus[i2c_num].conf_active = *conf;
}
#endif
ret = i2c_master_cmd_begin(i2c_num, cmd_handle, ticks_to_wait);
return ret;
}
/**************************************** Public Functions (Low level)*********************************************/
esp_err_t i2c_bus_cmd_begin(i2c_bus_device_handle_t dev_handle, i2c_cmd_handle_t cmd)
{
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG);
I2C_BUS_CHECK(cmd != NULL, "I2C command error", ESP_ERR_INVALID_ARG);
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle;
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE);
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT);
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf);
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL);
return ret;
}
static esp_err_t i2c_bus_read_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data)
{
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG);
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG);
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle;
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE);
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT);
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
if (mem_address != NULL_I2C_MEM_ADDR) {
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN);
i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN);
}
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN);
i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf);
i2c_cmd_link_delete(cmd);
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL);
return ret;
}
esp_err_t i2c_bus_read_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, uint8_t *data)
{
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG);
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG);
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle;
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE);
uint8_t memAddress8[2];
memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF);
memAddress8[1] = (uint8_t)(mem_address & 0x00FF);
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT);
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
if (mem_address != NULL_I2C_MEM_ADDR) {
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN);
i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN);
}
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN);
i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf);
i2c_cmd_link_delete(cmd);
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL);
return ret;
}
static esp_err_t i2c_bus_write_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data)
{
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG);
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG);
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle;
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE);
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT);
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN);
if (mem_address != NULL_I2C_MEM_ADDR) {
i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN);
}
i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf);
i2c_cmd_link_delete(cmd);
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL);
return ret;
}
esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, const uint8_t *data)
{
I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG);
I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG);
i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle;
I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE);
uint8_t memAddress8[2];
memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF);
memAddress8[1] = (uint8_t)(mem_address & 0x00FF);
I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT);
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN);
if (mem_address != NULL_I2C_MEM_ADDR) {
i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN);
}
i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf);
i2c_cmd_link_delete(cmd);
I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL);
return ret;
}
/**************************************** Private Functions*********************************************/
static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf)
{
I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG);
I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG);
if (s_i2c_bus[port].is_init) {
i2c_driver_delete(port);
s_i2c_bus[port].is_init = false;
ESP_LOGI(TAG, "i2c%d bus deinited", port);
}
esp_err_t ret = i2c_param_config(port, conf);
I2C_BUS_CHECK(ret == ESP_OK, "i2c param config failed", ret);
ret = i2c_driver_install(port, conf->mode, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_FLG_DEFAULT);
I2C_BUS_CHECK(ret == ESP_OK, "i2c driver install failed", ret);
s_i2c_bus[port].is_init = true;
ESP_LOGI(TAG, "i2c%d bus inited", port);
return ESP_OK;
}
static esp_err_t i2c_driver_deinit(i2c_port_t port)
{
I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG);
I2C_BUS_CHECK(s_i2c_bus[port].is_init == true, "i2c not inited", ESP_ERR_INVALID_STATE);
i2c_driver_delete(port); //always return ESP_OK
s_i2c_bus[port].is_init = false;
ESP_LOGI(TAG,"i2c%d bus deinited",port);
return ESP_OK;
}
/**
* @brief compare with active i2c_bus configuration
*
* @param port choose which i2c_port's configuration will be compared
* @param conf new configuration
* @return true new configuration is equal to active configuration
* @return false new configuration is not equal to active configuration
*/
inline static bool i2c_config_compare(i2c_port_t port, const i2c_config_t *conf)
{
if (s_i2c_bus[port].conf_active.master.clk_speed == conf->master.clk_speed
&& s_i2c_bus[port].conf_active.sda_io_num == conf->sda_io_num
&& s_i2c_bus[port].conf_active.scl_io_num == conf->scl_io_num
&& s_i2c_bus[port].conf_active.scl_pullup_en == conf->scl_pullup_en
&& s_i2c_bus[port].conf_active.sda_pullup_en == conf->sda_pullup_en) {
return true;
}
return false;
}

View File

@@ -0,0 +1,599 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_heap_caps.h"
#include "esp32/rom/lldesc.h"
#include "soc/dport_access.h"
#include "soc/dport_reg.h"
#include "soc/i2s_struct.h"
#include "hal/gpio_ll.h"
#include "esp_log.h"
#include "i2s_lcd_driver.h"
static const char *TAG = "ESP32_I2S_LCD";
#define I2S_CHECK(a, str, ret) if (!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
return (ret); \
}
#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4000) // 4-byte aligned
#define LCD_DATA_MAX_WIDTH (24) /*!< Maximum width of LCD data bus */
typedef struct {
uint32_t dma_buffer_size;
uint32_t dma_half_buffer_size;
uint32_t dma_node_buffer_size;
uint32_t dma_node_cnt;
uint32_t dma_half_node_cnt;
lldesc_t *dma;
uint8_t *dma_buffer;
QueueHandle_t event_queue;
uint8_t width;
bool swap_data;
} lcd_obj_t;
typedef struct {
lcd_obj_t lcd;
intr_handle_t lcd_cam_intr_handle;
i2s_dev_t *i2s_dev;
} lcd_cam_obj_t;
typedef struct {
void (*i2s_write_data_func)(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len);
int rs_io_num;
lcd_cam_obj_t *lcd_cam_obj;
SemaphoreHandle_t mutex;
} i2s_lcd_driver_t;
static void IRAM_ATTR i2s_isr(void *arg)
{
BaseType_t HPTaskAwoken = pdFALSE;
lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t *)arg;
i2s_dev_t *i2s_dev = lcd_cam_obj->i2s_dev;
typeof(i2s_dev->int_st) status = i2s_dev->int_st;
i2s_dev->int_clr.val = status.val;
if (status.val == 0) {
return;
}
if (status.out_eof) {
xQueueSendFromISR(lcd_cam_obj->lcd.event_queue, (void *)&status.val, &HPTaskAwoken);
}
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
static void lcd_dma_set_int(lcd_cam_obj_t *lcd_cam_obj)
{
// Generate a data DMA linked list
for (int x = 0; x < lcd_cam_obj->lcd.dma_node_cnt; x++) {
lcd_cam_obj->lcd.dma[x].size = lcd_cam_obj->lcd.dma_node_buffer_size;
lcd_cam_obj->lcd.dma[x].length = lcd_cam_obj->lcd.dma_node_buffer_size;
lcd_cam_obj->lcd.dma[x].buf = (lcd_cam_obj->lcd.dma_buffer + lcd_cam_obj->lcd.dma_node_buffer_size * x);
lcd_cam_obj->lcd.dma[x].eof = !((x + 1) % lcd_cam_obj->lcd.dma_half_node_cnt);
lcd_cam_obj->lcd.dma[x].empty = (uint32_t)&lcd_cam_obj->lcd.dma[(x + 1) % lcd_cam_obj->lcd.dma_node_cnt];
}
lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_half_node_cnt - 1].empty = (uint32_t)NULL;
lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_node_cnt - 1].empty = (uint32_t)NULL;
}
static void lcd_dma_set_left(lcd_cam_obj_t *lcd_cam_obj, int pos, size_t len)
{
int end_pos = 0, size = 0;
// Processing data length is an integer multiple of lcd_cam_obj->lcd.dma_node_buffer_size
if (len % lcd_cam_obj->lcd.dma_node_buffer_size) {
end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size;
size = len % lcd_cam_obj->lcd.dma_node_buffer_size;
} else {
end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size - 1;
size = lcd_cam_obj->lcd.dma_node_buffer_size;
}
// Process the tail node to make it a DMA tail
lcd_cam_obj->lcd.dma[end_pos].size = size;
lcd_cam_obj->lcd.dma[end_pos].length = size;
lcd_cam_obj->lcd.dma[end_pos].eof = 1;
lcd_cam_obj->lcd.dma[end_pos].empty = (uint32_t)NULL;
}
static void lcd_i2s_start(i2s_dev_t *i2s_dev, uint8_t fifo_mode, uint32_t addr, size_t len)
{
while (!i2s_dev->state.tx_idle);
i2s_dev->fifo_conf.tx_fifo_mod = fifo_mode;
i2s_dev->conf.tx_start = 0;
i2s_dev->conf.tx_reset = 1;
i2s_dev->conf.tx_reset = 0;
i2s_dev->lc_conf.out_rst = 1;
i2s_dev->lc_conf.out_rst = 0;
i2s_dev->conf.tx_fifo_reset = 1;
i2s_dev->conf.tx_fifo_reset = 0;
i2s_dev->out_link.addr = addr;
i2s_dev->out_link.start = 1;
ets_delay_us(1);
i2s_dev->conf.tx_start = 1;
}
static void i2s_write_8bit_data(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len)
{
int event = 0;
int x = 0, y = 0, left = 0, cnt = 0;
if (len <= 0) {
ESP_LOGE(TAG, "wrong len!");
return;
}
len = len * 2;
lcd_dma_set_int(lcd_cam_obj);
uint8_t fifo_mode = 1;
// Start signal
xQueueSend(lcd_cam_obj->lcd.event_queue, &event, 0);
cnt = len / lcd_cam_obj->lcd.dma_half_buffer_size;
// Process a complete piece of data, ping-pong operation
for (x = 0; x < cnt; x++) {
uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf;
uint8_t *in = data;
if (lcd_cam_obj->lcd.swap_data) {
for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) {
out[y + 3] = in[(y >> 1) + 0];
out[y + 1] = in[(y >> 1) + 1];
}
} else {
for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) {
out[y + 1] = in[(y >> 1) + 0];
out[y + 3] = in[(y >> 1) + 1];
}
}
data += lcd_cam_obj->lcd.dma_half_buffer_size >> 1;
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, lcd_cam_obj->lcd.dma_half_buffer_size);
}
left = len % lcd_cam_obj->lcd.dma_half_buffer_size;
// Process remaining incomplete segment data
while (left) {
uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf;
uint8_t *in = data;
if (left > 2) {
cnt = left - left % 4;
left = left % 4;
data += cnt >> 1;
if (lcd_cam_obj->lcd.swap_data) {
for (y = 0; y < cnt; y += 4) {
out[y + 3] = in[(y >> 1) + 0];
out[y + 1] = in[(y >> 1) + 1];
}
} else {
for (y = 0; y < cnt; y += 4) {
out[y + 1] = in[(y >> 1) + 0];
out[y + 3] = in[(y >> 1) + 1];
}
}
} else {
cnt = 4;
left = 0;
fifo_mode = 3;
out[3] = in[0];
}
lcd_dma_set_left(lcd_cam_obj, x, cnt);
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, cnt);
x++;
}
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
}
static void i2s_write_16bit_data(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len)
{
int event = 0;
int x = 0, y = 0, left = 0, cnt = 0;
if (len <= 0 || len % 2 != 0) {
ESP_LOGE(TAG, "wrong len!");
return;
}
lcd_dma_set_int(lcd_cam_obj);
uint8_t fifo_mode = 1;
// Start signal
xQueueSend(lcd_cam_obj->lcd.event_queue, &event, 0);
cnt = len / lcd_cam_obj->lcd.dma_half_buffer_size;
// Process a complete piece of data, ping-pong operation
for (x = 0; x < cnt; x++) {
uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf;
uint8_t *in = data;
if (lcd_cam_obj->lcd.swap_data) {
for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) {
out[y + 3] = in[y + 0];
out[y + 2] = in[y + 1];
out[y + 1] = in[y + 2];
out[y + 0] = in[y + 3];
}
} else {
for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) {
out[y + 2] = in[y + 0];
out[y + 3] = in[y + 1];
out[y + 0] = in[y + 2];
out[y + 1] = in[y + 3];
}
}
data += lcd_cam_obj->lcd.dma_half_buffer_size;
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, lcd_cam_obj->lcd.dma_half_buffer_size);
}
left = len % lcd_cam_obj->lcd.dma_half_buffer_size;
// Process remaining incomplete segment data
while (left) {
uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf;
uint8_t *in = data;
if (left > 2) {
cnt = left - left % 4;
left = left % 4;
data += cnt;
if (lcd_cam_obj->lcd.swap_data) {
for (y = 0; y < cnt; y += 4) {
out[y + 3] = in[y + 0];
out[y + 2] = in[y + 1];
out[y + 1] = in[y + 2];
out[y + 0] = in[y + 3];
}
} else {
for (y = 0; y < cnt; y += 4) {
out[y + 2] = in[y + 0];
out[y + 3] = in[y + 1];
out[y + 0] = in[y + 2];
out[y + 1] = in[y + 3];
}
}
} else {
cnt = 4;
left = 0;
fifo_mode = 3;
if (lcd_cam_obj->lcd.swap_data) {
out[3] = in[0];
out[2] = in[1];
} else {
out[2] = in[0];
out[3] = in[1];
}
}
lcd_dma_set_left(lcd_cam_obj, x, cnt);
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, cnt);
x++;
}
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
}
static esp_err_t i2s_lcd_reg_config(i2s_dev_t *i2s_dev, uint16_t data_width, uint32_t clk_freq)
{
// Configure the clock
i2s_dev->clkm_conf.clkm_div_num = 2; // 160MHz / 2 = 80MHz
i2s_dev->clkm_conf.clkm_div_b = 0;
i2s_dev->clkm_conf.clkm_div_a = 10;
i2s_dev->clkm_conf.clk_en = 1;
i2s_dev->conf.val = 0;
i2s_dev->fifo_conf.val = 0;
i2s_dev->fifo_conf.dscr_en = 1;
i2s_dev->conf2.lcd_en = 1;
i2s_dev->conf2.camera_en = 1;
i2s_dev->lc_conf.ahbm_fifo_rst = 1;
i2s_dev->lc_conf.ahbm_fifo_rst = 0;
i2s_dev->lc_conf.ahbm_rst = 1;
i2s_dev->lc_conf.ahbm_rst = 0;
i2s_dev->lc_conf.check_owner = 0;
i2s_dev->lc_conf.out_loop_test = 0;
i2s_dev->lc_conf.out_auto_wrback = 0;
i2s_dev->lc_conf.out_data_burst_en = 1;
i2s_dev->lc_conf.out_no_restart_clr = 0;
i2s_dev->lc_conf.indscr_burst_en = 0;
i2s_dev->lc_conf.out_eof_mode = 1;
i2s_dev->timing.val = 0;
i2s_dev->int_ena.val = 0;
i2s_dev->int_clr.val = ~0;
// Configure sampling rate
i2s_dev->sample_rate_conf.tx_bck_div_num = 40000000 / clk_freq; // Fws = Fbck / 2
i2s_dev->sample_rate_conf.tx_bits_mod = (data_width == 8) ? 0 : 1;
// Configuration data format
i2s_dev->conf.tx_start = 0;
i2s_dev->conf.tx_reset = 1;
i2s_dev->conf.tx_reset = 0;
i2s_dev->conf.tx_fifo_reset = 1;
i2s_dev->conf.tx_fifo_reset = 0;
i2s_dev->conf.tx_slave_mod = 0;
i2s_dev->conf.tx_right_first = 1; // Must be set to 1, otherwise the clock line will change during reset
i2s_dev->conf.tx_msb_right = 0;
i2s_dev->conf.tx_short_sync = 0;
i2s_dev->conf.tx_mono = 0;
i2s_dev->conf.tx_msb_shift = 0;
i2s_dev->conf1.tx_pcm_bypass = 1;
i2s_dev->conf1.tx_stop_en = 1;
i2s_dev->conf_chan.tx_chan_mod = 1;
i2s_dev->fifo_conf.tx_fifo_mod_force_en = 1;
i2s_dev->fifo_conf.tx_data_num = 32;
i2s_dev->fifo_conf.tx_fifo_mod = 1;
i2s_dev->lc_conf.out_rst = 1;
i2s_dev->lc_conf.out_rst = 0;
i2s_dev->int_ena.out_eof = 1;
return ESP_OK;
}
static esp_err_t lcd_set_pin(const i2s_lcd_config_t *config)
{
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_num_wr], PIN_FUNC_GPIO);
gpio_set_direction(config->pin_num_wr, GPIO_MODE_OUTPUT);
gpio_set_pull_mode(config->pin_num_wr, GPIO_FLOATING);
gpio_matrix_out(config->pin_num_wr, I2S0O_WS_OUT_IDX, true, false);
for (int i = 0; i < config->data_width; i++) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_data_num[i]], PIN_FUNC_GPIO);
gpio_set_direction(config->pin_data_num[i], GPIO_MODE_OUTPUT);
gpio_set_pull_mode(config->pin_data_num[i], GPIO_FLOATING);
// High bit aligned, OUT23 is always the highest bit
gpio_matrix_out(config->pin_data_num[i], I2S0O_DATA_OUT0_IDX + (LCD_DATA_MAX_WIDTH - config->data_width) + i, false, false);
}
return ESP_OK;
}
static esp_err_t lcd_dma_config(lcd_cam_obj_t *lcd_cam_obj, uint32_t max_dma_buffer_size)
{
int cnt = 0;
if (LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE % 2 != 0) {
ESP_LOGE(TAG, "ESP32 only supports 2-byte aligned data length");
return ESP_FAIL;
}
if (max_dma_buffer_size >= LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE * 2) {
lcd_cam_obj->lcd.dma_node_buffer_size = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE;
for (cnt = 0; cnt < max_dma_buffer_size - 8; cnt++) { // Find a buffer size that can divide dma_size
if ((max_dma_buffer_size - cnt) % (lcd_cam_obj->lcd.dma_node_buffer_size * 2) == 0) {
break;
}
}
lcd_cam_obj->lcd.dma_buffer_size = max_dma_buffer_size - cnt;
} else {
lcd_cam_obj->lcd.dma_node_buffer_size = max_dma_buffer_size / 2;
lcd_cam_obj->lcd.dma_buffer_size = lcd_cam_obj->lcd.dma_node_buffer_size * 2;
}
lcd_cam_obj->lcd.dma_half_buffer_size = lcd_cam_obj->lcd.dma_buffer_size / 2;
lcd_cam_obj->lcd.dma_node_cnt = (lcd_cam_obj->lcd.dma_buffer_size) / lcd_cam_obj->lcd.dma_node_buffer_size; // Number of DMA nodes
lcd_cam_obj->lcd.dma_half_node_cnt = lcd_cam_obj->lcd.dma_node_cnt / 2;
ESP_LOGI(TAG, "lcd_buffer_size: %d, lcd_dma_size: %d, lcd_dma_node_cnt: %d", lcd_cam_obj->lcd.dma_buffer_size, lcd_cam_obj->lcd.dma_node_buffer_size, lcd_cam_obj->lcd.dma_node_cnt);
lcd_cam_obj->lcd.dma = (lldesc_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_node_cnt * sizeof(lldesc_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
lcd_cam_obj->lcd.dma_buffer = (uint8_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
return ESP_OK;
}
esp_err_t lcd_cam_deinit(i2s_lcd_driver_t *drv)
{
if (!drv->lcd_cam_obj) {
return ESP_FAIL;
}
if (drv->lcd_cam_obj->lcd.event_queue) {
vQueueDelete(drv->lcd_cam_obj->lcd.event_queue);
}
if (drv->lcd_cam_obj->lcd.dma) {
heap_caps_free(drv->lcd_cam_obj->lcd.dma);
}
if (drv->lcd_cam_obj->lcd.dma_buffer) {
heap_caps_free(drv->lcd_cam_obj->lcd.dma_buffer);
}
if (drv->lcd_cam_obj->lcd_cam_intr_handle) {
esp_intr_free(drv->lcd_cam_obj->lcd_cam_intr_handle);
}
heap_caps_free(drv->lcd_cam_obj);
drv->lcd_cam_obj = NULL;
return ESP_OK;
}
static esp_err_t lcd_cam_init(i2s_lcd_driver_t *drv, const i2s_lcd_config_t *config)
{
esp_err_t ret = ESP_OK;
lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t *)heap_caps_calloc(1, sizeof(lcd_cam_obj_t), MALLOC_CAP_DMA);
if (lcd_cam_obj == NULL) {
ESP_LOGE(TAG, "lcd_cam object malloc failed");
return ESP_ERR_NO_MEM;
}
drv->lcd_cam_obj = lcd_cam_obj;
if (I2S_NUM_0 == config->i2s_port) {
lcd_cam_obj->i2s_dev = &I2S0;
periph_module_enable(PERIPH_I2S0_MODULE);
ESP_LOGI(TAG, "Enable I2S0");
} else if (I2S_NUM_1 == config->i2s_port) {
lcd_cam_obj->i2s_dev = &I2S1;
periph_module_enable(PERIPH_I2S1_MODULE);
ESP_LOGI(TAG, "Enable I2S1");
} else {
ESP_LOGE(TAG, "Designated I2S peripheral not found");
}
do {
ret |= i2s_lcd_reg_config(lcd_cam_obj->i2s_dev, config->data_width, config->clk_freq);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "lcd_cam config fail!");
break;
}
ret |= lcd_set_pin(config);
ret |= lcd_dma_config(lcd_cam_obj, config->buffer_size);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "lcd config fail!");
break;
}
lcd_cam_obj->lcd.event_queue = xQueueCreate(1, sizeof(int));
lcd_cam_obj->lcd.width = config->data_width;
lcd_cam_obj->lcd.swap_data = config->swap_data;;
if (lcd_cam_obj->lcd.event_queue == NULL) {
ESP_LOGE(TAG, "lcd config fail!");
break;
}
if (I2S_NUM_0 == config->i2s_port) {
ret |= esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, lcd_cam_obj, &lcd_cam_obj->lcd_cam_intr_handle);
} else if (I2S_NUM_1 == config->i2s_port) {
ret |= esp_intr_alloc(ETS_I2S1_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, lcd_cam_obj, &lcd_cam_obj->lcd_cam_intr_handle);
}
if (ret != ESP_OK) {
ESP_LOGE(TAG, "lcd_cam intr alloc fail!");
break;
}
ESP_LOGI(TAG, "i2s lcd driver init ok");
return ESP_OK;
} while (0);
lcd_cam_deinit(drv);
return ESP_FAIL;
}
/**< Public functions */
i2s_lcd_handle_t i2s_lcd_driver_init(const i2s_lcd_config_t *config)
{
I2S_CHECK(NULL != config, "config pointer invalid", NULL);
I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_num_wr), "GPIO WR invalid", NULL);
I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_num_rs), "GPIO RS invalid", NULL);
I2S_CHECK(config->data_width > 0 && config->data_width <= 16, "Bit width out of range", NULL);
I2S_CHECK(0 == (config->data_width % 8), "Bit width must be a multiple of 8", NULL);
uint64_t pin_mask = 0;
for (size_t i = 0; i < config->data_width; i++) {
uint64_t mask = 1ULL << config->pin_data_num[i];
I2S_CHECK(!(pin_mask & mask), "Data bus GPIO has a duplicate", NULL);
I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_data_num[i]), "Data bus gpio invalid", NULL);
pin_mask |= mask;
}
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)heap_caps_malloc(sizeof(i2s_lcd_driver_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
I2S_CHECK(NULL != i2s_lcd_drv, "Error malloc handle of i2s lcd driver", NULL);
esp_err_t ret = lcd_cam_init(i2s_lcd_drv, config);
if (ESP_OK != ret) {
ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "i2s lcd driver initialize failed");
heap_caps_free(i2s_lcd_drv);
return NULL;
}
i2s_lcd_drv->mutex = xSemaphoreCreateMutex();
if (i2s_lcd_drv->mutex == NULL) {
ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "lcd create mutex failed");
lcd_cam_deinit(i2s_lcd_drv);
heap_caps_free(i2s_lcd_drv);
return NULL;
}
if (8 == config->data_width) {
i2s_lcd_drv->i2s_write_data_func = i2s_write_8bit_data;
} else if (16 == config->data_width) {
i2s_lcd_drv->i2s_write_data_func = i2s_write_16bit_data;
}
if (config->pin_num_cs >= 0) {
gpio_pad_select_gpio(config->pin_num_cs);
gpio_set_direction(config->pin_num_cs, GPIO_MODE_OUTPUT);
gpio_set_level(config->pin_num_cs, 0);
}
gpio_pad_select_gpio(config->pin_num_rs);
gpio_set_direction(config->pin_num_rs, GPIO_MODE_OUTPUT);
i2s_lcd_drv->rs_io_num = config->pin_num_rs;
return (i2s_lcd_handle_t)i2s_lcd_drv;
}
esp_err_t i2s_lcd_driver_deinit(i2s_lcd_handle_t handle)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
lcd_cam_deinit(i2s_lcd_drv);
vSemaphoreDelete(i2s_lcd_drv->mutex);
heap_caps_free(handle);
return ESP_OK;
}
esp_err_t i2s_lcd_write_data(i2s_lcd_handle_t handle, uint16_t data)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&data, 2);
return ESP_OK;
}
esp_err_t i2s_lcd_write_cmd(i2s_lcd_handle_t handle, uint16_t cmd)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_CMD_LEV);
i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&cmd, 2);
gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_DATA_LEV);
return ESP_OK;
}
esp_err_t i2s_lcd_write(i2s_lcd_handle_t handle, const uint8_t *data, uint32_t length)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)data, length);
return ESP_OK;
}
esp_err_t i2s_lcd_acquire(i2s_lcd_handle_t handle)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
BaseType_t ret = xSemaphoreTake(i2s_lcd_drv->mutex, portMAX_DELAY);
I2S_CHECK(pdTRUE == ret, "Take semaphore failed", ESP_FAIL);
return ESP_OK;
}
esp_err_t i2s_lcd_release(i2s_lcd_handle_t handle)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
BaseType_t ret = xSemaphoreGive(i2s_lcd_drv->mutex);
I2S_CHECK(pdTRUE == ret, "Give semaphore failed", ESP_FAIL);
return ESP_OK;
}
#endif // CONFIG_IDF_TARGET_ESP32

View File

@@ -0,0 +1,481 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "sdkconfig.h"
#if CONFIG_IDF_TARGET_ESP32S2
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "esp_heap_caps.h"
#include "esp32s2/rom/lldesc.h"
#include "soc/system_reg.h"
#include "i2s_lcd_driver.h"
static const char *TAG = "ESP32S2_I2S_LCD";
#define I2S_CHECK(a, str, ret) if (!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
return (ret); \
}
#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4000) // 4-byte aligned
#define LCD_DATA_MAX_WIDTH (24) /*!< Maximum width of LCD data bus */
typedef struct {
uint32_t dma_buffer_size;
uint32_t dma_half_buffer_size;
uint32_t dma_node_buffer_size;
uint32_t dma_node_cnt;
uint32_t dma_half_node_cnt;
lldesc_t *dma;
uint8_t *dma_buffer;
QueueHandle_t event_queue;
uint8_t width;
bool swap_data;
} lcd_obj_t;
typedef struct {
lcd_obj_t lcd;
intr_handle_t lcd_cam_intr_handle;
i2s_dev_t *i2s_dev;
} lcd_cam_obj_t;
typedef struct {
int rs_io_num;
lcd_cam_obj_t *lcd_cam_obj;
SemaphoreHandle_t mutex;
} i2s_lcd_driver_t;
static void IRAM_ATTR i2s_isr(void *arg)
{
BaseType_t HPTaskAwoken = pdFALSE;
lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t*)arg;
i2s_dev_t *i2s_dev = lcd_cam_obj->i2s_dev;
typeof(i2s_dev->int_st) status = i2s_dev->int_st;
i2s_dev->int_clr.val = status.val;
if (status.val == 0) {
return;
}
if (status.out_eof) {
xQueueSendFromISR(lcd_cam_obj->lcd.event_queue, (void*)&status.val, &HPTaskAwoken);
}
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
static void lcd_dma_set_int(lcd_cam_obj_t *lcd_cam_obj)
{
// Generate a data DMA linked list
for (int x = 0; x < lcd_cam_obj->lcd.dma_node_cnt; x++) {
lcd_cam_obj->lcd.dma[x].size = lcd_cam_obj->lcd.dma_node_buffer_size;
lcd_cam_obj->lcd.dma[x].length = lcd_cam_obj->lcd.dma_node_buffer_size;
lcd_cam_obj->lcd.dma[x].buf = (lcd_cam_obj->lcd.dma_buffer + lcd_cam_obj->lcd.dma_node_buffer_size * x);
lcd_cam_obj->lcd.dma[x].eof = !((x + 1) % lcd_cam_obj->lcd.dma_half_node_cnt);
lcd_cam_obj->lcd.dma[x].empty = (uint32_t)&lcd_cam_obj->lcd.dma[(x + 1) % lcd_cam_obj->lcd.dma_node_cnt];
}
lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_half_node_cnt - 1].empty = (uint32_t)NULL;
lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_node_cnt - 1].empty = (uint32_t)NULL;
}
static void lcd_dma_set_left(lcd_cam_obj_t *lcd_cam_obj, int pos, size_t len)
{
int end_pos = 0, size = 0;
// Processing data length is an integer multiple of lcd_cam_obj->lcd.dma_node_buffer_size
if (len % lcd_cam_obj->lcd.dma_node_buffer_size) {
end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size;
size = len % lcd_cam_obj->lcd.dma_node_buffer_size;
} else {
end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size - 1;
size = lcd_cam_obj->lcd.dma_node_buffer_size;
}
// Process the tail node to make it a DMA tail
lcd_cam_obj->lcd.dma[end_pos].size = size;
lcd_cam_obj->lcd.dma[end_pos].length = size;
lcd_cam_obj->lcd.dma[end_pos].eof = 1;
lcd_cam_obj->lcd.dma[end_pos].empty = (uint32_t)NULL;
}
static void lcd_i2s_start(i2s_dev_t *i2s_dev, uint32_t addr, size_t len)
{
while (!i2s_dev->state.tx_idle);
i2s_dev->conf.tx_reset = 1;
i2s_dev->conf.tx_reset = 0;
i2s_dev->conf.tx_fifo_reset = 1;
i2s_dev->conf.tx_fifo_reset = 0;
i2s_dev->out_link.addr = addr;
i2s_dev->out_link.start = 1;
ets_delay_us(1);
i2s_dev->conf.tx_start = 1;
}
static void i2s_write_data(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len)
{
int event = 0;
int x = 0, y = 0, left = 0, cnt = 0;
if (len <= 0) {
ESP_LOGE(TAG, "wrong len!");
return;
}
lcd_dma_set_int(lcd_cam_obj);
cnt = len / lcd_cam_obj->lcd.dma_half_buffer_size;
// Start signal
xQueueSend(lcd_cam_obj->lcd.event_queue, &event, 0);
// Process a complete piece of data, ping-pong operation
for (x = 0; x < cnt; x++) {
uint8_t *out = (uint8_t*)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf;
uint8_t *in = data;
if (lcd_cam_obj->lcd.swap_data) {
for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y+=2) {
out[y+1] = in[y+0];
out[y+0] = in[y+1];
}
} else {
memcpy(out, in, lcd_cam_obj->lcd.dma_half_buffer_size);
}
data += lcd_cam_obj->lcd.dma_half_buffer_size;
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
lcd_i2s_start(lcd_cam_obj->i2s_dev, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, lcd_cam_obj->lcd.dma_half_buffer_size);
}
left = len % lcd_cam_obj->lcd.dma_half_buffer_size;
// Process remaining incomplete segment data
if (left) {
uint8_t *out = (uint8_t*)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf;
uint8_t *in = data;
cnt = left - left % 2;
if (cnt) {
if (lcd_cam_obj->lcd.swap_data) {
for (y = 0; y < cnt; y+=2) {
out[y+1] = in[y+0];
out[y+0] = in[y+1];
}
} else {
memcpy(out, in, cnt);
}
}
if (left % 2) {
out[cnt] = in[cnt];
}
lcd_dma_set_left(lcd_cam_obj, x, left);
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
lcd_i2s_start(lcd_cam_obj->i2s_dev, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, left);
}
xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY);
}
static esp_err_t i2s_lcd_reg_config(i2s_dev_t *i2s_dev, uint16_t data_width, uint32_t clk_freq)
{
// Configure the clock
i2s_dev->clkm_conf.clkm_div_num = 2; // 160MHz / 2 = 80MHz
i2s_dev->clkm_conf.clkm_div_b = 0;
i2s_dev->clkm_conf.clkm_div_a = 0;
i2s_dev->clkm_conf.clk_sel = 2;
i2s_dev->clkm_conf.clk_en = 1;
i2s_dev->conf.val = 0;
i2s_dev->fifo_conf.val = 0;
i2s_dev->fifo_conf.dscr_en = 1;
i2s_dev->lc_conf.ahbm_fifo_rst = 1;
i2s_dev->lc_conf.ahbm_fifo_rst = 0;
i2s_dev->lc_conf.ahbm_rst = 1;
i2s_dev->lc_conf.ahbm_rst = 0;
i2s_dev->lc_conf.check_owner = 0;
i2s_dev->timing.val = 0;
i2s_dev->int_ena.val = 0;
i2s_dev->int_clr.val = ~0;
i2s_dev->conf2.lcd_en = 1;
// Configure sampling rate
i2s_dev->sample_rate_conf.tx_bck_div_num = 40000000 / clk_freq; // Fws = Fbck / 2
i2s_dev->sample_rate_conf.tx_bits_mod = data_width;
// Configuration data format
i2s_dev->conf.tx_right_first = 1;
i2s_dev->conf.tx_msb_right = 1;
i2s_dev->conf.tx_dma_equal = 1;
i2s_dev->conf1.tx_pcm_bypass = 1;
i2s_dev->conf1.tx_stop_en = 1;
i2s_dev->conf2.lcd_en = 1;
i2s_dev->conf_chan.tx_chan_mod = 1;
i2s_dev->fifo_conf.tx_fifo_mod_force_en = 1;
i2s_dev->fifo_conf.tx_data_num = 32;
i2s_dev->fifo_conf.tx_fifo_mod = 2;
i2s_dev->fifo_conf.tx_24msb_en = 0;
i2s_dev->lc_conf.out_rst = 1;
i2s_dev->lc_conf.out_rst = 0;
i2s_dev->int_ena.out_eof = 1;
return ESP_OK;
}
static esp_err_t lcd_set_pin(const i2s_lcd_config_t *config)
{
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_num_wr], PIN_FUNC_GPIO);
gpio_set_direction(config->pin_num_wr, GPIO_MODE_OUTPUT);
gpio_set_pull_mode(config->pin_num_wr, GPIO_FLOATING);
gpio_matrix_out(config->pin_num_wr, I2S0O_WS_OUT_IDX, true, false);
for (int i = 0; i < config->data_width; i++) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_data_num[i]], PIN_FUNC_GPIO);
gpio_set_direction(config->pin_data_num[i], GPIO_MODE_OUTPUT);
gpio_set_pull_mode(config->pin_data_num[i], GPIO_FLOATING);
// High bit aligned, OUT23 is always the highest bit
gpio_matrix_out(config->pin_data_num[i], I2S0O_DATA_OUT0_IDX + (LCD_DATA_MAX_WIDTH - config->data_width) + i, false, false);
}
return ESP_OK;
}
static esp_err_t lcd_dma_config(lcd_cam_obj_t *lcd_cam_obj, uint32_t max_dma_buffer_size)
{
int cnt = 0;
if (LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE % 2 != 0) {
ESP_LOGE(TAG, "ESP32 only supports 2-byte aligned data length");
return ESP_FAIL;
}
if (max_dma_buffer_size >= LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE * 2) {
lcd_cam_obj->lcd.dma_node_buffer_size = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE;
for (cnt = 0; cnt < max_dma_buffer_size - 8; cnt++) { // Find a buffer size that can divide dma_size
if ((max_dma_buffer_size - cnt) % (lcd_cam_obj->lcd.dma_node_buffer_size * 2) == 0) {
break;
}
}
lcd_cam_obj->lcd.dma_buffer_size = max_dma_buffer_size - cnt;
} else {
lcd_cam_obj->lcd.dma_node_buffer_size = max_dma_buffer_size / 2;
lcd_cam_obj->lcd.dma_buffer_size = lcd_cam_obj->lcd.dma_node_buffer_size * 2;
}
lcd_cam_obj->lcd.dma_half_buffer_size = lcd_cam_obj->lcd.dma_buffer_size / 2;
lcd_cam_obj->lcd.dma_node_cnt = (lcd_cam_obj->lcd.dma_buffer_size) / lcd_cam_obj->lcd.dma_node_buffer_size; // Number of DMA nodes
lcd_cam_obj->lcd.dma_half_node_cnt = lcd_cam_obj->lcd.dma_node_cnt / 2;
ESP_LOGI(TAG, "lcd_buffer_size: %d, lcd_dma_size: %d, lcd_dma_node_cnt: %d", lcd_cam_obj->lcd.dma_buffer_size, lcd_cam_obj->lcd.dma_node_buffer_size, lcd_cam_obj->lcd.dma_node_cnt);
lcd_cam_obj->lcd.dma = (lldesc_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_node_cnt * sizeof(lldesc_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
lcd_cam_obj->lcd.dma_buffer = (uint8_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
return ESP_OK;
}
static esp_err_t lcd_cam_deinit(i2s_lcd_driver_t *drv)
{
if (!drv->lcd_cam_obj) {
return ESP_FAIL;
}
if (drv->lcd_cam_obj->lcd.event_queue) {
vQueueDelete(drv->lcd_cam_obj->lcd.event_queue);
}
if (drv->lcd_cam_obj->lcd.dma) {
heap_caps_free(drv->lcd_cam_obj->lcd.dma);
}
if (drv->lcd_cam_obj->lcd.dma_buffer) {
heap_caps_free(drv->lcd_cam_obj->lcd.dma_buffer);
}
if (drv->lcd_cam_obj->lcd_cam_intr_handle) {
esp_intr_free(drv->lcd_cam_obj->lcd_cam_intr_handle);
}
heap_caps_free(drv->lcd_cam_obj);
drv->lcd_cam_obj = NULL;
return ESP_OK;
}
static esp_err_t lcd_cam_init(i2s_lcd_driver_t *drv, const i2s_lcd_config_t *config)
{
esp_err_t ret = ESP_OK;
lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t *)heap_caps_calloc(1, sizeof(lcd_cam_obj_t), MALLOC_CAP_DMA);
if (lcd_cam_obj == NULL) {
ESP_LOGE(TAG, "lcd_cam object malloc error");
return ESP_ERR_NO_MEM;
}
drv->lcd_cam_obj = lcd_cam_obj;
if (I2S_NUM_0 == config->i2s_port) {
lcd_cam_obj->i2s_dev = &I2S0;
periph_module_enable(PERIPH_I2S0_MODULE);
} else {
ESP_LOGE(TAG, "Designated I2S peripheral not found");
}
ret |= i2s_lcd_reg_config(lcd_cam_obj->i2s_dev, config->data_width, config->clk_freq);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "lcd_cam config fail!");
lcd_cam_deinit(drv);
return ESP_FAIL;
}
ret |= lcd_set_pin(config);
ret |= lcd_dma_config(lcd_cam_obj, config->buffer_size);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "lcd config fail!");
lcd_cam_deinit(drv);
return ESP_FAIL;
}
lcd_cam_obj->lcd.event_queue = xQueueCreate(1, sizeof(int));
lcd_cam_obj->lcd.width = config->data_width;
lcd_cam_obj->lcd.swap_data = config->swap_data;
if (lcd_cam_obj->lcd.event_queue == NULL) {
ESP_LOGE(TAG, "lcd config fail!");
lcd_cam_deinit(drv);
return ESP_FAIL;
}
ret |= esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, lcd_cam_obj, &lcd_cam_obj->lcd_cam_intr_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "lcd_cam intr alloc fail!");
lcd_cam_deinit(drv);
return ESP_FAIL;
}
ESP_LOGI(TAG, "lcd init ok");
return ESP_OK;
}
/**< Public functions */
i2s_lcd_handle_t i2s_lcd_driver_init(const i2s_lcd_config_t *config)
{
I2S_CHECK(NULL != config, "config pointer invalid", NULL);
I2S_CHECK(GPIO_IS_VALID_GPIO(config->pin_num_wr), "GPIO WR invalid", NULL);
I2S_CHECK(GPIO_IS_VALID_GPIO(config->pin_num_rs), "GPIO RS invalid", NULL);
I2S_CHECK(config->data_width > 0 && config->data_width <= 16, "Bit width out of range", NULL);
I2S_CHECK(0 == (config->data_width % 8), "Bit width must be a multiple of 8", NULL);
uint64_t pin_mask = 0;
for (size_t i = 0; i < config->data_width; i++) {
uint64_t mask = 1ULL << config->pin_data_num[i];
I2S_CHECK(!(pin_mask & mask), "Data bus GPIO has a duplicate", NULL);
I2S_CHECK(GPIO_IS_VALID_GPIO(config->pin_data_num[i]), "Data bus gpio invalid", NULL);
pin_mask |= mask;
}
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)heap_caps_malloc(sizeof(i2s_lcd_driver_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
I2S_CHECK(NULL != i2s_lcd_drv, "Error malloc handle of i2s lcd driver", NULL);
esp_err_t ret = lcd_cam_init(i2s_lcd_drv, config);
if(ESP_OK != ret) {
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "i2s lcd driver initialize failed");
heap_caps_free(i2s_lcd_drv);
return NULL;
}
i2s_lcd_drv->mutex = xSemaphoreCreateMutex();
if (i2s_lcd_drv->mutex == NULL) {
ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "lcd create mutex failed");
lcd_cam_deinit(i2s_lcd_drv);
heap_caps_free(i2s_lcd_drv);
return NULL;
}
if (config->pin_num_cs >= 0) {
gpio_pad_select_gpio(config->pin_num_cs);
gpio_set_direction(config->pin_num_cs, GPIO_MODE_OUTPUT);
gpio_set_level(config->pin_num_cs, 0);
}
gpio_pad_select_gpio(config->pin_num_rs);
gpio_set_direction(config->pin_num_rs, GPIO_MODE_OUTPUT);
i2s_lcd_drv->rs_io_num = config->pin_num_rs;
return (i2s_lcd_handle_t)i2s_lcd_drv;
}
esp_err_t i2s_lcd_driver_deinit(i2s_lcd_handle_t handle)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
lcd_cam_deinit(i2s_lcd_drv);
vSemaphoreDelete(i2s_lcd_drv->mutex);
heap_caps_free(handle);
return ESP_OK;
}
esp_err_t i2s_lcd_write_data(i2s_lcd_handle_t handle, uint16_t data)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
i2s_write_data(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&data, 2);
return ESP_OK;
}
esp_err_t i2s_lcd_write_cmd(i2s_lcd_handle_t handle, uint16_t cmd)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_CMD_LEV);
i2s_write_data(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&cmd, 2);
gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_DATA_LEV);
return ESP_OK;
}
esp_err_t i2s_lcd_write(i2s_lcd_handle_t handle, const uint8_t *data, uint32_t length)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
i2s_write_data(i2s_lcd_drv->lcd_cam_obj, (uint8_t*)data, length);
return ESP_OK;
}
esp_err_t i2s_lcd_acquire(i2s_lcd_handle_t handle)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
BaseType_t ret = xSemaphoreTake(i2s_lcd_drv->mutex, portMAX_DELAY);
I2S_CHECK(pdTRUE == ret, "Take semaphore failed", ESP_FAIL);
return ESP_OK;
}
esp_err_t i2s_lcd_release(i2s_lcd_handle_t handle)
{
i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle;
I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG);
BaseType_t ret = xSemaphoreGive(i2s_lcd_drv->mutex);
I2S_CHECK(pdTRUE == ret, "Give semaphore failed", ESP_FAIL);
return ESP_OK;
}
#endif // CONFIG_IDF_TARGET_ESP32S2

View File

@@ -0,0 +1,296 @@
// Copyright 2019-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _I2C_BUS_H_
#define _I2C_BUS_H_
#include "driver/i2c.h"
#define NULL_I2C_MEM_ADDR 0xFF /*!< set mem_address to NULL_I2C_MEM_ADDR if i2c device has no internal address during read/write */
#define NULL_I2C_DEV_ADDR 0xFF /*!< invalid i2c device address */
typedef void *i2c_bus_handle_t; /*!< i2c bus handle */
typedef void *i2c_bus_device_handle_t; /*!< i2c device handle */
#ifdef __cplusplus
extern "C"
{
#endif
/**************************************** Public Functions (Application level)*********************************************/
/**
* @brief Create an I2C bus instance then return a handle if created successfully. Each I2C bus works in a singleton mode,
* which means for an i2c port only one group parameter works. When i2c_bus_create is called more than one time for the
* same i2c port, following parameter will override the previous one.
*
* @param port I2C port number
* @param conf Pointer to I2C bus configuration
* @return i2c_bus_handle_t Return the I2C bus handle if created successfully, return NULL if failed.
*/
i2c_bus_handle_t i2c_bus_create(i2c_port_t port, const i2c_config_t *conf);
/**
* @brief Delete and release the I2C bus resource.
*
* @param p_bus_handle Point to the I2C bus handle, if delete succeed handle will set to NULL.
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t i2c_bus_delete(i2c_bus_handle_t *p_bus_handle);
/**
* @brief Scan i2c devices attached on i2c bus
*
* @param bus_handle I2C bus handle
* @param buf Pointer to a buffer to save devices' address, if NULL no address will be saved.
* @param num Maximum number of addresses to save, invalid if buf set to NULL,
* higer addresses will be discarded if num less-than the total number found on the I2C bus.
* @return uint8_t Total number of devices found on the I2C bus
*/
uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num);
/**
* @brief Get current active clock speed.
*
* @param bus_handle I2C bus handle
* @return uint32_t current clock speed
*/
uint32_t i2c_bus_get_current_clk_speed(i2c_bus_handle_t bus_handle);
/**
* @brief Get created device number of the bus.
*
* @param bus_handle I2C bus handle
* @return uint8_t created device number of the bus
*/
uint8_t i2c_bus_get_created_device_num(i2c_bus_handle_t bus_handle);
/**
* @brief Create an I2C device on specific bus.
* Dynamic configuration must be enable to achieve multiple devices with different configs on a single bus.
* menuconfig:Bus Options->I2C Bus Options->enable dynamic configuration
*
* @param bus_handle Point to the I2C bus handle
* @param dev_addr i2c device address
* @param clk_speed device specified clock frequency the i2c_bus will switch to during each transfer. 0 if use current bus speed.
* @return i2c_bus_device_handle_t return a device handle if created successfully, return NULL if failed.
*/
i2c_bus_device_handle_t i2c_bus_device_create(i2c_bus_handle_t bus_handle, uint8_t dev_addr, uint32_t clk_speed);
/**
* @brief Delete and release the I2C device resource, i2c_bus_device_delete should be used in pairs with i2c_bus_device_create.
*
* @param p_dev_handle Point to the I2C device handle, if delete succeed handle will set to NULL.
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t i2c_bus_device_delete(i2c_bus_device_handle_t *p_dev_handle);
/**
* @brief Get device's I2C address
*
* @param dev_handle I2C device handle
* @return uint8_t I2C address, return NULL_I2C_DEV_ADDR if dev_handle is invalid.
*/
uint8_t i2c_bus_device_get_address(i2c_bus_device_handle_t dev_handle);
/**
* @brief Read single byte from i2c device with 8-bit internal register/memory address
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address.
* @param data Pointer to a buffer to save the data that was read
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_read_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t *data);
/**
* @brief Read multiple bytes from i2c device with 8-bit internal register/memory address.
* If internal reg/mem address is 16-bit, please refer i2c_bus_read_reg16
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address.
* @param data_len Number of bytes to read
* @param data Pointer to a buffer to save the data that was read
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_read_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data);
/**
* @brief Read single bit of a byte from i2c device with 8-bit internal register/memory address
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address.
* @param bit_num The bit number 0 - 7 to read
* @param data Pointer to a buffer to save the data that was read. *data == 0 -> bit = 0, *data !=0 -> bit = 1.
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_read_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t *data);
/**
* @brief Read multiple bits of a byte from i2c device with 8-bit internal register/memory address
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address.
* @param bit_start The bit to start from, 0 - 7, MSB at 0
* @param length The number of bits to read, 1 - 8
* @param data Pointer to a buffer to save the data that was read
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_read_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t *data);
/**
* @brief Write single byte to i2c device with 8-bit internal register/memory address
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address.
* @param data The byte to write.
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_write_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t data);
/**
* @brief Write multiple byte to i2c device with 8-bit internal register/memory address
* If internal reg/mem address is 16-bit, please refer i2c_bus_write_reg16
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address.
* @param data_len Number of bytes to write
* @param data Pointer to the bytes to write.
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_write_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data);
/**
* @brief Write single bit of a byte to an i2c device with 8-bit internal register/memory address
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address.
* @param bit_num The bit number 0 - 7 to write
* @param data The bit to write, data == 0 means set bit = 0, data !=0 means set bit = 1.
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_write_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t data);
/**
* @brief Write multiple bits of a byte to an i2c device with 8-bit internal register/memory address
*
* @param dev_handle I2C device handle
* @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address.
* @param bit_start The bit to start from, 0 - 7, MSB at 0
* @param length The number of bits to write, 1 - 8
* @param data The bits to write.
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_write_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t data);
/**************************************** Public Functions (Low level)*********************************************/
/**
* @brief I2C master send queued commands create by ``i2c_cmd_link_create`` .
* This function will trigger sending all queued commands.
* The task will be blocked until all the commands have been sent out.
* If I2C_BUS_DYNAMIC_CONFIG enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer,
* hence multiple devices with different configs on a single bus can be supported.
* @note
* Only call this function when ``i2c_bus_read/write_xx`` do not meet the requirements
*
* @param dev_handle I2C device handle
* @param cmd I2C command handler
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_cmd_begin(i2c_bus_device_handle_t dev_handle, i2c_cmd_handle_t cmd);
/**
* @brief Write date to an i2c device with 16-bit internal reg/mem address
*
* @param dev_handle I2C device handle
* @param mem_address The internal 16-bit reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address.
* @param data_len Number of bytes to write
* @param data Pointer to the bytes to write.
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, const uint8_t *data);
/**
* @brief Read date from i2c device with 16-bit internal reg/mem address
*
* @param dev_handle I2C device handle
* @param mem_address The internal 16-bit reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address.
* @param data_len Number of bytes to read
* @param data Pointer to a buffer to save the data that was read
* @return esp_err_t
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_FAIL Sending command error, slave doesn't ACK the transfer.
* - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode.
* - ESP_ERR_TIMEOUT Operation timeout because the bus is busy.
*/
esp_err_t i2c_bus_read_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, uint8_t *data);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,125 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __I2S_LCD_DRIVER_H__
#define __I2S_LCD_DRIVER_H__
#include "driver/i2s.h"
#ifdef __cplusplus
extern "C"
{
#endif
#define LCD_CMD_LEV (0)
#define LCD_DATA_LEV (1)
typedef void * i2s_lcd_handle_t; /** Handle of i2s lcd driver */
/**
* @brief Configuration of i2s lcd mode
*
*/
typedef struct {
int8_t data_width; /*!< Parallel data width, 16bit or 8bit available */
int8_t pin_data_num[16]; /*!< Parallel data output IO*/
int8_t pin_num_cs; /*!< CS io num */
int8_t pin_num_wr; /*!< Write clk io*/
int8_t pin_num_rs; /*!< RS io num */
int clk_freq; /*!< I2s clock frequency */
i2s_port_t i2s_port; /*!< I2S port number */
bool swap_data; /*!< Swap the 2 bytes of RGB565 color */
uint32_t buffer_size; /*!< DMA buffer size */
} i2s_lcd_config_t;
/**
* @brief Initilize i2s lcd driver.
*
* @param config configuration of i2s
*
* @return A handle to the created i2s lcd driver, or NULL in case of error.
*/
i2s_lcd_handle_t i2s_lcd_driver_init(const i2s_lcd_config_t *config);
/**
* @brief Deinit i2s lcd driver.
*
* @param handle i2s lcd driver handle to deinitilize
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG handle is invalid
*/
esp_err_t i2s_lcd_driver_deinit(i2s_lcd_handle_t handle);
/**
* @brief Write a data to LCD
*
* @param handle i2s lcd driver handle
* @param data Data to write
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG handle is invalid
*/
esp_err_t i2s_lcd_write_data(i2s_lcd_handle_t handle, uint16_t data);
/**
* @brief Write a command to LCD
*
* @param handle Handle of i2s lcd driver
* @param cmd command to write
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG handle is invalid
*/
esp_err_t i2s_lcd_write_cmd(i2s_lcd_handle_t handle, uint16_t cmd);
/**
* @brief Write block data to LCD
*
* @param handle Handle of i2s lcd driver
* @param data Pointer of data
* @param length length of data
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG handle is invalid
*/
esp_err_t i2s_lcd_write(i2s_lcd_handle_t handle, const uint8_t *data, uint32_t length);
/**
* @brief acquire a lock
*
* @param handle Handle of i2s lcd driver
*
* @return Always return ESP_OK
*/
esp_err_t i2s_lcd_acquire(i2s_lcd_handle_t handle);
/**
* @brief release a lock
*
* @param handle Handle of i2s lcd driver
*
* @return Always return ESP_OK
*/
esp_err_t i2s_lcd_release(i2s_lcd_handle_t handle);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,164 @@
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_SPI_BUS_H_
#define _IOT_SPI_BUS_H_
#include "driver/spi_master.h"
#include "driver/gpio.h"
#define NULL_SPI_CS_PIN -1 /*!< set cs_io_num to NULL_SPI_CS_PIN if spi device has no CP pin */
typedef void *spi_bus_handle_t; /*!< spi bus handle */
typedef void *spi_bus_device_handle_t; /*!< spi device handle */
/**
* spi bus initialization parameters.
* */
typedef struct {
gpio_num_t miso_io_num; /*!< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.*/
gpio_num_t mosi_io_num; /*!< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.*/
gpio_num_t sclk_io_num; /*!< GPIO pin for Spi CLocK signal, or -1 if not used*/
int max_transfer_sz; /*!< <Maximum length of bytes available to send, if < 4096, 4096 will be set*/
}spi_config_t;
/**
* spi device initialization parameters.
* */
typedef struct {
gpio_num_t cs_io_num; /*!< GPIO pin to select this device (CS), or -1 if not used*/
uint8_t mode; /*!< modes (0,1,2,3) that correspond to the four possible clocking configurations*/
int clock_speed_hz; /*!< spi clock speed, divisors of 80MHz, in Hz. See ``SPI_MASTER_FREQ_*`*/
}spi_device_config_t;
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Create and initialize a spi bus and return the spi bus handle
*
* @param host_id SPI peripheral that controls this bus, SPI2_HOST or SPI3_HOST
* @param bus_conf spi bus configurations details in spi_config_t
* @return spi_bus_handle_t handle for spi bus operation, NULL if failed.
*/
spi_bus_handle_t spi_bus_create(spi_host_device_t host_id, const spi_config_t *bus_conf);
/**
* @brief Deinitialize and delete the spi bus
*
* @param p_bus_handle pointer to spi bus handle, if delete succeed handle will set to NULL.
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_FAIL Fail
* - ESP_OK Success
*/
esp_err_t spi_bus_delete(spi_bus_handle_t *p_bus_handle);
/**
* @brief Create and add a device on the spi bus.
*
* @param bus_handle handle for spi bus operation.
* @param device_conf spi device configurations details in spi_device_config_t
* @return spi_bus_device_handle_t handle for device operation, NULL if failed.
*/
spi_bus_device_handle_t spi_bus_device_create(spi_bus_handle_t bus_handle, const spi_device_config_t *device_conf);
/**
* @brief Deinitialize and remove the device from spi bus.
*
* @param p_dev_handle pointer to device handle, if delete succeed handle will set to NULL.
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_FAIL Fail
* - ESP_OK Success
*/
esp_err_t spi_bus_device_delete(spi_bus_device_handle_t *p_dev_handle);
/**
* @brief Transfer one byte with the device.
*
* @param dev_handle handle for device operation.
* @param data_out data will send to device.
* @param data_in pointer to receive buffer, set NULL to skip receive phase.
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_TIMEOUT if bus is busy
* - ESP_OK on success
*/
esp_err_t spi_bus_transfer_byte(spi_bus_device_handle_t dev_handle, uint8_t data_out, uint8_t *data_in);
/**
* @brief Transfer multi-bytes with the device.
*
* @param dev_handle handle for device operation.
* @param data_out pointer to sent buffer, set NULL to skip sent phase.
* @param data_in pointer to receive buffer, set NULL to skip receive phase.
* @param data_len number of bytes will transfer.
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_TIMEOUT if bus is busy
* - ESP_OK on success
*/
esp_err_t spi_bus_transfer_bytes(spi_bus_device_handle_t dev_handle, const uint8_t *data_out, uint8_t *data_in, uint32_t data_len);
/**************************************** Public Functions (Low level)*********************************************/
/**
* @brief Send a polling transaction, wait for it to complete, and return the result
* @note
* Only call this function when ``spi_bus_transfer_xx`` do not meet the requirements
*
* @param dev_handle handle for device operation.
* @param p_trans Description of transaction to execute
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_TIMEOUT if bus is busy
* - ESP_OK on success
*/
esp_err_t spi_bus_transmit_begin(spi_bus_device_handle_t dev_handle, spi_transaction_t *p_trans);
/**
* @brief Transfer one 16-bit value with the device. using msb by default.
* For example 0x1234, 0x12 will send first then 0x34.
*
* @param dev_handle handle for device operation.
* @param data_out data will send to device.
* @param data_in pointer to receive buffer, set NULL to skip receive phase.
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_TIMEOUT if bus is busy
* - ESP_OK on success
*/
esp_err_t spi_bus_transfer_reg16(spi_bus_device_handle_t dev_handle, uint16_t data_out, uint16_t *data_in);
/**
* @brief Transfer one 32-bit value with the device. using msb by default.
* For example 0x12345678, 0x12 will send first, 0x78 will send in the end.
*
* @param dev_handle handle for device operation.
* @param data_out data will send to device.
* @param data_in pointer to receive buffer, set NULL to skip receive phase.
* @return esp_err_t
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_TIMEOUT if bus is busy
* - ESP_OK on success
*/
esp_err_t spi_bus_transfer_reg32(spi_bus_device_handle_t dev_handle, uint32_t data_out, uint32_t *data_in);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,253 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/spi_master.h"
#include "driver/spi_common.h"
#include "spi_bus.h"
typedef struct {
spi_host_device_t host_id; /*!<spi device number */
bool is_init;
spi_bus_config_t conf; /*!<spi bus active configuration */
} _spi_bus_t;
typedef struct {
spi_device_handle_t handle;
spi_bus_handle_t spi_bus; /*!<spi bus handle */
spi_device_interface_config_t conf; /*!<spi device active configuration */
SemaphoreHandle_t mutex; /* mutex to achive device thread-safe*/
} _spi_device_t;
static const char *TAG = "spi_bus";
static _spi_bus_t s_spi_bus[2];
#define ESP_SPI_MUTEX_TICKS_TO_WAIT 2
#define SPI_BUS_CHECK(a, str, ret) if(!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
return (ret); \
}
#define SPI_BUS_CHECK_GOTO(a, str, lable) if(!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
goto lable; \
}
#define SPI_DEVICE_MUTEX_TAKE(p_spi_dev, ret) if (!xSemaphoreTake((p_spi_dev)->mutex, ESP_SPI_MUTEX_TICKS_TO_WAIT)) { \
ESP_LOGE(TAG, "spi device(%d) take mutex timeout, max wait = %d ticks", (int32_t)((p_spi_dev)->handle), ESP_SPI_MUTEX_TICKS_TO_WAIT); \
return (ret); \
}
#define SPI_DEVICE_MUTEX_GIVE(p_spi_dev, ret) if (!xSemaphoreGive((p_spi_dev)->mutex)) { \
ESP_LOGE(TAG, "spi device(%d) give mutex failed", (int32_t)((p_spi_dev)->handle)); \
return (ret); \
}
spi_bus_handle_t spi_bus_create(spi_host_device_t host_id, const spi_config_t *bus_conf)
{
SPI_BUS_CHECK(SPI1_HOST < host_id && host_id <= SPI3_HOST, "Invalid spi host_id", NULL);
uint8_t index = host_id - 1; //find related index
spi_bus_config_t buscfg = {
.miso_io_num = bus_conf->miso_io_num,
.mosi_io_num = bus_conf->mosi_io_num,
.sclk_io_num = bus_conf->sclk_io_num,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = bus_conf->max_transfer_sz,
};
int dma_chan = host_id; //set dma channel equals to host_id by default
esp_err_t ret = spi_bus_initialize(host_id, &buscfg, dma_chan);
SPI_BUS_CHECK(ESP_OK == ret, "spi bus create failed", NULL);
s_spi_bus[index].host_id = host_id;
memcpy(&s_spi_bus[index].conf, &buscfg, sizeof(spi_bus_config_t));
s_spi_bus[index].is_init = true;
ESP_LOGI(TAG, "SPI%d bus created", host_id + 1);
return (spi_bus_handle_t)&s_spi_bus[index];
}
esp_err_t spi_bus_delete(spi_bus_handle_t *p_bus_handle)
{
SPI_BUS_CHECK((NULL != p_bus_handle) && (NULL != *p_bus_handle), "Handle error", ESP_ERR_INVALID_ARG);
_spi_bus_t *spi_bus = (_spi_bus_t *)(*p_bus_handle);
if (!spi_bus->is_init) {
ESP_LOGW(TAG, "spi_bus%d has been de-inited", spi_bus->host_id);
return ESP_ERR_INVALID_STATE;
}
esp_err_t ret = spi_bus_free(spi_bus->host_id);
SPI_BUS_CHECK(ESP_OK == ret, "spi bus delete failed", ESP_FAIL);
ESP_LOGI(TAG, "SPI%d bus delete", spi_bus->host_id + 1);
memset(spi_bus, 0, sizeof(_spi_bus_t));
*p_bus_handle = NULL;
return ESP_OK;
}
spi_bus_device_handle_t spi_bus_device_create(spi_bus_device_handle_t bus_handle, const spi_device_config_t *device_conf)
{
SPI_BUS_CHECK(NULL != bus_handle, "Pointer error", NULL);
_spi_bus_t *spi_bus = (_spi_bus_t *)bus_handle;
_spi_device_t *spi_dev = malloc(sizeof(_spi_device_t));
spi_device_interface_config_t devcfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.clock_speed_hz = device_conf->clock_speed_hz,
.duty_cycle_pos = 128, //50% duty cycle
.mode = device_conf->mode,
.spics_io_num = device_conf->cs_io_num,
.cs_ena_posttrans = 3, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
.queue_size = 3
};
esp_err_t ret = spi_bus_add_device(spi_bus->host_id, &devcfg, &spi_dev->handle);
SPI_BUS_CHECK_GOTO(ESP_OK == ret, "add spi device failed", cleanup_device);
spi_dev->mutex = xSemaphoreCreateMutex();
SPI_BUS_CHECK_GOTO(NULL != spi_dev->mutex, "spi device create mutex failed", cleanup_device);
spi_dev->spi_bus = bus_handle;
memcpy(&spi_dev->conf, &devcfg, sizeof(spi_device_interface_config_t));
ESP_LOGI(TAG, "SPI%d bus device added, CS=%d Mode=%u Speed=%d", spi_bus->host_id + 1, device_conf->cs_io_num, device_conf->mode, device_conf->clock_speed_hz);
return (spi_bus_device_handle_t)spi_dev;
cleanup_device:
free(spi_dev);
return NULL;
}
esp_err_t spi_bus_device_delete(spi_bus_device_handle_t *p_dev_handle)
{
SPI_BUS_CHECK((NULL != p_dev_handle) && (NULL != *p_dev_handle), "Pointer error", ESP_ERR_INVALID_ARG);
_spi_device_t *spi_dev = (_spi_device_t *)(*p_dev_handle);
_spi_bus_t *spi_bus = (_spi_bus_t *)(spi_dev->spi_bus);
SPI_DEVICE_MUTEX_TAKE(spi_dev, ESP_FAIL);
esp_err_t ret = spi_bus_remove_device(spi_dev->handle);
SPI_DEVICE_MUTEX_GIVE(spi_dev, ESP_FAIL);
SPI_BUS_CHECK(ESP_OK == ret, "spi bus delete device failed", ret);
vSemaphoreDelete(spi_dev->mutex);
ESP_LOGI(TAG, "SPI%d device removed, CS=%d", spi_bus->host_id + 1, spi_dev->conf.spics_io_num);
free(spi_dev);
*p_dev_handle = NULL;
return ESP_OK;
}
/* this function should lable with inline*/
inline static esp_err_t _spi_device_polling_transmit(spi_bus_device_handle_t dev_handle, spi_transaction_t *trans)
{
SPI_BUS_CHECK(NULL != dev_handle, "Pointer error", ESP_ERR_INVALID_ARG);
_spi_device_t *spi_dev = (_spi_device_t *)(dev_handle);
esp_err_t ret;
SPI_DEVICE_MUTEX_TAKE(spi_dev, ESP_FAIL);
ret = spi_device_polling_transmit(spi_dev->handle, trans);
SPI_DEVICE_MUTEX_GIVE(spi_dev, ESP_FAIL);
return ret;
}
esp_err_t spi_bus_transfer_byte(spi_bus_device_handle_t dev_handle, uint8_t data_out, uint8_t *data_in)
{
esp_err_t ret;
spi_transaction_t trans = {
.length = 8,
.flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA,
.tx_data = {
[0] = data_out
}
};
ret = _spi_device_polling_transmit(dev_handle, &trans);
SPI_BUS_CHECK(ret == ESP_OK, "spi transfer byte failed", ret);
if (data_in) {
*data_in = trans.rx_data[0];
}
return ESP_OK;
}
esp_err_t spi_bus_transfer_bytes(spi_bus_device_handle_t dev_handle, const uint8_t *data_out, uint8_t *data_in, uint32_t data_len)
{
esp_err_t ret;
spi_transaction_t trans = {
.length = data_len * 8,
.tx_buffer = NULL,
.rx_buffer = NULL
};
if (data_out) {
trans.tx_buffer = data_out;
}
if (data_in) {
trans.rx_buffer = data_in;
}
ret = _spi_device_polling_transmit(dev_handle, &trans);
SPI_BUS_CHECK(ret == ESP_OK, "spi transfer bytes failed", ret);
return ESP_OK;
}
/**************************************** Public Functions (Low level)*********************************************/
esp_err_t spi_bus_transmit_begin(spi_bus_device_handle_t dev_handle, spi_transaction_t *p_trans)
{
return _spi_device_polling_transmit(dev_handle, p_trans);
}
esp_err_t spi_bus_transfer_reg16(spi_bus_device_handle_t dev_handle, uint16_t data_out, uint16_t *data_in)
{
esp_err_t ret;
spi_transaction_t trans = {
.length = 16,
.flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA,
/* default MSB first */
.tx_data = {
[0] = (data_out >> 8) & 0xff,
[1] = data_out & 0xff,
}
};
ret = _spi_device_polling_transmit(dev_handle, &trans);
SPI_BUS_CHECK(ret == ESP_OK, "spi transfer reg16 failed", ret);
if (data_in) {
*data_in = (trans.rx_data[0] << 8) | (trans.rx_data[1]);
}
return ESP_OK;
}
esp_err_t spi_bus_transfer_reg32(spi_bus_device_handle_t dev_handle, uint32_t data_out, uint32_t *data_in)
{
esp_err_t ret;
spi_transaction_t trans = {
.length = 32,
.flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA,
/* default MSB first */
.tx_data = {
[0] = (data_out >> 24) & 0xff,
[1] = (data_out >> 16) & 0xff,
[2] = (data_out >> 8) & 0xff,
[3] = data_out & 0xff
}
};
ret = _spi_device_polling_transmit(dev_handle, &trans);
SPI_BUS_CHECK(ret == ESP_OK, "spi transfer reg32 failed", ret);
if (data_in) {
*data_in = (trans.rx_data[0] << 24) | (trans.rx_data[1] << 16) | (trans.rx_data[2] << 8) | (trans.rx_data[3]);
}
return ESP_OK;
}

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "test_i2c_bus.c" "test_spi_bus.c"
INCLUDE_DIRS .
REQUIRES test_utils bus)

View File

@@ -0,0 +1,5 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@@ -0,0 +1,269 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "test_utils.h"
#include "unity_config.h"
#include "i2c_bus.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define I2C_MASTER_SCL_IO (gpio_num_t)22 /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO (gpio_num_t)21 /*!< gpio number for I2C master data */
#define DATA_LENGTH 512 /*!<Data buffer length for test buffer*/
#define RW_TEST_LENGTH 129 /*!<Data length for r/w test, any value from 0-DATA_LENGTH*/
#define DELAY_TIME_BETWEEN_ITEMS_MS 1234 /*!< delay time between different test items */
#define I2C_SLAVE_SCL_IO 17 /*!<gpio number for i2c slave clock */
#define I2C_SLAVE_SDA_IO 16 /*!<gpio number for i2c slave data */
#define I2C_MASTER_NUM I2C_NUM_1 /*!<I2C port number for master dev */
#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */
#define I2C_SLAVE_NUM I2C_NUM_0 /*!<I2C port number for slave dev */
#define I2C_SLAVE_TX_BUF_LEN (2*DATA_LENGTH) /*!<I2C slave tx buffer size */
#define I2C_SLAVE_RX_BUF_LEN (2*DATA_LENGTH) /*!<I2C slave rx buffer size */
#define ESP_SLAVE_ADDR 0x28 /*!< ESP32 slave address, you can set any 7bit value */
void i2c_bus_init_deinit_test()
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_bus_handle_t i2c0_bus_1 = i2c_bus_create(I2C_NUM_0, &conf);
TEST_ASSERT(i2c0_bus_1 != NULL);
/** configs not change**/
i2c0_bus_1 = i2c_bus_create(I2C_NUM_0, &conf);
TEST_ASSERT(i2c0_bus_1 != NULL);
/** configs not change**/
conf.master.clk_speed *= 2;
i2c0_bus_1 = i2c_bus_create(I2C_NUM_0, &conf);
TEST_ASSERT(i2c0_bus_1 != NULL);
vTaskDelay(100 / portTICK_RATE_MS);
TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c0_bus_1));
TEST_ASSERT(i2c0_bus_1 == NULL);
}
void i2c_bus_device_add_test()
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_bus_handle_t i2c0_bus_1 = i2c_bus_create(I2C_NUM_0, &conf);
TEST_ASSERT(i2c0_bus_1 != NULL);
i2c_bus_device_handle_t i2c_device1 = i2c_bus_device_create(i2c0_bus_1, 0x01, 400000);
TEST_ASSERT(i2c_device1 != NULL);
i2c_bus_device_handle_t i2c_device2 = i2c_bus_device_create(i2c0_bus_1, 0x01, 100000);
TEST_ASSERT(i2c_device2 != NULL);
i2c_bus_device_delete(&i2c_device1);
TEST_ASSERT(i2c_device1 == NULL);
i2c_bus_device_delete(&i2c_device2);
TEST_ASSERT(i2c_device2 == NULL);
TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c0_bus_1));
TEST_ASSERT(i2c0_bus_1 == NULL);
}
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2)
// print the reading buffer
static void disp_buf(uint8_t *buf, int len)
{
int i;
for (i = 0; i < len; i++) {
printf("%02x ", buf[i]);
if ((i + 1) % 16 == 0) {
printf("\n");
}
}
printf("\n");
}
static void i2c_master_write_test(void)
{
uint8_t *data_wr = (uint8_t *) malloc(DATA_LENGTH);
int i;
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_bus_handle_t i2c0_bus = i2c_bus_create(I2C_MASTER_NUM, &conf);
TEST_ASSERT(i2c0_bus != NULL);
i2c_bus_device_handle_t i2c_device1 = i2c_bus_device_create(i2c0_bus, ESP_SLAVE_ADDR, 0);
TEST_ASSERT(i2c_device1 != NULL);
unity_wait_for_signal("i2c slave init finish");
unity_send_signal("master write");
for (i = 0; i < DATA_LENGTH / 2; i++) {
data_wr[i] = i;
}
i2c_bus_write_bytes(i2c_device1, NULL_I2C_MEM_ADDR, DATA_LENGTH / 2, data_wr);
disp_buf(data_wr, i + 1);
free(data_wr);
i2c_bus_device_delete(&i2c_device1);
TEST_ASSERT(i2c_device1 == NULL);
TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c0_bus));
TEST_ASSERT(i2c0_bus == NULL);
}
static void i2c_slave_read_test(void)
{
uint8_t *data_rd = (uint8_t *) malloc(DATA_LENGTH);
int size_rd = 0;
int len = 0;
i2c_config_t conf_slave = {
.mode = I2C_MODE_SLAVE,
.sda_io_num = I2C_SLAVE_SDA_IO,
.scl_io_num = I2C_SLAVE_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.slave.addr_10bit_en = 0,
.slave.slave_addr = ESP_SLAVE_ADDR,
};
TEST_ESP_OK(i2c_param_config(I2C_SLAVE_NUM, &conf_slave));
TEST_ESP_OK(i2c_driver_install(I2C_SLAVE_NUM, I2C_MODE_SLAVE,
I2C_SLAVE_RX_BUF_LEN,
I2C_SLAVE_TX_BUF_LEN, 0));
unity_send_signal("i2c slave init finish");
unity_wait_for_signal("master write");
while (1) {
len = i2c_slave_read_buffer(I2C_SLAVE_NUM, data_rd + size_rd, DATA_LENGTH, 10000 / portTICK_RATE_MS);
if (len == 0) {
break;
}
size_rd += len;
}
disp_buf(data_rd, size_rd);
for (int i = 0; i < size_rd; i++) {
TEST_ASSERT(data_rd[i] == i);
}
free(data_rd);
unity_send_signal("ready to delete");
TEST_ESP_OK(i2c_driver_delete(I2C_SLAVE_NUM));
}
TEST_CASE_MULTIPLE_DEVICES("I2C master write slave test", "[i2c_bus]", i2c_master_write_test, i2c_slave_read_test);
static void master_read_slave_test(void)
{
uint8_t *data_rd = (uint8_t *) malloc(DATA_LENGTH);
memset(data_rd, 0, DATA_LENGTH);
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_bus_handle_t i2c0_bus = i2c_bus_create(I2C_MASTER_NUM, &conf);
TEST_ASSERT(i2c0_bus != NULL);
i2c_bus_device_handle_t i2c_device1 = i2c_bus_device_create(i2c0_bus, ESP_SLAVE_ADDR, 0);
TEST_ASSERT(i2c_device1 != NULL);
unity_wait_for_signal("i2c slave init finish");
unity_send_signal("slave write");
unity_wait_for_signal("master read");
i2c_bus_read_bytes(i2c_device1, NULL_I2C_MEM_ADDR, RW_TEST_LENGTH, data_rd);
vTaskDelay(100 / portTICK_RATE_MS);
for (int i = 0; i < RW_TEST_LENGTH; i++) {
printf("%d\n", data_rd[i]);
TEST_ASSERT(data_rd[i] == i);
}
free(data_rd);
unity_send_signal("ready to delete");
i2c_bus_device_delete(&i2c_device1);
TEST_ASSERT(i2c_device1 == NULL);
TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c0_bus));
TEST_ASSERT(i2c0_bus == NULL);
}
static void slave_write_buffer_test(void)
{
uint8_t *data_wr = (uint8_t *) malloc(DATA_LENGTH);
int size_rd;
i2c_config_t conf_slave = {
.mode = I2C_MODE_SLAVE,
.sda_io_num = I2C_SLAVE_SDA_IO,
.scl_io_num = I2C_SLAVE_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.slave.addr_10bit_en = 0,
.slave.slave_addr = ESP_SLAVE_ADDR,
};
TEST_ESP_OK(i2c_param_config(I2C_SLAVE_NUM, &conf_slave));
TEST_ESP_OK(i2c_driver_install(I2C_SLAVE_NUM, I2C_MODE_SLAVE,
I2C_SLAVE_RX_BUF_LEN,
I2C_SLAVE_TX_BUF_LEN, 0));
unity_send_signal("i2c slave init finish");
unity_wait_for_signal("slave write");
for (int i = 0; i < DATA_LENGTH / 2; i++) {
data_wr[i] = i;
}
size_rd = i2c_slave_write_buffer(I2C_SLAVE_NUM, data_wr, RW_TEST_LENGTH, 2000 / portTICK_RATE_MS);
disp_buf(data_wr, size_rd);
unity_send_signal("master read");
unity_wait_for_signal("ready to delete");
free(data_wr);
i2c_driver_delete(I2C_SLAVE_NUM);
}
TEST_CASE_MULTIPLE_DEVICES("I2C master read slave test", "[i2c_bus]", master_read_slave_test, slave_write_buffer_test);
#endif //DISABLED_FOR_TARGET(ESP32S2)
TEST_CASE("i2c bus init-deinit test", "[bus][i2c_bus]")
{
i2c_bus_init_deinit_test();
i2c_bus_device_add_test();
}

View File

@@ -0,0 +1,119 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include "unity.h"
#include "unity_config.h"
#include "spi_bus.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define SPI_SCK_IO 18
#define SPI_MOSI_IO 23
#define SPI_MISO_IO 19
void spi_bus_init_deinit_test()
{
spi_bus_handle_t bus_handle = NULL;
spi_config_t bus_conf = {
.miso_io_num = SPI_MISO_IO,
.mosi_io_num = SPI_MOSI_IO,
.sclk_io_num = SPI_SCK_IO,
};
bus_handle = spi_bus_create(SPI2_HOST, &bus_conf);
TEST_ASSERT(bus_handle != NULL);
TEST_ASSERT(ESP_OK == spi_bus_delete(&bus_handle));
TEST_ASSERT(bus_handle == NULL);
bus_handle = spi_bus_create(SPI3_HOST, &bus_conf);
TEST_ASSERT(bus_handle != NULL);
TEST_ASSERT(ESP_OK == spi_bus_delete(&bus_handle));
TEST_ASSERT(bus_handle == NULL);
}
/* connect mosi with miso for transfer test */
void spi_bus_transfer_test()
{
spi_bus_handle_t bus_handle = NULL;
spi_config_t bus_conf = {
.miso_io_num = SPI_MISO_IO,
.mosi_io_num = SPI_MOSI_IO,
.sclk_io_num = SPI_SCK_IO,
};
spi_device_config_t device_conf = {
.cs_io_num = NULL_SPI_CS_PIN,
.mode = 0,
.clock_speed_hz = 20 * 1000 * 1000,
};
bus_handle = spi_bus_create(SPI2_HOST, &bus_conf);
TEST_ASSERT(bus_handle != NULL);
spi_bus_device_handle_t device_handle = NULL;
device_handle = spi_bus_device_create(bus_handle, &device_conf);
TEST_ASSERT(device_handle != NULL);
printf("************byte transfer test***************\n");
for (uint8_t i = 0; i < 200; i++) {
uint8_t in = 0;
TEST_ASSERT(ESP_OK == spi_bus_transfer_byte(device_handle, i, &in));
TEST_ASSERT_EQUAL_UINT8(i, in);
printf("in=%u\n", in);
}
vTaskDelay(2);
printf("************bytes transfer test***************\n");
uint8_t data[200] = {0};
uint8_t data_in[200] = {0};
for (uint8_t i = 0; i < 200; i++) {
data[i] = i;
}
TEST_ASSERT(ESP_OK == spi_bus_transfer_bytes(device_handle, data, data_in, 200));
for (uint8_t i = 0; i < 200; i++) {
printf("%u ", data_in[i]);
}
printf("\n");
vTaskDelay(2);
printf("************reg16 transfer test***************\n");
for (uint16_t i = (0xffff - 200); i < 0xffff; i++) {
uint16_t in = 0;
TEST_ASSERT(ESP_OK == spi_bus_transfer_reg16(device_handle, i, &in));
TEST_ASSERT_EQUAL_UINT16(i, in);
printf("in=%u\n", in);
}
vTaskDelay(2);
printf("************reg32 transfer test***************\n");
for (uint32_t i = (0xffffffff - 200); i < 0xffffffff; i++) {
uint32_t in = 0;
TEST_ASSERT(ESP_OK == spi_bus_transfer_reg32(device_handle, i, &in));
TEST_ASSERT_EQUAL_UINT32(i, in);
printf("in=%x\n", in);
}
TEST_ASSERT(ESP_OK == spi_bus_device_delete(&device_handle));
TEST_ASSERT(device_handle == NULL);
TEST_ASSERT(ESP_OK == spi_bus_delete(&bus_handle));
TEST_ASSERT(bus_handle == NULL);
}
TEST_CASE("spi bus init-deinit test", "[bus]spi_bus]")
{
spi_bus_init_deinit_test();
}
TEST_CASE("spi bus transfer test", "[bus][spi_bus]")
{
spi_bus_transfer_test();
}

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "button_adc.c" "button_gpio.c" "iot_button.c"
INCLUDE_DIRS include
PRIV_REQUIRES esp_adc_cal)

View File

@@ -0,0 +1,46 @@
menu "IoT Button"
config BUTTON_PERIOD_TIME_MS
int "BUTTON PERIOD TIME (MS)"
range 2 20
default 5
help
"Button scan interval"
config BUTTON_DEBOUNCE_TICKS
int "BUTTON DEBOUNCE TICKS"
range 1 8
default 2
config BUTTON_SHORT_PRESS_TIME_MS
int "BUTTON SHORT PRESS TIME (MS)"
range 50 800
default 180
config BUTTON_LONG_PRESS_TIME_MS
int "BUTTON LONG PRESS TIME (MS)"
range 500 5000
default 1500
config ADC_BUTTON_MAX_CHANNEL
int "ADC BUTTON MAX CHANNEL"
range 1 5
default 3
help
"Maximum number of channels for ADC buttons"
config ADC_BUTTON_MAX_BUTTON_PER_CHANNEL
int "ADC BUTTON MAX BUTTON PER CHANNEL"
range 1 10
default 8
help
"Maximum number of buttons per channel"
config ADC_BUTTON_SAMPLE_TIMES
int "ADC BUTTON SAMPLE TIMES"
range 1 4
default 1
help
"Number of samples per scan"
endmenu

View File

@@ -0,0 +1,208 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"
#include "button_adc.h"
#include "esp_timer.h"
static const char *TAG = "adc button";
#define ADC_BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
#define DEFAULT_VREF 1100
#define NO_OF_SAMPLES CONFIG_ADC_BUTTON_SAMPLE_TIMES //Multisampling
#if CONFIG_IDF_TARGET_ESP32
#define ADC_BUTTON_WIDTH ADC_WIDTH_BIT_12
#elif CONFIG_IDF_TARGET_ESP32S2
#define ADC_BUTTON_WIDTH ADC_WIDTH_BIT_13
#endif
#define ADC_BUTTON_ATTEN ADC_ATTEN_DB_11
#define ADC_BUTTON_ADC_UNIT ADC_UNIT_1
#define ADC_BUTTON_MAX_CHANNEL CONFIG_ADC_BUTTON_MAX_CHANNEL
#define ADC_BUTTON_MAX_BUTTON CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL
typedef struct {
uint16_t min;
uint16_t max;
} button_data_t;
typedef struct {
adc1_channel_t channel;
uint8_t is_init;
button_data_t btns[ADC_BUTTON_MAX_BUTTON]; /* all button on the channel */
uint64_t last_time; /* the last time of adc sample */
} btn_adc_channel_t;
typedef struct {
bool is_configured;
esp_adc_cal_characteristics_t adc_chars;
btn_adc_channel_t ch[ADC_BUTTON_MAX_CHANNEL];
uint8_t ch_num;
} adc_button_t;
static adc_button_t g_button = {0};
static int find_unused_channel(void)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (0 == g_button.ch[i].is_init) {
return i;
}
}
return -1;
}
static int find_channel(adc1_channel_t channel)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (channel == g_button.ch[i].channel) {
return i;
}
}
return -1;
}
esp_err_t button_adc_init(const button_adc_config_t *config)
{
ADC_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG);
ADC_BTN_CHECK(config->adc_channel < ADC1_CHANNEL_MAX, "channel out of range", ESP_ERR_NOT_SUPPORTED);
ADC_BTN_CHECK(config->button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_NOT_SUPPORTED);
ADC_BTN_CHECK(config->max > 0, "key max voltage invalid", ESP_ERR_INVALID_ARG);
int ch_index = find_channel(config->adc_channel);
if (ch_index >= 0) { /**< the channel has been initialized */
ADC_BTN_CHECK(g_button.ch[ch_index].btns[config->button_index].max == 0, "The button_index has been used", ESP_ERR_INVALID_STATE);
} else { /**< this is a new channel */
int unused_ch_index = find_unused_channel();
ADC_BTN_CHECK(unused_ch_index >= 0, "exceed max channel number, can't create a new channel", ESP_ERR_INVALID_STATE);
ch_index = unused_ch_index;
}
/** initialize adc */
if (0 == g_button.is_configured) {
//Configure ADC
adc1_config_width(ADC_BUTTON_WIDTH);
//Characterize ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, ADC_BUTTON_WIDTH, DEFAULT_VREF, &g_button.adc_chars);
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
ESP_LOGI(TAG, "Characterized using Two Point Value");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
ESP_LOGI(TAG, "Characterized using eFuse Vref");
} else {
ESP_LOGI(TAG, "Characterized using Default Vref");
}
g_button.is_configured = 1;
}
/** initialize adc channel */
if (0 == g_button.ch[ch_index].is_init) {
adc1_config_channel_atten(config->adc_channel, ADC_BUTTON_ATTEN);
g_button.ch[ch_index].channel = config->adc_channel;
g_button.ch[ch_index].is_init = 1;
g_button.ch[ch_index].last_time = 0;
}
g_button.ch[ch_index].btns[config->button_index].max = config->max;
g_button.ch[ch_index].btns[config->button_index].min = config->min;
g_button.ch_num++;
return ESP_OK;
}
esp_err_t button_adc_deinit(adc1_channel_t channel, int button_index)
{
ADC_BTN_CHECK(channel < ADC1_CHANNEL_MAX, "channel out of range", ESP_ERR_INVALID_ARG);
ADC_BTN_CHECK(button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_INVALID_ARG);
int ch_index = find_channel(channel);
ADC_BTN_CHECK(ch_index >= 0, "can't find the channel", ESP_ERR_INVALID_ARG);
g_button.ch[ch_index].btns[button_index].max = 0;
g_button.ch[ch_index].btns[button_index].min = 0;
/** check button usage on the channel*/
uint8_t unused_button = 0;
for (size_t i = 0; i < ADC_BUTTON_MAX_BUTTON; i++) {
if (0 == g_button.ch[ch_index].btns[i].max) {
unused_button++;
}
}
if (unused_button == ADC_BUTTON_MAX_BUTTON && g_button.ch[ch_index].is_init) { /**< if all button is unused, deinit the channel */
/* TODO: to deinit the channel */
g_button.ch[ch_index].is_init = 0;
g_button.ch[ch_index].channel = ADC1_CHANNEL_MAX;
ESP_LOGD(TAG, "all button is unused on channel%d, deinit the channel", g_button.ch[ch_index].channel);
}
/** check channel usage on the adc*/
uint8_t unused_ch = 0;
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (0 == g_button.ch[i].is_init) {
unused_ch++;
}
}
if (unused_ch == ADC_BUTTON_MAX_CHANNEL && g_button.is_configured) { /**< if all channel is unused, deinit the adc */
/* TODO: to deinit the peripheral adc */
g_button.is_configured = false;
memset(&g_button, 0, sizeof(adc_button_t));
ESP_LOGD(TAG, "all channel is unused, , deinit adc");
}
return ESP_OK;
}
static uint32_t get_adc_volatge(adc1_channel_t channel)
{
uint32_t adc_reading = 0;
//Multisampling
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_reading += adc1_get_raw(channel);
}
adc_reading /= NO_OF_SAMPLES;
//Convert adc_reading to voltage in mV
uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, &g_button.adc_chars);
ESP_LOGV(TAG, "Raw: %d\tVoltage: %dmV", adc_reading, voltage);
return voltage;
}
uint8_t button_adc_get_key_level(void *button_index)
{
static uint16_t vol = 0;
uint32_t ch = ADC_BUTTON_SPLIT_CHANNEL(button_index);
uint32_t index = ADC_BUTTON_SPLIT_INDEX(button_index);
ADC_BTN_CHECK(ch < ADC1_CHANNEL_MAX, "channel out of range", 0);
ADC_BTN_CHECK(index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", 0);
int ch_index = find_channel(ch);
ADC_BTN_CHECK(ch_index >= 0, "The button_index is not init", 0);
/** It starts only when the elapsed time is more than 1ms */
if ((esp_timer_get_time() - g_button.ch[ch_index].last_time) > 1000) {
vol = get_adc_volatge(ch);
g_button.ch[ch_index].last_time = esp_timer_get_time();
}
if (vol <= g_button.ch[ch_index].btns[index].max &&
vol > g_button.ch[ch_index].btns[index].min) {
return 1;
}
return 0;
}

View File

@@ -0,0 +1,65 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_log.h"
#include "driver/gpio.h"
#include "button_gpio.h"
static const char *TAG = "gpio button";
#define GPIO_BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
esp_err_t button_gpio_init(const button_gpio_config_t *config)
{
GPIO_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG);
gpio_config_t gpio_conf;
gpio_conf.intr_type = GPIO_INTR_DISABLE;
gpio_conf.mode = GPIO_MODE_INPUT;
gpio_conf.pin_bit_mask = (1ULL << config->gpio_num);
if (config->active_level) {
gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
} else {
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
}
gpio_config(&gpio_conf);
return ESP_OK;
}
esp_err_t button_gpio_deinit(int gpio_num)
{
/** both disable pullup and pulldown */
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_INPUT,
.pin_bit_mask = (1ULL << gpio_num),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
gpio_config(&gpio_conf);
return ESP_OK;
}
uint8_t button_gpio_get_key_level(void *gpio_num)
{
return (uint8_t)gpio_get_level((uint32_t)gpio_num);
}

View File

@@ -0,0 +1,8 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_ADD_INCLUDEDIRS := ./include
COMPONENT_SRCDIRS := .

View File

@@ -0,0 +1,79 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_BUTTON_ADC_H_
#define _IOT_BUTTON_ADC_H_
#include "driver/gpio.h"
#include "driver/adc.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ADC_BUTTON_COMBINE(channel, index) ((channel)<<8 | (index))
#define ADC_BUTTON_SPLIT_INDEX(data) ((uint32_t)(data)&0xff)
#define ADC_BUTTON_SPLIT_CHANNEL(data) (((uint32_t)(data) >> 8) & 0xff)
/**
* @brief adc button configuration
*
*/
typedef struct {
adc1_channel_t adc_channel; /**< Channel of ADC */
uint8_t button_index; /**< button index on the channel */
uint16_t min; /**< min voltage in mv corresponding to the button */
uint16_t max; /**< max voltage in mv corresponding to the button */
} button_adc_config_t;
/**
* @brief Initialize gpio button
*
* @param config pointer of configuration struct
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is NULL.
* - ESP_ERR_NOT_SUPPORTED Arguments out of range.
* - ESP_ERR_INVALID_STATE State is error.
*/
esp_err_t button_adc_init(const button_adc_config_t *config);
/**
* @brief Deinitialize gpio button
*
* @param channel ADC channel
* @param button_index Button index on the channel
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
esp_err_t button_adc_deinit(adc1_channel_t channel, int button_index);
/**
* @brief Get the adc button level
*
* @param button_index It is compressed by ADC channel and button index, use the macro ADC_BUTTON_COMBINE to generate. It will be treated as a uint32_t variable.
*
* @return
* - 0 Not pressed
* - 1 Pressed
*/
uint8_t button_adc_get_key_level(void *button_index);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,65 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_BUTTON_GPIO_H_
#define _IOT_BUTTON_GPIO_H_
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief gpio button configuration
*
*/
typedef struct {
int32_t gpio_num;
uint8_t active_level;
} button_gpio_config_t;
/**
* @brief Initialize gpio button
*
* @param config pointer of configuration struct
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is NULL.
*/
esp_err_t button_gpio_init(const button_gpio_config_t *config);
/**
* @brief Deinitialize gpio button
*
* @param gpio_num gpio number of button
*
* @return Always return ESP_OK
*/
esp_err_t button_gpio_deinit(int gpio_num);
/**
* @brief Get current level on button gpio
*
* @param gpio_num gpio number of button, it will be treated as a uint32_t variable.
*
* @return Level on gpio
*/
uint8_t button_gpio_get_key_level(void *gpio_num);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,131 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _IOT_BUTTON_H_
#define _IOT_BUTTON_H_
#include "button_adc.h"
#include "button_gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (* button_cb_t)(void *);
typedef void *button_handle_t;
/**
* @brief Button events
*
*/
typedef enum {
BUTTON_PRESS_DOWN = 0,
BUTTON_PRESS_UP,
BUTTON_PRESS_REPEAT,
BUTTON_SINGLE_CLICK,
BUTTON_DOUBLE_CLICK,
BUTTON_LONG_PRESS_START,
BUTTON_LONG_PRESS_HOLD,
BUTTON_EVENT_MAX,
BUTTON_NONE_PRESS,
} button_event_t;
/**
* @brief Supported button type
*
*/
typedef enum {
BUTTON_TYPE_GPIO,
BUTTON_TYPE_ADC,
} button_type_t;
/**
* @brief Button configuration
*
*/
typedef struct {
button_type_t type; /**< button type, The corresponding button configuration must be filled */
union {
button_gpio_config_t gpio_button_config; /**< gpio button configuration */
button_adc_config_t adc_button_config; /**< adc button configuration */
}; /**< button configuration */
} button_config_t;
/**
* @brief Create a button
*
* @param config pointer of button configuration, must corresponding the button type
*
* @return A handle to the created button, or NULL in case of error.
*/
button_handle_t iot_button_create(const button_config_t *config);
/**
* @brief Delete a button
*
* @param btn_handle A button handle to delete
*
* @return
* - ESP_OK Success
* - ESP_FAIL Failure
*/
esp_err_t iot_button_delete(button_handle_t btn_handle);
/**
* @brief Register the button event callback function.
*
* @param btn_handle A button handle to register
* @param event Button event
* @param cb Callback function.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_cb_t cb);
/**
* @brief Unregister the button event callback function.
*
* @param btn_handle A button handle to unregister
* @param event Button event
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event);
/**
* @brief Get button event
*
* @param btn_handle Button handle
*
* @return Current button event. See button_event_t
*/
button_event_t iot_button_get_event(button_handle_t btn_handle);
/**
* @brief Get button repeat times
*
* @param btn_handle Button handle
*
* @return button pressed times. For example, double-click return 2, triple-click return 3, etc.
*/
uint8_t iot_button_get_repeat(button_handle_t btn_handle);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,306 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "iot_button.h"
#include "esp_timer.h"
#include "sdkconfig.h"
static const char *TAG = "button";
#define BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
typedef struct Button {
uint16_t ticks;
uint8_t repeat;
button_event_t event;
uint8_t state: 3;
uint8_t debounce_cnt: 3;
uint8_t active_level: 1;
uint8_t button_level: 1;
uint8_t (*hal_button_Level)(void *usr_data);
void *usr_data;
button_type_t type;
button_cb_t cb[BUTTON_EVENT_MAX];
struct Button *next;
} button_dev_t;
//button handle list head.
static button_dev_t *g_head_handle = NULL;
static esp_timer_handle_t g_button_timer_handle;
static bool g_is_timer_running = false;
#define TICKS_INTERVAL CONFIG_BUTTON_PERIOD_TIME_MS
#define DEBOUNCE_TICKS CONFIG_BUTTON_DEBOUNCE_TICKS //MAX 8
#define SHORT_TICKS (CONFIG_BUTTON_SHORT_PRESS_TIME_MS /TICKS_INTERVAL)
#define LONG_TICKS (CONFIG_BUTTON_LONG_PRESS_TIME_MS /TICKS_INTERVAL)
#define CALL_EVENT_CB(ev) if(btn->cb[ev])btn->cb[ev](btn)
/**
* @brief Button driver core function, driver state machine.
*/
static void button_handler(button_dev_t *btn)
{
uint8_t read_gpio_level = btn->hal_button_Level(btn->usr_data);
/** ticks counter working.. */
if ((btn->state) > 0) {
btn->ticks++;
}
/**< button debounce handle */
if (read_gpio_level != btn->button_level) {
if (++(btn->debounce_cnt) >= DEBOUNCE_TICKS) {
btn->button_level = read_gpio_level;
btn->debounce_cnt = 0;
}
} else {
btn->debounce_cnt = 0;
}
/** State machine */
switch (btn->state) {
case 0:
if (btn->button_level == btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_DOWN;
CALL_EVENT_CB(BUTTON_PRESS_DOWN);
btn->ticks = 0;
btn->repeat = 1;
btn->state = 1;
} else {
btn->event = (uint8_t)BUTTON_NONE_PRESS;
}
break;
case 1:
if (btn->button_level != btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->ticks = 0;
btn->state = 2;
} else if (btn->ticks > LONG_TICKS) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_START;
CALL_EVENT_CB(BUTTON_LONG_PRESS_START);
btn->state = 5;
}
break;
case 2:
if (btn->button_level == btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_DOWN;
CALL_EVENT_CB(BUTTON_PRESS_DOWN);
btn->repeat++;
// CALL_EVENT_CB(BUTTON_PRESS_REPEAT); // repeat hit
btn->ticks = 0;
btn->state = 3;
} else if (btn->ticks > SHORT_TICKS) {
if (btn->repeat == 1) {
btn->event = (uint8_t)BUTTON_SINGLE_CLICK;
CALL_EVENT_CB(BUTTON_SINGLE_CLICK);
}
// else if (btn->repeat == 2) {
// btn->event = (uint8_t)BUTTON_DOUBLE_CLICK;
// CALL_EVENT_CB(BUTTON_DOUBLE_CLICK); // repeat hit
// ESP_LOGE(TAG, "003double click");
// }
else {
CALL_EVENT_CB(BUTTON_PRESS_REPEAT); // repeat hit
}
btn->state = 0;
}
break;
case 3:
if (btn->button_level != btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
if (btn->ticks < SHORT_TICKS) {
btn->ticks = 0;
btn->state = 2; //repeat press
} else {
btn->state = 0;
}
}
break;
case 5:
if (btn->button_level == btn->active_level) {
//continue hold trigger
btn->event = (uint8_t)BUTTON_LONG_PRESS_HOLD;
CALL_EVENT_CB(BUTTON_LONG_PRESS_HOLD);
} else { //releasd
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->state = 0; //reset
}
break;
}
}
static void button_cb(void *args)
{
button_dev_t *target;
for (target = g_head_handle; target; target = target->next) {
button_handler(target);
}
}
static button_dev_t *button_create_com(uint8_t active_level, uint8_t (*hal_get_key_state)(void *usr_data), void *usr_data)
{
BTN_CHECK(NULL != hal_get_key_state, "Function pointer is invalid", NULL);
button_dev_t *btn = (button_dev_t *) calloc(1, sizeof(button_dev_t));
BTN_CHECK(NULL != btn, "Button memory alloc failed", NULL);
btn->usr_data = usr_data;
btn->event = BUTTON_NONE_PRESS;
btn->active_level = active_level;
btn->hal_button_Level = hal_get_key_state;
btn->button_level = !active_level;
/** Add handle to list */
btn->next = g_head_handle;
g_head_handle = btn;
if (false == g_is_timer_running) {
esp_timer_create_args_t button_timer;
button_timer.arg = NULL;
button_timer.callback = button_cb;
button_timer.dispatch_method = ESP_TIMER_TASK;
button_timer.name = "button_timer";
esp_timer_create(&button_timer, &g_button_timer_handle);
esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U);
g_is_timer_running = true;
}
return btn;
}
static esp_err_t button_delete_com(button_dev_t *btn)
{
BTN_CHECK(NULL != btn, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t **curr;
for (curr = &g_head_handle; *curr; ) {
button_dev_t *entry = *curr;
if (entry == btn) {
*curr = entry->next;
free(entry);
} else {
curr = &entry->next;
}
}
/* count button number */
uint16_t number = 0;
button_dev_t *target = g_head_handle;
while (target) {
target = target->next;
number++;
}
ESP_LOGD(TAG, "remain btn number=%d", number);
if (0 == number && g_is_timer_running) { /**< if all button is deleted, stop the timer */
esp_timer_stop(g_button_timer_handle);
esp_timer_delete(g_button_timer_handle);
g_is_timer_running = false;
}
return ESP_OK;
}
button_handle_t iot_button_create(const button_config_t *config)
{
esp_err_t ret = ESP_OK;
button_dev_t *btn = NULL;
switch (config->type) {
case BUTTON_TYPE_GPIO: {
const button_gpio_config_t *cfg = &(config->gpio_button_config);
ret = button_gpio_init(cfg);
BTN_CHECK(ESP_OK == ret, "gpio button init failed", NULL);
btn = button_create_com(cfg->active_level, button_gpio_get_key_level, (void *)cfg->gpio_num);
} break;
case BUTTON_TYPE_ADC: {
const button_adc_config_t *cfg = &(config->adc_button_config);
ret = button_adc_init(cfg);
BTN_CHECK(ESP_OK == ret, "adc button init failed", NULL);
btn = button_create_com(1, button_adc_get_key_level, (void *)ADC_BUTTON_COMBINE(cfg->adc_channel, cfg->button_index));
} break;
default:
ESP_LOGE(TAG, "Unsupported button type");
break;
}
BTN_CHECK(NULL != btn, "button create failed", NULL);
btn->type = config->type;
return (button_handle_t)btn;
}
esp_err_t iot_button_delete(button_handle_t btn_handle)
{
esp_err_t ret = ESP_OK;
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *)btn_handle;
switch (btn->type) {
case BUTTON_TYPE_GPIO:
ret = button_gpio_deinit((int)(btn->usr_data));
break;
case BUTTON_TYPE_ADC:
ret = button_adc_deinit(ADC_BUTTON_SPLIT_CHANNEL(btn->usr_data), ADC_BUTTON_SPLIT_INDEX(btn->usr_data));
break;
default:
break;
}
BTN_CHECK(ESP_OK == ret, "button deinit failed", ESP_FAIL);
button_delete_com(btn);
return ESP_OK;
}
esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_cb_t cb)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
btn->cb[event] = cb;
return ESP_OK;
}
esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
btn->cb[event] = NULL;
return ESP_OK;
}
button_event_t iot_button_get_event(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", BUTTON_NONE_PRESS);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->event;
}
uint8_t iot_button_get_repeat(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->repeat;
}

View File

@@ -0,0 +1,3 @@
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity test_utils button)

View File

@@ -0,0 +1,234 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "stdio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_button.h"
static const char *TAG = "BUTTON TEST";
#define BUTTON_IO_NUM 0
#define BUTTON_ACTIVE_LEVEL 0
#define BUTTON_NUM 16
static button_handle_t g_btns[BUTTON_NUM] = {0};
static int get_btn_index(button_handle_t btn)
{
for (size_t i = 0; i < BUTTON_NUM; i++) {
if (btn == g_btns[i]) {
return i;
}
}
return -1;
}
static void button_press_down_cb(void *arg)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_PRESS_DOWN, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_DOWN", get_btn_index((button_handle_t)arg));
}
static void button_press_up_cb(void *arg)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_PRESS_UP, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_UP", get_btn_index((button_handle_t)arg));
}
static void button_press_repeat_cb(void *arg)
{
ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_REPEAT[%d]", get_btn_index((button_handle_t)arg), iot_button_get_repeat((button_handle_t)arg));
}
static void button_single_click_cb(void *arg)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_SINGLE_CLICK, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_SINGLE_CLICK", get_btn_index((button_handle_t)arg));
}
static void button_double_click_cb(void *arg)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_DOUBLE_CLICK, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_DOUBLE_CLICK", get_btn_index((button_handle_t)arg));
}
static void button_long_press_start_cb(void *arg)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_LONG_PRESS_START, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_START", get_btn_index((button_handle_t)arg));
}
static void button_long_press_hold_cb(void *arg)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_LONG_PRESS_HOLD, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_HOLD", get_btn_index((button_handle_t)arg));
}
static void print_button_event(button_handle_t btn)
{
button_event_t evt = iot_button_get_event(btn);
switch (evt) {
case BUTTON_PRESS_DOWN:
ESP_LOGI(TAG, "BUTTON_PRESS_DOWN");
break;
case BUTTON_PRESS_UP:
ESP_LOGI(TAG, "BUTTON_PRESS_UP");
break;
case BUTTON_PRESS_REPEAT:
ESP_LOGI(TAG, "BUTTON_PRESS_REPEAT");
break;
case BUTTON_SINGLE_CLICK:
ESP_LOGI(TAG, "BUTTON_SINGLE_CLICK");
break;
case BUTTON_DOUBLE_CLICK:
ESP_LOGI(TAG, "BUTTON_DOUBLE_CLICK");
break;
case BUTTON_LONG_PRESS_START:
ESP_LOGI(TAG, "BUTTON_LONG_PRESS_START");
break;
case BUTTON_LONG_PRESS_HOLD:
ESP_LOGI(TAG, "BUTTON_LONG_PRESS_HOLD");
break;
default:
break;
}
}
TEST_CASE("gpio button test", "[button][iot]")
{
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.gpio_button_config = {
.gpio_num = 0,
.active_level = 0,
},
};
g_btns[0] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[0]);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_DOWN, button_press_down_cb);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_UP, button_press_up_cb);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT, button_press_repeat_cb);
iot_button_register_cb(g_btns[0], BUTTON_SINGLE_CLICK, button_single_click_cb);
iot_button_register_cb(g_btns[0], BUTTON_DOUBLE_CLICK, button_double_click_cb);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_START, button_long_press_start_cb);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb);
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_button_delete(g_btns[0]);
}
TEST_CASE("adc button test", "[button][iot]")
{
/** ESP32-LyraT-Mini board */
const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410};
button_config_t cfg = {0};
cfg.type = BUTTON_TYPE_ADC;
for (size_t i = 0; i < 6; i++) {
cfg.adc_button_config.adc_channel = 3,
cfg.adc_button_config.button_index = i;
if (i == 0) {
cfg.adc_button_config.min = (0 + vol[i]) / 2;
} else {
cfg.adc_button_config.min = (vol[i - 1] + vol[i]) / 2;
}
if (i == 5) {
cfg.adc_button_config.max = (vol[i] + 3000) / 2;
} else {
cfg.adc_button_config.max = (vol[i] + vol[i + 1]) / 2;
}
g_btns[i] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[i]);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_DOWN, button_press_down_cb);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_UP, button_press_up_cb);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT, button_press_repeat_cb);
iot_button_register_cb(g_btns[i], BUTTON_SINGLE_CLICK, button_single_click_cb);
iot_button_register_cb(g_btns[i], BUTTON_DOUBLE_CLICK, button_double_click_cb);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_START, button_long_press_start_cb);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb);
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (size_t i = 0; i < 6; i++) {
iot_button_delete(g_btns[i]);
}
}
TEST_CASE("adc gpio button test", "[button][iot]")
{
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.gpio_button_config = {
.gpio_num = 0,
.active_level = 0,
},
};
g_btns[8] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[8]);
iot_button_register_cb(g_btns[8], BUTTON_PRESS_DOWN, button_press_down_cb);
iot_button_register_cb(g_btns[8], BUTTON_PRESS_UP, button_press_up_cb);
iot_button_register_cb(g_btns[8], BUTTON_PRESS_REPEAT, button_press_repeat_cb);
iot_button_register_cb(g_btns[8], BUTTON_SINGLE_CLICK, button_single_click_cb);
iot_button_register_cb(g_btns[8], BUTTON_DOUBLE_CLICK, button_double_click_cb);
iot_button_register_cb(g_btns[8], BUTTON_LONG_PRESS_START, button_long_press_start_cb);
iot_button_register_cb(g_btns[8], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb);
/** ESP32-LyraT-Mini board */
const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410};
// button_config_t cfg = {0};
cfg.type = BUTTON_TYPE_ADC;
for (size_t i = 0; i < 6; i++) {
cfg.adc_button_config.adc_channel = 3,
cfg.adc_button_config.button_index = i;
if (i == 0) {
cfg.adc_button_config.min = (0 + vol[i]) / 2;
} else {
cfg.adc_button_config.min = (vol[i - 1] + vol[i]) / 2;
}
if (i == 5) {
cfg.adc_button_config.max = (vol[i] + 3000) / 2;
} else {
cfg.adc_button_config.max = (vol[i] + vol[i + 1]) / 2;
}
g_btns[i] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[i]);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_DOWN, button_press_down_cb);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_UP, button_press_up_cb);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT, button_press_repeat_cb);
iot_button_register_cb(g_btns[i], BUTTON_SINGLE_CLICK, button_single_click_cb);
iot_button_register_cb(g_btns[i], BUTTON_DOUBLE_CLICK, button_double_click_cb);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_START, button_long_press_start_cb);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb);
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (size_t i = 0; i < 6; i++) {
iot_button_delete(g_btns[i]);
}
}

View File

@@ -0,0 +1,5 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@@ -0,0 +1,3 @@
set(COMPONENT_SRCS "cJSON_Utils.c" "cJSON.c")
set(COMPONENT_ADD_INCLUDEDIRS ". include")
register_component()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,285 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 14
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable adress area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,85 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON_Utils__h
#define cJSON_Utils__h
#ifdef __cplusplus
extern "C"
{
#endif
#include "cJSON.h"
/* Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec. */
CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON * const object, const char *pointer);
CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointerCaseSensitive(cJSON * const object, const char *pointer);
/* Implement RFC6902 (https://tools.ietf.org/html/rfc6902) JSON Patch spec. */
/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */
CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON * const from, cJSON * const to);
CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatchesCaseSensitive(cJSON * const from, cJSON * const to);
/* Utility for generating patch array entries. */
CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON * const array, const char * const operation, const char * const path, const cJSON * const value);
/* Returns 0 for success. */
CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON * const object, const cJSON * const patches);
CJSON_PUBLIC(int) cJSONUtils_ApplyPatchesCaseSensitive(cJSON * const object, const cJSON * const patches);
/*
// Note that ApplyPatches is NOT atomic on failure. To implement an atomic ApplyPatches, use:
//int cJSONUtils_AtomicApplyPatches(cJSON **object, cJSON *patches)
//{
// cJSON *modme = cJSON_Duplicate(*object, 1);
// int error = cJSONUtils_ApplyPatches(modme, patches);
// if (!error)
// {
// cJSON_Delete(*object);
// *object = modme;
// }
// else
// {
// cJSON_Delete(modme);
// }
//
// return error;
//}
// Code not added to library since this strategy is a LOT slower.
*/
/* Implement RFC7386 (https://tools.ietf.org/html/rfc7396) JSON Merge Patch spec. */
/* target will be modified by patch. return value is new ptr for target. */
CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, const cJSON * const patch);
CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJSON * const patch);
/* generates a patch to move from -> to */
/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */
CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to);
CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to);
/* Given a root object and a target object, construct a pointer from one to the other. */
CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(const cJSON * const object, const cJSON * const target);
/* Sorts the members of the object into alphabetical order. */
CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON * const object);
CJSON_PUBLIC(void) cJSONUtils_SortObjectCaseSensitive(cJSON * const object);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,3 @@
set(COMPONENT_SRCS "example.c")
set(COMPONENT_ADD_INCLUDEDIRS ". include")
register_component()

View File

@@ -0,0 +1,5 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,7 @@
#include "example.h"
#include <stdio.h>
void example()
{
printf("example\n");
}

View File

@@ -0,0 +1,9 @@
#ifndef _IOT_EXAMPLE2_H_
#define _IOT_EXAMPLE2_H_
void example();
#endif

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "sht3x.c"
INCLUDE_DIRS include
REQUIRES bus)

View File

@@ -0,0 +1,17 @@
# SHT3x
- This component will show you how to use I2C module read external i2c sensor data, here we use SHT3x-series temperature and humidity sensor(SHT30 is used this component).
- Pin assignment:
- GPIO21 is assigned as the data signal of i2c master port
- GPIO22 is assigned as the clock signal of i2c master port
- Connection:
* connect sda of sensor with GPIO21
* connect scl of sensor with GPIO22
- SHT3x measurement mode:
* single shot data acquisition mode: in this mode one issued measurement command triggers the acquisition of one data pair.
* periodic data acquisition mode: in this mode one issued measurement command yields a stream of data pairs. when use periodic data acquisition mode, you should send hex code 0xE000 firstly, and send 0x3093 to stop periodic mode.
# Notice
- SHT3x uses 8-bit CRC checksum, you can see `CheckCrc8() `for detail.
- The raw measurement data needs to be convert to physical scale. formulas are shown in `sht3x_get_humiture()`

View File

@@ -0,0 +1,8 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := .

View File

@@ -0,0 +1,233 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _SHT3x_H_
#define _SHT3x_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include "driver/i2c.h"
#include "i2c_bus.h"
#include "esp_log.h"
#include "math.h"
typedef enum {
SOFT_RESET_CMD = 0x30A2, /*!< Command to soft reset*/
READOUT_FOR_PERIODIC_MODE = 0xE000, /*!< Command to read Periodic*/
READ_SERIAL_NUMBER = 0x3780, /*!< Command to read senser number*/
SHT3x_STOP_PERIODIC = 0x3093, /*!< Command to break or stop periodic mode*/
SHT3x_ART_CMD = 0x2B32, /*!< Command to accelerated response time*/
/* Single Shot Data Acquisition Mode*/
SHT3x_SINGLE_HIGH_ENABLED = 0x2C06, /*!< Command to set measure mode as Single Shot Data Acquisition mode in high repeatability and Clock Stretching enabled*/
SHT3x_SINGLE_MEDIUM_ENABLED = 0x2C0D, /*!< Command to set measure mode as Single Shot Data Acquisition mode in medium repeatability and Clock Stretching enabled*/
SHT3x_SINGLE_LOW_ENABLED = 0x2C10, /*!< Command to set measure mode as Single Shot Data Acquisition mode in low repeatability and Clock Stretching enabled*/
SHT3x_SINGLE_HIGH_DISABLED = 0x2400, /*!< Command to set measure mode as Single Shot Data Acquisition mode in high repeatability and Clock Stretching disabled*/
SHT3x_SINGLE_MEDIUM_DISABLED = 0x240B, /*!< Command to set measure mode as Single Shot Data Acquisition mode in medium repeatability and Clock Stretching disabled*/
SHT3x_SINGLE_LOW_DISABLED = 0x2416, /*!< Command to set measure mode as Single Shot Data Acquisition mode in low repeatability and Clock Stretching disabled*/
/* Periodic Data Acquisition mode*/
SHT3x_PER_0_5_HIGH = 0x2032, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 0.5 mps*/
SHT3x_PER_0_5_MEDIUM = 0x2024, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 0.5 mps*/
SHT3x_PER_0_5_LOW = 0x202F, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 0.5 mps*/
SHT3x_PER_1_HIGH = 0x2130, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 1 mps*/
SHT3x_PER_1_MEDIUM = 0x2126, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 1 mps*/
SHT3x_PER_1_LOW = 0x212D, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 1 mps*/
SHT3x_PER_2_HIGH = 0x2236, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 2 mps*/
SHT3x_PER_2_MEDIUM = 0x2220, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 2 mps*/
SHT3x_PER_2_LOW = 0x222B, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 2 mps*/
SHT3x_PER_4_HIGH = 0x2334, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 4 mps*/
SHT3x_PER_4_MEDIUM = 0x2322, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 4 mps*/
SHT3x_PER_4_LOW = 0x2329, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 4 mps*/
SHT3x_PER_10_HIGH = 0x2737, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 10 mps*/
SHT3x_PER_10_MEDIUM = 0x2721, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 10 mps*/
SHT3x_PER_10_LOW = 0x272A, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 10 mps*/
/* cmd for sht3x heater condition*/
SHT3x_HEATER_ENABLE = 0x306D, /*!< Command to enable the heater*/
SHT3x_HEATER_DISABLED = 0x3066, /*!< Command to disable the heater*/
} sht3x_cmd_measure_t;
typedef enum {
SHT3x_ADDR_PIN_SELECT_VSS = 0x44, /*!< set address PIN select VSS */
SHT3x_ADDR_PIN_SELECT_VDD = 0x45, /*!< set address PIN select VDD */
} sht3x_set_address_t;
typedef void *sht3x_handle_t;
/**
* @brief Create sht3x handle_t
*
* @param bus sensorice object handle of sht3x
* @param dev_addr sensorice address
*
* @return
* - sht3x handle_t
*/
sht3x_handle_t sht3x_create(i2c_bus_handle_t bus, uint8_t dev_addr);
/**
* @brief Delete sht3x handle_t
*
* @param sensor point to sensorice object handle of sht3x
*
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t sht3x_delete(sht3x_handle_t *sensor);
/**
* @brief Get temperature and humidity
*
* @param sensor object handle of shd3x
* @param Tem_val temperature data buffer
* @param Hum_val humidity data buffer
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t sht3x_get_humiture(sht3x_handle_t sensor, float *Tem_val, float *Hum_val);
/**
* @brief Get temperature and humidity just once
*
* @param sensor object handle of shd3x
* @param Tem_val temperature data
* @param Hum_val humidity data
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t sht3x_get_single_shot(sht3x_handle_t sensor, float *Tem_val, float *Hum_val);
/**
* @brief Soft reset for sht3x
*
* @param sensor object handle of sht3x
*
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t sht3x_soft_reset(sht3x_handle_t sensor);
/**
* @brief stop or break or stop periodic mode
*
* @param sensor object handle of sht3x
*
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t sht3x_stop_periodic(sht3x_handle_t sensor);
/**
* @brief accelerated response time
*
* @param sensor object handle of sht3x
*
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t sht3x_art(sht3x_handle_t sensor);
/**
* @brief set measure mode of sht3x
*
* @param sensor object handle of shd3x
* @param sht3x_cmd_measure_t the instruction to set measurement mode
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t sht3x_set_measure_mode(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_measure_mode);
/**
* @brief change the condition of sht3x heater
*
* @param sensor object handle of shd3x
* @param sht3x_cmd_measure_t the instruction to turn on/off heater of sht3x
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
* @note
* the default condition of heater is disabled
*/
esp_err_t sht3x_heater(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_heater_condition);
/***implements of humiture hal interface****/
#ifdef CONFIG_SENSOR_HUMITURE_INCLUDED_SHT3X
/**
* @brief initialize sht3x with default configurations
*
* @param i2c_bus i2c bus handle the sensor will attached to
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t humiture_sht3x_init(i2c_bus_handle_t handle);
/**
* @brief de-initialize sht3x
*
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t humiture_sht3x_deinit(void);
/**
* @brief test if sht3x is active
*
* @return
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t humiture_sht3x_test(void);
/**
* @brief acquire relative humidity result one time.
*
* @param h point to result data (unit:percentage)
* @return esp_err_t
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t humiture_sht3x_acquire_humidity(float *h);
/**
* @brief acquire temperature result one time.
*
* @param t point to result data (unit:dCelsius)
* @return esp_err_t
* - ESP_OK Success
* - ESP_FAIL Fail
*/
esp_err_t humiture_sht3x_acquire_temperature(float *t);
#endif
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,318 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include "driver/i2c.h"
#include "i2c_bus.h"
#include "esp_log.h"
#include "esp_system.h"
#include "sht3x.h"
typedef struct {
i2c_bus_device_handle_t i2c_dev;
uint8_t dev_addr;
} sht3x_sensor_t;
sht3x_handle_t sht3x_create(i2c_bus_handle_t bus, uint8_t dev_addr)
{
sht3x_sensor_t *sens = (sht3x_sensor_t *) calloc(1, sizeof(sht3x_sensor_t));
sens->i2c_dev = i2c_bus_device_create(bus, dev_addr, i2c_bus_get_current_clk_speed(bus));
if (sens->i2c_dev == NULL) {
free(sens);
return NULL;
}
sens->dev_addr = dev_addr;
return (sht3x_handle_t) sens;
}
esp_err_t sht3x_delete(sht3x_handle_t *sensor)
{
if (*sensor == NULL) {
return ESP_OK;
}
sht3x_sensor_t *sens = (sht3x_sensor_t *)(*sensor);
i2c_bus_device_delete(&sens->i2c_dev);
free(sens);
*sensor = NULL;
return ESP_OK;
}
static esp_err_t sht3x_write_cmd(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_cmd)
{
sht3x_sensor_t *sens = (sht3x_sensor_t *) sensor;
uint8_t cmd_buffer[2];
cmd_buffer[0] = sht3x_cmd >> 8;
cmd_buffer[1] = sht3x_cmd;
esp_err_t ret = i2c_bus_write_bytes(sens->i2c_dev, NULL_I2C_MEM_ADDR, 2, cmd_buffer);
return ret;
}
static esp_err_t sht3x_get_data(sht3x_handle_t sensor, uint8_t data_len, uint8_t *data_arr)
{
sht3x_sensor_t *sens = (sht3x_sensor_t *) sensor;
esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, NULL_I2C_MEM_ADDR, data_len, data_arr);
return ret;
}
static uint8_t CheckCrc8(uint8_t *const message, uint8_t initial_value)
{
uint8_t crc;
int i = 0, j = 0;
crc = initial_value;
for (j = 0; j < 2; j++) {
crc ^= message[j];
for (i = 0; i < 8; i++) {
if (crc & 0x80) {
crc = (crc << 1) ^ 0x31; /*!< 0x31 is Polynomial for 8-bit CRC checksum*/
} else {
crc = (crc << 1);
}
}
}
return crc;
}
esp_err_t sht3x_measure_period(bool set, uint16_t *min_delay)
{
static uint16_t s_min_delay = 250;
//get value
if (!set) {
*min_delay = s_min_delay;
return ESP_OK;
}
//set value
uint8_t delay_mode = *min_delay >> 8;
switch (delay_mode) {
case 0x20:
s_min_delay = 2000;
break;
case 0x21:
s_min_delay = 1000;
break;
case 0x22:
s_min_delay = 500;
break;
case 0x24:
s_min_delay = 250;
break;
case 0x27:
s_min_delay = 100;
break;
default:
s_min_delay = 250;
break;
}
return ESP_OK;
}
esp_err_t sht3x_get_humiture(sht3x_handle_t sensor, float *Tem_val, float *Hum_val)
{
uint8_t buff[6];
uint16_t tem, hum;
float Temperature = 0;
float Humidity = 0;
sht3x_write_cmd(sensor, READOUT_FOR_PERIODIC_MODE); /*!< if you want to read data just onetime, Comment this code*/
sht3x_get_data(sensor, 6, buff);
/* check crc */
if (CheckCrc8(buff, 0xFF) != buff[2] || CheckCrc8(&buff[3], 0xFF) != buff[5]) {
return ESP_FAIL;
}
tem = (((uint16_t)buff[0] << 8) | buff[1]);
Temperature = (175.0 * (float)tem / 65535.0 - 45.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Celsius °C */
//Temperature= (315.0*(float)tem/65535.0-49.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Fahrenheit °F */
hum = (((uint16_t)buff[3] << 8) | buff[4]);
Humidity = (100.0 * (float)hum / 65535.0); /*!< RH = hum*100 / (2^16-1) */
if ((Temperature >= -20) && (Temperature <= 125) && (Humidity >= 0) && (Humidity <= 100)) {
*Tem_val = Temperature;
*Hum_val = Humidity;
return ESP_OK; /*!< here is mesurement range */
} else {
return ESP_FAIL;
}
}
esp_err_t sht3x_get_single_shot(sht3x_handle_t sensor, float *Tem_val, float *Hum_val)
{
uint8_t buff[6];
uint16_t tem, hum;
static float Temperature = 0;
static float Humidity = 0;
static int64_t last_shot_time = 0;
int64_t current_time = esp_timer_get_time();
uint16_t min_delay = 0;
int64_t min_delay_us = 0;
sht3x_measure_period(false, &min_delay);
min_delay_us = min_delay * 1000;
if ((current_time - last_shot_time) < min_delay_us) {
*Tem_val = Temperature;
*Hum_val = Humidity;
return ESP_OK;
}
esp_err_t ret = sht3x_get_data(sensor, 6, buff);
/* check crc */
if (ret != ESP_OK || CheckCrc8(buff, 0xFF) != buff[2] || CheckCrc8(&buff[3], 0xFF) != buff[5]) {
return ESP_FAIL;
}
last_shot_time = current_time;
tem = (((uint16_t)buff[0] << 8) | buff[1]);
Temperature = (175.0 * (float)tem / 65535.0 - 45.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Celsius °C */
//Temperature= (315.0*(float)tem/65535.0-49.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Fahrenheit °F */
hum = (((uint16_t)buff[3] << 8) | buff[4]);
Humidity = (100.0 * (float)hum / 65535.0); /*!< RH = hum*100 / (2^16-1) */
if ((Temperature >= -20) && (Temperature <= 125) && (Humidity >= 0) && (Humidity <= 100)) {
*Tem_val = Temperature;
*Hum_val = Humidity;
return ESP_OK;
} else {
return ESP_FAIL;
}
}
esp_err_t sht3x_soft_reset(sht3x_handle_t sensor)
{
esp_err_t ret = sht3x_write_cmd(sensor, SOFT_RESET_CMD);
return ret;
}
esp_err_t sht3x_stop_periodic(sht3x_handle_t sensor)
{
esp_err_t ret = sht3x_write_cmd(sensor, SHT3x_STOP_PERIODIC);
return ret;
}
esp_err_t sht3x_art(sht3x_handle_t sensor)
{
esp_err_t ret = sht3x_write_cmd(sensor, SHT3x_ART_CMD);
return ret;
}
esp_err_t sht3x_set_measure_mode(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_measure_mode)
{
esp_err_t ret = sht3x_write_cmd(sensor, sht3x_measure_mode);
sht3x_measure_period(true, (uint16_t *)&sht3x_measure_mode);
return ret;
}
esp_err_t sht3x_heater(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_heater_condition)
{
esp_err_t ret = sht3x_write_cmd(sensor, sht3x_heater_condition);
return ret;
}
#ifdef CONFIG_SENSOR_HUMITURE_INCLUDED_SHT3X
static sht3x_handle_t sht3x = NULL;
static bool is_init = false;
esp_err_t humiture_sht3x_init(i2c_bus_handle_t i2c_bus)
{
if (is_init || !i2c_bus) {
return ESP_FAIL;
}
sht3x = sht3x_create(i2c_bus, SHT3x_ADDR_PIN_SELECT_VSS);
if (!sht3x) {
return ESP_FAIL;
}
esp_err_t ret = sht3x_set_measure_mode(sht3x, SHT3x_PER_4_MEDIUM); /**medium accuracy/repeatability with 250ms period (1000ms/4)**/
if (ret != ESP_OK) {
return ESP_FAIL;
}
is_init = true;
return ESP_OK;
}
esp_err_t humiture_sht3x_deinit(void)
{
if (!is_init) {
return ESP_FAIL;
}
esp_err_t ret = sht3x_delete(&sht3x);
if (ret != ESP_OK) {
return ESP_FAIL;
}
is_init = false;
return ESP_OK;
}
esp_err_t humiture_sht3x_test(void)
{
if (!is_init) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t humiture_sht3x_acquire_humidity(float *h)
{
if (!is_init) {
return ESP_FAIL;
}
float temperature = 0;
float humidity = 0;
esp_err_t ret = sht3x_get_single_shot(sht3x, &temperature, &humidity);
if (ret == ESP_OK) {
*h = humidity;
return ESP_OK;
}
*h = 0;
return ESP_FAIL;
}
esp_err_t humiture_sht3x_acquire_temperature(float *t)
{
if (!is_init) {
return ESP_FAIL;
}
float temperature = 0;
float humidity = 0;
esp_err_t ret = sht3x_get_single_shot(sht3x, &temperature, &humidity);
if (ret == ESP_OK) {
*t = temperature;
return ESP_OK;
}
*t = 0;
return ESP_FAIL;
}
#endif

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "sht3x_test.c"
INCLUDE_DIRS .
REQUIRES unity sht3x bus)

View File

@@ -0,0 +1,5 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@@ -0,0 +1,73 @@
// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include "unity.h"
#include "driver/i2c.h"
#include "i2c_bus.h"
#include "esp_system.h"
#include "sht3x.h"
#define I2C_MASTER_SCL_IO (gpio_num_t)22 /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO (gpio_num_t)21 /*!< gpio number for I2C master data */
#define I2C_MASTER_NUM I2C_NUM_1 /*!< I2C port number for master dev */
#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */
static i2c_bus_handle_t i2c_bus = NULL;
static sht3x_handle_t sht3x = NULL;
/**
* @brief i2c master initialization
*/
static void sht3x_init_test()
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_bus = i2c_bus_create(I2C_MASTER_NUM, &conf);
sht3x = sht3x_create(i2c_bus, SHT3x_ADDR_PIN_SELECT_VSS);
sht3x_set_measure_mode(sht3x, SHT3x_PER_2_MEDIUM); /*!< here read data in periodic mode*/
}
static void sht3x_deinit_test()
{
sht3x_delete(&sht3x);
i2c_bus_delete(&i2c_bus);
}
void sht3x_get_data_test()
{
float Tem_val, Hum_val;
int cnt = 10;
while (cnt--) {
if (sht3x_get_humiture(sht3x, &Tem_val, &Hum_val) == 0) {
printf("temperature %.2f°C ", Tem_val);
printf("humidity:%.2f %%\n", Hum_val);
}
vTaskDelay(1000 / portTICK_RATE_MS);
}
}
TEST_CASE("Sensor sht3x test", "[sht3x][iot][sensor]")
{
sht3x_init_test();
vTaskDelay(1000 / portTICK_RATE_MS);
sht3x_get_data_test();
sht3x_deinit_test();
}

View File

@@ -0,0 +1,45 @@
# Examples
This directory contains a range of example ESP-IDF projects. These are intended to demonstrate parts of ESP-IDF functionality, and to provide code that you can copy and adapt into your own projects.
# Example Layout
The examples are grouped into subdirectories by category. Each category directory contains one or more example projects:
* `bluetooth/bluedroid` contains Classic BT, BLE and coex examples using default Bluedroid host stack.
* `bluetooth/nimble` contains BLE examples using NimBLE host stack.
* `bluetooth/esp_ble_mesh` contains ESP BLE Mesh examples.
* `bluetooth/hci` contains HCI transport (VHCI and HCI UART) examples
* `ethernet` contains Ethernet examples.
* `get-started` contains some very simple examples with minimal functionality.
* `mesh` contains Wi-Fi Mesh examples.
* `peripherals` contains examples showing driver functionality for the various onboard ESP32 peripherals.
* `protocols` contains examples showing network protocol interactions.
* `storage` contains examples showing data storage methods using SPI flash or external storage like the SD/MMC interface.
* `system` contains examples which demonstrate some internal chip features, or debugging & development tools.
* `wifi` contains examples of advanced Wi-Fi features. (For network protocol examples, see `protocols` instead.)
* `build_system` contains examples of build system features
# Using Examples
Building an example is the same as building any other project:
* Follow the Getting Started instructions which include building the "Hello World" example.
* Change into the directory of the new example you'd like to build.
* Run `idf.py menuconfig` to open the project configuration menu. Most examples have a project-specific "Example Configuration" section here (for example, to set the WiFi SSID & password to use).
* `idf.py build` to build the example.
* Follow the printed instructions to flash, or run `idf.py -p PORT flash`.
# Copying Examples
Each example is a standalone project. The examples *do not have to be inside the esp-idf directory*. You can copy an example directory to anywhere on your computer in order to make a copy that you can modify and work with.
The `IDF_PATH` environment variable is the only thing that connects the example to the rest of ESP-IDF.
If you're looking for a more bare-bones project to start from, try [esp-idf-template](https://github.com/espressif/esp-idf-template).
# Contributing Examples
If you have a new example you think we'd like, please consider sending it to us as a Pull Request.
In the ESP-IDF documentation, you can find a "Creating Examples" page which lays out the steps to creating a top quality example.

View File

@@ -0,0 +1,12 @@
# Bluetooth Examples for Bluedroid host
Note: To use examples in this directory, you need to have Bluetooth enabled in configuration and Bluedroid selected as the host stack.
# Example Layout
The examples are grouped into subdirectories by category. Each category directory contains one or more example projects:
* `classic_bt` contains Classic BT examples
* `ble` contains BLE examples
* `coex` contains Classic BT and BLE coex examples
See the [README.md](../../README.md) file in the upper level [examples](../../) directory for more information about examples.

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ble_ancs)

View File

@@ -0,0 +1,10 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := ble_ancs
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,21 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP-IDF BLE ANCS Example
==========================
The purpose of the Apple Notification Center Service (ANCS) is to give Bluetooth accessories (that connect to iOS devices through a Bluetooth low-energy link) a simple and convenient way to access many kinds of notifications that are generated on iOS devices.
The Apple Notification Center Service is a primary service whose service UUID is 7905F431-B5CE-4E99-A40F-4B1E122D00D0.
Only one instance of the ANCS may be present on an NP. Due to the nature of iOS, the ANCS is not guaranteed to always be present. As a result, the NC should look for and subscribe to the Service Changed characteristic of the GATT service in order to monitor for the potential publishing and unpublishing of the ANCS at any time.
In its basic form, the ANCS exposes three characteristics:
Notification Source: UUID 9FBF120D-6301-42D9-8C58-25E699A21DBD (notifiable)
Control Point: UUID 69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9 (writeable with response)
Data Source: UUID 22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB (notifiable)
All these characteristics require authorization for access.

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "ble_ancs_demo.c" "ble_ancs.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,228 @@
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <string.h>
#include "esp_log.h"
#include "ble_ancs.h"
#define BLE_ANCS_TAG "BLE_ANCS"
/*
| EventID(1 Byte) | EventFlags(1 Byte) | CategoryID(1 Byte) | CategoryCount(1 Byte) | NotificationUID(4 Bytes) |
A GATT notification delivered through the Notification Source characteristic contains the following information:
* EventID: This field informs the accessory whether the given iOS notification was added, modified, or removed. The enumerated values for this field are defined
in EventID Values.
* EventFlags: A bitmask whose set bits inform an NC of specificities with the iOS notification. For example, if an iOS notification is considered “important”,
the NC may want to display a more aggressive user interface (UI) to make sure the user is properly alerted. The enumerated bits for this field
are defined in EventFlags.
* CategoryID: A numerical value providing a category in which the iOS notification can be classified. The NP will make a best effort to provide an accurate category
for each iOS notification. The enumerated values for this field are defined in CategoryID Values.
* CategoryCount: The current number of active iOS notifications in the given category. For example, if two unread emails are sitting in a users email inbox, and a new
email is pushed to the users iOS device, the value of CategoryCount is 3.
* NotificationUID: A 32-bit numerical value that is the unique identifier (UID) for the iOS notification. This value can be used as a handle in commands sent to the
Control Point characteristic to interact with the iOS notification.
*/
char *EventID_to_String(uint8_t EventID)
{
char *str = NULL;
switch (EventID)
{
case EventIDNotificationAdded:
str = "New message";
break;
case EventIDNotificationModified:
str = "Modified message";
break;
case EventIDNotificationRemoved:
str = "Removed message";
break;
default:
str = "unknown EventID";
break;
}
return str;
}
char *CategoryID_to_String(uint8_t CategoryID)
{
char *Cidstr = NULL;
switch(CategoryID) {
case CategoryIDOther:
Cidstr = "Other";
break;
case CategoryIDIncomingCall:
Cidstr = "IncomingCall";
break;
case CategoryIDMissedCall:
Cidstr = "MissedCall";
break;
case CategoryIDVoicemail:
Cidstr = "Voicemail";
break;
case CategoryIDSocial:
Cidstr = "Social";
break;
case CategoryIDSchedule:
Cidstr = "Schedule";
break;
case CategoryIDEmail:
Cidstr = "Email";
break;
case CategoryIDNews:
Cidstr = "News";
break;
case CategoryIDHealthAndFitness:
Cidstr = "HealthAndFitness";
break;
case CategoryIDBusinessAndFinance:
Cidstr = "BusinessAndFinance";
break;
case CategoryIDLocation:
Cidstr = "Location";
break;
case CategoryIDEntertainment:
Cidstr = "Entertainment";
break;
default:
Cidstr = "Unknown CategoryID";
break;
}
return Cidstr;
}
/*
| EventID(1 Byte) | EventFlags(1 Byte) | CategoryID(1 Byte) | CategoryCount(1 Byte) | NotificationUID(4 Bytes) |
*/
void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len)
{
if (!message || message_len < 5) {
return;
}
uint8_t EventID = message[0];
char *EventIDS = EventID_to_String(EventID);
uint8_t EventFlags = message[1];
uint8_t CategoryID = message[2];
char *Cidstr = CategoryID_to_String(CategoryID);
uint8_t CategoryCount = message[3];
uint32_t NotificationUID = (message[4]) | (message[5]<< 8) | (message[6]<< 16) | (message[7] << 24);
ESP_LOGI(BLE_ANCS_TAG, "EventID:%s EventFlags:0x%x CategoryID:%s CategoryCount:%d NotificationUID:%d", EventIDS, EventFlags, Cidstr, CategoryCount, NotificationUID);
}
void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len)
{
//esp_log_buffer_hex("data source", message, message_len);
if (!message || message_len == 0) {
return;
}
uint8_t Command_id = message[0];
switch (Command_id)
{
case CommandIDGetNotificationAttributes: {
uint32_t NotificationUID = (message[1]) | (message[2]<< 8) | (message[3]<< 16) | (message[4] << 24);
uint32_t remian_attr_len = message_len - 5;
uint8_t *attrs = &message[5];
ESP_LOGI(BLE_ANCS_TAG, "recevice Notification Attributes response Command_id %d NotificationUID %d", Command_id, NotificationUID);
while(remian_attr_len > 0) {
uint8_t AttributeID = attrs[0];
uint16_t len = attrs[1] | (attrs[2] << 8);
if(len > (remian_attr_len -3)) {
ESP_LOGE(BLE_ANCS_TAG, "data error");
break;
}
switch (AttributeID)
{
case NotificationAttributeIDAppIdentifier:
esp_log_buffer_char("Identifier", &attrs[3], len);
break;
case NotificationAttributeIDTitle:
esp_log_buffer_char("Title", &attrs[3], len);
break;
case NotificationAttributeIDSubtitle:
esp_log_buffer_char("Subtitle", &attrs[3], len);
break;
case NotificationAttributeIDMessage:
esp_log_buffer_char("Message", &attrs[3], len);
break;
case NotificationAttributeIDMessageSize:
esp_log_buffer_char("MessageSize", &attrs[3], len);
break;
case NotificationAttributeIDDate:
//yyyyMMdd'T'HHmmSS
esp_log_buffer_char("Date", &attrs[3], len);
break;
case NotificationAttributeIDPositiveActionLabel:
esp_log_buffer_hex("PActionLabel", &attrs[3], len);
break;
case NotificationAttributeIDNegativeActionLabel:
esp_log_buffer_hex("NActionLabel", &attrs[3], len);
break;
default:
esp_log_buffer_hex("unknownAttributeID", &attrs[3], len);
break;
}
attrs += (1 + 2 + len);
remian_attr_len -= (1 + 2 + len);
}
break;
}
case CommandIDGetAppAttributes:
ESP_LOGI(BLE_ANCS_TAG, "recevice APP Attributes response");
break;
case CommandIDPerformNotificationAction:
ESP_LOGI(BLE_ANCS_TAG, "recevice Perform Notification Action");
break;
default:
ESP_LOGI(BLE_ANCS_TAG, "unknown Command ID");
break;
}
}
char *Errcode_to_String(uint16_t status)
{
char *Errstr = NULL;
switch (status) {
case Unknown_command:
Errstr = "Unknown_command";
break;
case Invalid_command:
Errstr = "Invalid_command";
break;
case Invalid_parameter:
Errstr = "Invalid_parameter";
break;
case Action_failed:
Errstr = "Action_failed";
break;
default:
Errstr = "unknown_failed";
break;
}
return Errstr;
}

View File

@@ -0,0 +1,124 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//EventID values
typedef enum {
EventIDNotificationAdded = 0,
EventIDNotificationModified = 1,
EventIDNotificationRemoved = 2,
//Reserved EventID values = 3255
} esp_EventID;
//EventFlags
typedef enum {
EventFlagSilent = (1 << 0),
EventFlagImportant = (1 << 1),
EventFlagPreExisting = (1 << 2),
EventFlagPositiveAction = (1 << 3),
EventFlagNegativeAction = (1 << 4),
//Reserved EventFlags = (1 << 5)(1 << 7
}esp_EventFlags;
// CategoryID values
typedef enum {
CategoryIDOther = 0,
CategoryIDIncomingCall = 1,
CategoryIDMissedCall = 2,
CategoryIDVoicemail = 3,
CategoryIDSocial = 4,
CategoryIDSchedule = 5,
CategoryIDEmail = 6,
CategoryIDNews = 7,
CategoryIDHealthAndFitness = 8,
CategoryIDBusinessAndFinance = 9,
CategoryIDLocation = 10,
CategoryIDEntertainment = 11,
//Reserved CategoryID values = 12255
} esp_CategoryID;
//CommandID values
typedef enum {
CommandIDGetNotificationAttributes = 0,
CommandIDGetAppAttributes = 1,
CommandIDPerformNotificationAction = 2,
//Reserved CommandID values = 3255
} esp_CommandID;
//NotificationAttributeID
typedef enum {
NotificationAttributeIDAppIdentifier = 0,
NotificationAttributeIDTitle = 1, //(Needs to be followed by a 2-bytes max length parameter)
NotificationAttributeIDSubtitle = 2, //(Needs to be followed by a 2-bytes max length parameter)
NotificationAttributeIDMessage = 3, //(Needs to be followed by a 2-bytes max length parameter)
NotificationAttributeIDMessageSize = 4,
NotificationAttributeIDDate = 5,
NotificationAttributeIDPositiveActionLabel = 6,
NotificationAttributeIDNegativeActionLabel = 7,
//Reserved NotificationAttributeID values = 8255
} esp_NotificationAttributeID;
/*
Note: The format of the NotificationAttributeIDMessageSize constant is a string that represents the integral value
of the message size. The format of the NotificationAttributeIDDate constant is a string that uses the Unicode Technical
Standard (UTS) #35 date format pattern yyyyMMdd'T'HHmmSS. The format of all the other constants in Table 3-5 are UTF-8
strings.
*/
//ActionID values
typedef enum {
ActionIDPositive = 0,
ActionIDNegative = 1,
//Reserved ActionID values = 2255
} esp_ActionID;
//AppAttributeID Values
typedef enum {
AppAttributeIDDisplayName = 0,
//Reserved AppAttributeID values = 1255
} esp_AppAttributeID;
typedef struct {
uint8_t noti_attribute_id;
uint16_t attribute_len;
} esp_noti_attr_list_t;
typedef enum {
Unknown_command = (0xA0), //The commandID was not recognized by the NP.
Invalid_command = (0xA1), //The command was improperly formatted.
Invalid_parameter = (0xA2), // One of the parameters (for example, the NotificationUID) does not refer to an existing object on the NP.
Action_failed = (0xA3), //The action was not performed
} esp_error_code;
typedef enum {
attr_appidentifier_index = 0, //The commandID was not recognized by the NP.
attr_title_index,
attr_subtitle_index,
attr_message_index,
attr_messagesize_index,
attr_date_index,
attr_positiveactionlabel_index,
attr_negativeactionlabel_index,
} esp_attr_index;
#define ESP_NOTIFICATIONUID_LEN 4
char *EventID_to_String(uint8_t EventID);
char *CategoryID_to_String(uint8_t CategoryID);
void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len);
void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len);
char *Errcode_to_String(uint16_t status);

View File

@@ -0,0 +1,688 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#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_defs.h"
#include "esp_bt_main.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_gatt_common_api.h"
#include "ble_ancs.h"
#define BLE_ANCS_TAG "BLE_ANCS"
#define EXAMPLE_DEVICE_NAME "ESP_BLE_ANCS"
#define PROFILE_A_APP_ID 0
#define PROFILE_NUM 1
#define ADV_CONFIG_FLAG (1 << 0)
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
#define INVALID_HANDLE 0
static uint8_t adv_config_done = 0;
static bool get_service = false;
static esp_gattc_char_elem_t *char_elem_result = NULL;
static esp_gattc_descr_elem_t *descr_elem_result = NULL;
static void periodic_timer_callback(void* arg);
esp_timer_handle_t periodic_timer;
const esp_timer_create_args_t periodic_timer_args = {
.callback = &periodic_timer_callback,
/* name is optional, but may help identify the timer when debugging */
.name = "periodic"
};
struct data_source_buffer {
uint8_t buffer[1024];
uint16_t len;
};
static struct data_source_buffer data_buffer = {0};
//In its basic form, the ANCS exposes three characteristics:
// service UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0
uint8_t Apple_NC_UUID[16] = {0xD0, 0x00, 0x2D, 0x12, 0x1E, 0x4B, 0x0F, 0xA4, 0x99, 0x4E, 0xCE, 0xB5, 0x31, 0xF4, 0x05, 0x79};
// Notification Source UUID: 9FBF120D-6301-42D9-8C58-25E699A21DBD(notifiable)
uint8_t notification_source[16] = {0xbd, 0x1d, 0xa2, 0x99, 0xe6, 0x25, 0x58, 0x8c, 0xd9, 0x42, 0x01, 0x63, 0x0d, 0x12, 0xbf, 0x9f};
// Control Point UUID:69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9(writeable with response)
uint8_t control_point[16] = {0xd9, 0xd9, 0xaa, 0xfd, 0xbd, 0x9b, 0x21, 0x98, 0xa8, 0x49, 0xe1, 0x45, 0xf3, 0xd8, 0xd1, 0x69};
// Data Source UUID:22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB(notifiable)
uint8_t data_source[16] = {0xfb, 0x7b, 0x7c, 0xce, 0x6a, 0xb3, 0x44, 0xbe, 0xb5, 0x4b, 0xd6, 0x24, 0xe9, 0xc6, 0xea, 0x22};
/*
Note: There may be more characteristics present in the ANCS than the three listed above. That said, an NC may ignore any characteristic it does not recognize.
*/
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static esp_bt_uuid_t apple_nc_uuid = {
.len = ESP_UUID_LEN_128,
};
static uint8_t hidd_service_uuid128[] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
};
// config adv data
static esp_ble_adv_data_t adv_config = {
.set_scan_rsp = false,
.include_txpower = false,
.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 = ESP_BLE_APPEARANCE_GENERIC_HID,
.service_uuid_len = sizeof(hidd_service_uuid128),
.p_service_uuid = hidd_service_uuid128,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
// config scan response data
static esp_ble_adv_data_t scan_rsp_config = {
.set_scan_rsp = true,
.include_name = true,
.manufacturer_len = 0,
.p_manufacturer_data = NULL,
};
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x100,
.adv_int_max = 0x100,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
struct gattc_profile_inst {
esp_gattc_cb_t gattc_cb;
uint16_t gattc_if;
uint16_t app_id;
uint16_t conn_id;
uint16_t service_start_handle;
uint16_t service_end_handle;
uint16_t notification_source_handle;
uint16_t data_source_handle;
uint16_t contol_point_handle;
esp_bd_addr_t remote_bda;
uint16_t MTU_size;
};
static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gattc_cb = gattc_profile_event_handler,
.gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
esp_noti_attr_list_t p_attr[8] = {
[attr_appidentifier_index] = {
.noti_attribute_id = NotificationAttributeIDAppIdentifier,
.attribute_len = 0,
},
[attr_title_index] = {
.noti_attribute_id = NotificationAttributeIDTitle,
.attribute_len = 0xFFFF,
},
[attr_subtitle_index] = {
.noti_attribute_id = NotificationAttributeIDSubtitle,
.attribute_len = 0xFFFF,
},
[attr_message_index] = {
.noti_attribute_id = NotificationAttributeIDMessage,
.attribute_len = 0xFFFF,
},
[attr_messagesize_index] = {
.noti_attribute_id = NotificationAttributeIDMessageSize,
.attribute_len = 0,
},
[attr_date_index] = {
.noti_attribute_id = NotificationAttributeIDDate,
.attribute_len = 0,
},
[attr_positiveactionlabel_index] = {
.noti_attribute_id = NotificationAttributeIDPositiveActionLabel,
.attribute_len = 0,
},
[attr_negativeactionlabel_index] = {
.noti_attribute_id = NotificationAttributeIDNegativeActionLabel,
.attribute_len = 0,
},
};
/*
| CommandID(1 Byte) | NotificationUID(4 Bytes) | AttributeIDs |
*/
void esp_get_notification_attributes(uint8_t *notificationUID, uint8_t num_attr, esp_noti_attr_list_t *p_attr)
{
uint8_t cmd[600] = {0};
uint32_t index = 0;
cmd[0] = CommandIDGetNotificationAttributes;
index ++;
memcpy(&cmd[index], notificationUID, ESP_NOTIFICATIONUID_LEN);
index += ESP_NOTIFICATIONUID_LEN;
while(num_attr > 0) {
cmd[index ++] = p_attr->noti_attribute_id;
if (p_attr->attribute_len > 0) {
cmd[index ++] = p_attr->attribute_len;
cmd[index ++] = (p_attr->attribute_len << 8);
}
p_attr ++;
num_attr --;
}
esp_ble_gattc_write_char( gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle,
index,
cmd,
ESP_GATT_WRITE_TYPE_RSP,
ESP_GATT_AUTH_REQ_NONE);
}
void esp_get_app_attributes(uint8_t *appidentifier, uint16_t appidentifier_len, uint8_t num_attr, uint8_t *p_app_attrs)
{
uint8_t buffer[600] = {0};
uint32_t index = 0;
buffer[0] = CommandIDGetAppAttributes;
index ++;
memcpy(&buffer[index], appidentifier, appidentifier_len);
index += appidentifier_len;
memcpy(&buffer[index], p_app_attrs, num_attr);
index += num_attr;
esp_ble_gattc_write_char( gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle,
index,
buffer,
ESP_GATT_WRITE_TYPE_RSP,
ESP_GATT_AUTH_REQ_NONE);
}
void esp_perform_notification_action(uint8_t *notificationUID, uint8_t ActionID)
{
uint8_t buffer[600] = {0};
uint32_t index = 0;
buffer[0] = CommandIDPerformNotificationAction;
index ++;
memcpy(&buffer[index], notificationUID, ESP_NOTIFICATIONUID_LEN);
index += ESP_NOTIFICATIONUID_LEN;
buffer[index] = ActionID;
index ++;
esp_ble_gattc_write_char( gl_profile_tab[PROFILE_A_APP_ID].gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle,
index,
buffer,
ESP_GATT_WRITE_TYPE_RSP,
ESP_GATT_AUTH_REQ_NONE);
}
static void periodic_timer_callback(void* arg)
{
esp_timer_stop(periodic_timer);
if (data_buffer.len > 0) {
esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
memset(data_buffer.buffer, 0, data_buffer.len);
data_buffer.len = 0;
}
}
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
ESP_LOGV(BLE_ANCS_TAG, "GAP_EVT, event %d\n", event);
switch (event) {
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;
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_ADV_START_COMPLETE_EVT:
//advertising start complete event to indicate advertising start successfully or failed
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(BLE_ANCS_TAG, "advertising start failed, error status = %x", param->adv_start_cmpl.status);
break;
}
ESP_LOGI(BLE_ANCS_TAG, "advertising start success");
break;
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT");
/* Call the following function to input the passkey which is displayed on the remote device */
//esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
break;
case ESP_GAP_BLE_OOB_REQ_EVT: {
ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_OOB_REQ_EVT");
uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk
esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
break;
}
case ESP_GAP_BLE_NC_REQ_EVT:
/* The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
show the passkey number to the user to confirm it with the number displayed by peer device. */
esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_SEC_REQ_EVT:
/* send the positive(true) security response to the peer device to accept the security request.
If not accept the security request, should send the security response with negative(false) accept value*/
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
///show the passkey number to the user to input it in the peer device.
ESP_LOGI(BLE_ANCS_TAG, "The passkey Notify number:%06d", param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
esp_log_buffer_hex("addr", param->ble_security.auth_cmpl.bd_addr, ESP_BD_ADDR_LEN);
ESP_LOGI(BLE_ANCS_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
if (!param->ble_security.auth_cmpl.success) {
ESP_LOGI(BLE_ANCS_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
}
break;
}
case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT:
if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(BLE_ANCS_TAG, "config local privacy failed, error status = %x", param->local_privacy_cmpl.status);
break;
}
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_config);
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "config adv data failed, error code = %x", ret);
} else {
adv_config_done |= ADV_CONFIG_FLAG;
}
ret = esp_ble_gap_config_adv_data(&scan_rsp_config);
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "config adv data failed, error code = %x", ret);
} else {
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
}
break;
default:
break;
}
}
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
switch (event) {
case ESP_GATTC_REG_EVT:
ESP_LOGI(BLE_ANCS_TAG, "REG_EVT");
esp_ble_gap_set_device_name(EXAMPLE_DEVICE_NAME);
esp_ble_gap_config_local_icon (ESP_BLE_APPEARANCE_GENERIC_WATCH);
//generate a resolvable random address
esp_ble_gap_config_local_privacy(true);
break;
case ESP_GATTC_OPEN_EVT:
if (param->open.status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG, "open failed, error status = %x", param->open.status);
break;
}
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_OPEN_EVT");
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->open.conn_id;
esp_ble_set_encryption(param->open.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, param->open.conn_id);
if (mtu_ret) {
ESP_LOGE(BLE_ANCS_TAG, "config MTU error, error code = %x", mtu_ret);
}
break;
case ESP_GATTC_CFG_MTU_EVT:
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status);
}
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
gl_profile_tab[PROFILE_A_APP_ID].MTU_size = param->cfg_mtu.mtu;
memcpy(apple_nc_uuid.uuid.uuid128, Apple_NC_UUID,16);
esp_ble_gattc_search_service(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, &apple_nc_uuid);
break;
case ESP_GATTC_SEARCH_RES_EVT: {
if (param->search_res.srvc_id.uuid.len == ESP_UUID_LEN_128) {
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = param->search_res.start_handle;
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = param->search_res.end_handle;
get_service = true;
}
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT:
if (param->search_cmpl.status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG, "search service failed, error status = %x", param->search_cmpl.status);
break;
}
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_SEARCH_CMPL_EVT");
if (get_service) {
uint16_t count = 0;
uint16_t offset = 0;
esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
ESP_GATT_DB_CHARACTERISTIC,
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
INVALID_HANDLE,
&count);
if (ret_status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__);
}
if (count > 0) {
char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
memset(char_elem_result, 0xff, sizeof(esp_gattc_char_elem_t) * count);
if (!char_elem_result) {
ESP_LOGE(BLE_ANCS_TAG, "gattc no mem");
} else {
ret_status = esp_ble_gattc_get_all_char(gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
char_elem_result,
&count,
offset);
if (ret_status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_all_char error, %d", __LINE__);
}
if (count > 0) {
for (int i = 0; i < count; i ++) {
if (char_elem_result[i].uuid.len == ESP_UUID_LEN_128) {
if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY
&& memcmp(char_elem_result[i].uuid.uuid.uuid128, notification_source, 16) == 0) {
gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle = char_elem_result[i].char_handle;
esp_ble_gattc_register_for_notify (gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].remote_bda,
char_elem_result[i].char_handle);
ESP_LOGI(BLE_ANCS_TAG, "Find Apple noticification source char");
} else if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY
&& memcmp(char_elem_result[i].uuid.uuid.uuid128, data_source, 16) == 0) {
gl_profile_tab[PROFILE_A_APP_ID].data_source_handle = char_elem_result[i].char_handle;
esp_ble_gattc_register_for_notify (gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].remote_bda,
char_elem_result[i].char_handle);
ESP_LOGI(BLE_ANCS_TAG, "Find Apple data source char");
} else if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_WRITE
&& memcmp(char_elem_result[i].uuid.uuid.uuid128, control_point, 16) == 0) {
gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle = char_elem_result[i].char_handle;
ESP_LOGI(BLE_ANCS_TAG, "Find Apple control point char");
}
}
}
}
}
free(char_elem_result);
}
} else {
ESP_LOGE(BLE_ANCS_TAG, "No Apple Notification Service found");
}
break;
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
if (param->reg_for_notify.status != ESP_GATT_OK) {
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT status %d", param->reg_for_notify.status);
break;
}
uint16_t count = 0;
uint16_t offset = 0;
//uint16_t notify_en = 1;
uint8_t notify_en[2] = {0x01, 0x00};
esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
ESP_GATT_DB_DESCRIPTOR,
gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
param->reg_for_notify.handle,
&count);
if (ret_status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__);
}
if (count > 0) {
descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count);
if (!descr_elem_result) {
ESP_LOGE(BLE_ANCS_TAG, "malloc error, gattc no mem");
} else {
ret_status = esp_ble_gattc_get_all_descr(gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
param->reg_for_notify.handle,
descr_elem_result,
&count,
offset);
if (ret_status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_all_descr error, %d", __LINE__);
}
for (int i = 0; i < count; ++ i) {
if (descr_elem_result[i].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[i].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
esp_ble_gattc_write_char_descr (gattc_if,
gl_profile_tab[PROFILE_A_APP_ID].conn_id,
descr_elem_result[i].handle,
sizeof(notify_en),
(uint8_t *)&notify_en,
ESP_GATT_WRITE_TYPE_RSP,
ESP_GATT_AUTH_REQ_NONE);
break;
}
}
}
free(descr_elem_result);
}
break;
}
case ESP_GATTC_NOTIFY_EVT:
//esp_log_buffer_hex(BLE_ANCS_TAG, param->notify.value, param->notify.value_len);
if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle) {
esp_receive_apple_notification_source(param->notify.value, param->notify.value_len);
uint8_t *notificationUID = &param->notify.value[4];
if (param->notify.value[0] == EventIDNotificationAdded && param->notify.value[2] == CategoryIDIncomingCall) {
ESP_LOGI(BLE_ANCS_TAG, "IncomingCall, reject");
//Call reject
esp_perform_notification_action(notificationUID, ActionIDNegative);
} else if (param->notify.value[0] == EventIDNotificationAdded) {
//get more information
ESP_LOGI(BLE_ANCS_TAG, "Get detailed information");
esp_get_notification_attributes(notificationUID, sizeof(p_attr)/sizeof(esp_noti_attr_list_t), p_attr);
}
} else if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].data_source_handle) {
memcpy(&data_buffer.buffer[data_buffer.len], param->notify.value, param->notify.value_len);
data_buffer.len += param->notify.value_len;
if (param->notify.value_len == (gl_profile_tab[PROFILE_A_APP_ID].MTU_size - 3)) {
// cpoy and wait next packet, start timer 500ms
esp_timer_start_periodic(periodic_timer, 500000);
} else {
esp_timer_stop(periodic_timer);
esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len);
memset(data_buffer.buffer, 0, data_buffer.len);
data_buffer.len = 0;
}
} else {
ESP_LOGI(BLE_ANCS_TAG, "unknown handle, receive notify value:");
}
break;
case ESP_GATTC_WRITE_DESCR_EVT:
if (param->write.status != ESP_GATT_OK) {
ESP_LOGE(BLE_ANCS_TAG, "write descr failed, error status = %x", param->write.status);
break;
}
//ESP_LOGI(BLE_ANCS_TAG, "write descr successfully");
break;
case ESP_GATTC_SRVC_CHG_EVT: {
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:");
esp_log_buffer_hex(BLE_ANCS_TAG, param->srvc_chg.remote_bda, 6);
break;
}
case ESP_GATTC_WRITE_CHAR_EVT:
if (param->write.status != ESP_GATT_OK) {
char *Errstr = Errcode_to_String(param->write.status);
if (Errstr) {
ESP_LOGE(BLE_ANCS_TAG, "write control point error %s", Errstr);
}
break;
}
//ESP_LOGI(BLE_ANCS_TAG, "Write char success ");
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
get_service = false;
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTC_CONNECT_EVT:
//ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_CONNECT_EVT");
//esp_log_buffer_hex("bda", param->connect.remote_bda, 6);
memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, param->connect.remote_bda, 6);
// create gattc virtual connection
esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, BLE_ADDR_TYPE_RANDOM, true);
break;
case ESP_GATTC_DIS_SRVC_CMPL_EVT:
ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DIS_SRVC_CMPL_EVT");
break;
default:
break;
}
}
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
/* If event is register event, store the gattc_if for each profile */
if (event == ESP_GATTC_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
} else {
ESP_LOGI(BLE_ANCS_TAG, "Reg app failed, app_id %04x, status %d",
param->reg.app_id,
param->reg.status);
return;
}
}
/* If the gattc_if equal to profile A, call profile A cb handler,
* so here call each profile's callback */
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gattc_if == gl_profile_tab[idx].gattc_if) {
if (gl_profile_tab[idx].gattc_cb) {
gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
}
}
}
} while (0);
}
void init_timer(void)
{
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
}
void app_main(void)
{
esp_err_t ret;
// 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 );
// init timer
init_timer();
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(BLE_ANCS_TAG, "%s init controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ESP_LOGI(BLE_ANCS_TAG, "%s init bluetooth", __func__);
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
//register the callback function to the gattc module
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "%s gattc register error, error code = %x\n", __func__, ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "gap register error, error code = %x", ret);
return;
}
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "%s gattc app register error, error code = %x\n", __func__, ret);
}
ret = esp_ble_gatt_set_local_mtu(500);
if (ret) {
ESP_LOGE(BLE_ANCS_TAG, "set local MTU failed, error code = %x", ret);
}
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //set the IO capability to No output No input
uint8_t key_size = 16; //the key size should be 7~16 bytes
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
//set static passkey
uint32_t passkey = 123456;
uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE;
uint8_t oob_support = ESP_BLE_OOB_DISABLE;
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t));
/* If your BLE device acts as a Slave, the init_key means you hope which types of key of the master should distribute to you,
and the response key means which key you can distribute to the master;
If your BLE device acts as a master, the response key means you hope which types of key of the slave should distribute to you,
and the init key means which key you can distribute to the slave. */
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
}

View File

@@ -0,0 +1,9 @@
#
# Main Makefile. This is basically the same as a component makefile.
#
# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default,
# this will take the sources in the src/ directory, compile them and link them into
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#

View File

@@ -0,0 +1,6 @@
# Override some defaults so BT stack is enabled
# by default in this example
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ble_compatibility_test)

View File

@@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := ble_compatibility_test
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,19 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP-IDF BLE Compatibility Test Example
=======================================
This demo is to test the compatibility of Bluetooth and mobile phones.
* ESP32 Module: ESP-WROOM-32
* IDF version: 7c29a39d6f9f2dfbefc49d34d34e9267afc7200d
* [Test case](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/ble_compatibility_test_case.md)
* Test APK: LightBlue V1.1.3
* [Test report](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/esp_ble_compatibility_test_report.md)

View File

@@ -0,0 +1,182 @@
# Test Case for BLE Smartphone Compatibility
This document provides a test case for BLE smartphone compatibility and includes detailed procedures for various test items.
## Preparation
### What You Need
* ESP device which needs to flash [this test program] (https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c)
* Smartphone with LightBlue® Explorer app
### Initialization
Prior to conducting tests, please initialize the smartphone and the ESP device as follows:
* Set the device name as `BLE_COMP_TEST`.
* Set the maximum transmission unit (MTU) of the device to 33 bytes, to test the assembly and division of data packets.
* If the smartphone has been paired with the ESP device before, please delete the pairing in the Bluetooth setting as follows: `Bluetooth` -> `My Devices` -> `Find this device with "i" in a circle on the right` -> `Forget this device`. Then restart the Bluetooth service.
* Before flashing the test program onto the ESP device, make sure to erase the contents of the flash by executing the command `make erase_flash flash` in the Terminal.
* When the ESP device restarts, the pairing information will be erased automatically. After that, make sure that the pairing information in the Bluetooth setting of the smartphone is deleted.
**Note:**
* For tests marked with (*) further in the document, please bear in mind the following:
* Your phone performance may affect the results of these tests. If such a test fails, it does not mean the phone fails to meet the test requirements, but that you need to arrange targeted tests.
* Taking "Test for Connection Success Rate" as an example: if the test cannot be passed for 10 consecutive times, you need to record how many times the test was passed and then arrange targeted tests.
* For extended testing, please use the [examples] (https://github.com/espressif/esp-idf/tree/master/examples/bluetooth) provided by Espressif.
## Test for ADV Performance (*)
### Search Device
Refresh the scanning in LightBlue® Explorer to check if the device to be tested can be found quickly. Please repeat this action 10 times.
### Test Results
The test is passed, if you get the following results:
* The device starts advertizing and outputs the log `(0) ***** advertising start successfully *****`.
* LightBlue® Explorer scans and successfully discovers ` BLE_COMP_TEST` each time.
**Note:**
* The device broadcasts on 3 channels, with an ADV interval of 40 ms.
* Check if the ADV packet can be received.
* Check if the Scan Response packet can be received.
* The device name is included in Scan Response packets only and cannot be found in ADV packets.
## Test for Pairing Performance
### Connect Device
* Open the LightBlue® Explorer scan list and tap on the device name ` BLE_COMP_TEST` to establish connection.
* ESP device prints a passkey: `The passkey notify number: 123456`.
* A prompt on the smartphone appears asking if you want to pair. Tap on *Pair*, and then enter the passkey "123456".
### Test Results
The test is passed, if you get the following results:
* If the connection is successful:
* Smartphone shows DATA beginning with `ADVERTISEMENT DATA`
* ESP device outputs the log: `ESP_GATTS_CONNECT_EVT`
* When the pairing is established, the device shows the following log in green: `(1) ***** pair status = success *****`
## Test for Service Discovery Performance
### Test Procedures
In LightBlue® Explorer, check the contents of `GATT SERVICES & CHARACTERISTICS`.
### Test Results
The test is passed, if you get the following results:
* Service that starts with ``000000ff`` appears at the bottom of your smartphone.
* This service contains 3 characteristics
* `Char_1_Short_WR`
* `Char_2_Long_WR`
* `Char_3_Short_Notify`
## Test for Read and Encrypt
### Test Procedures
Read the value of `Char_1` in LightBlue, and tap on `READ AGAIN`.
### Test Results
* Encryption is successful, if your smartphone shows the value "11 22 33 44", and the ESP device prints the log: `(2) ***** read char_1 *****`.
* Encryption fails, if your smartphone shows a blank screen, and the ESP device outputs the error log in red: `GATT_INSUF_AUTHENTICATION: MITM Required`.
## Test for Short Read and Write
### Test Procedures
* Navigate to the WRITE interface in LightBlue® Explorer, and write the value "88 99" to `Char_1`.
* Read `Char_1` and check if its value is consistent with the data you have written to it.
### Test Results
The test is passed, if you get the following results:
* ESP device prints the log: `(3)***** short write success *****`.
* LightBlue® Explorer shows "88 99" below `READ AGAIN`.
## Test for Long Read and Write
### Test Procedures
* Navigate to the WRITE interface in LightBlue® Explorer, and write the string `0x001122…FF001122…FF` of 256 bytes to `Char_2`. The data takes up 16 lines and looks as follows:
```
00112233445566778899AABBCCDDEEFF
00112233445566778899AABBCCDDEEFF
00112233445566778899AABBCCDDEEFF
00112233445566778899AABBCCDDEEFF
```
* Read `Char_2` and check if its value is consistent with the data you have written to it.
### Test Results
The test is passed, if you get the following results:
* The device prints the log: ``ESP_GATTS_EXEC_WRITE_EVT, Length=256`` and ``(4) ***** long write success *****``.
* LightBlue® Explorer shows `(5) ***** read char_2 *****` below `READ AGAIN`.
**Note:**
The data to be written can be copied from a text file and pasted into LightBlue® Explorer.
## Test for Short Notify
### Test Procedures
* Enter `Char_3` and tap on `SUBSCRIBE` to enable its Notify function.
* Your phone automatically receives Notify data from the device.
### Test Results
The test is passed, if you get the following results:
* ESP device prints the log: `(6) ***** send notify AA BB *****`.
* "AA BB" appears on your smartphone.
## Test for Connection Success Rate (*)
### Test procedures
* Break the connection
* Re-establish the connection
* Repeat 10 times
### Test Results
The test is passed, if you get the following results:
* Your phone establishes the connection successfully, and the ESP device outputs the log: `(1) ***** pair status = success *****`.
* Your phone breaks the connection, and the device outputs the log: `ESP_GATTS_DISCONNECT_EVT`.
* Connection can be set up each time with no issues.
## Test for Long Connection Stability
The connection must be stable throughout the tests.
**Note:**
If the existing connection breaks:
* LightBlue® Explorer prints `Disconnected`.
* ESP device outputs the log: ``ESP_GATTS_DISCONNECT_EVT, reason = (0) ***** advertising start successfully *****``.
## Further Information
* If you see any log entry in red, please record it for future reference or feedback it to our engineer.
* Tests to be added in the future:
* Multi-connection Test
* Automatic Re-connection Test

View File

@@ -0,0 +1,905 @@
<table class="table table-bordered table-striped table-condensed">
<tr>
<th colspan="14">Test Report for ESP BLE Smartphone Compatibility</th>
</tr>
<tr>
<td>ESP32 Module:</td>
<td colspan="13">ESP-WROOM-32</td>
</tr>
<tr>
<td>Commit ID:</td>
<td colspan="13">7c29a39d6f9f2dfbefc49d34d34e9267afc7200d</td>
</tr>
<tr>
<td>Test Demo:</td>
<td colspan="13">https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test</td>
</tr>
<tr>
<td rowspan="2"><font size="1">Phone Brand</td>
<td rowspan="2"><font size="2">Model</td>
<td rowspan="2"><font size="2">OS Version</td>
<td rowspan="2"><font size="2">Test APP & Version</td>
<td colspan="9" font size="2" align="center">Test Item</td>
<td rowspan="2"><font size="2">Note</td>
</tr>
<tr>
<td><font size="2">ADV</td>
<td><font size="2">Pairing</td>
<td><font size="2">Service Discovery</td>
<td><font size="2">Read & Encrypt</td>
<td><font size="2">Short Read & Write</td>
<td><font size="2">Long Read & Write</td>
<td><font size="2">Short Notify</td>
<td><font size="2">Connection Success Rate (10 times)</td>
<td><font size="2">Long Connection Stability</td>
</tr>
<tr>
<td rowspan="6"><font size="2">Samsung (三星)</td>
<td><font size="2">Galaxy S9</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2"><font size="2">LightBlue V1.1.3*</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td><font size="2">"LightBlue" here is the abbreviation of "<font size="2">LightBlue® Explorer"</td>
</tr>
<tr>
<td><font size="2">Galaxy Note 4 </td>
<td><font size="2">Android 6.0.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Galaxy S8+</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">S3 GT-I9300</td>
<td><font size="2">Android 4.3</td>
<td><font size="2">nRF Connect V4.10*</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
</tr>
<tr>
<td><font size="2">S4 GT-I9502</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2">nRF Connect V4.10*</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
</tr>
<tr>
<td><font size="2">S4 GT-I9500</td>
<td><font size="2">Android 4.3</td>
<td><font size="2">nRF Connect V4.10*</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
</tr>
<tr>
<td rowspan="7"><font size="2">Apple苹果</td>
<td><font size="2">iPhone 5S/A1518</td>
<td><font size="2">iOS 12.1</td>
<td><font size="2">LightBlue V2.7</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">iPhone X</td>
<td><font size="2">iOS 12.1</td>
<td><font size="2">LightBlue V2.7</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">iPhone SE</td>
<td><font size="2">iOS 10.2.1</td>
<td><font size="2">LightBlue V2.7</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">iPhone 6s Plus</td>
<td><font size="2">iOS 12.1</td>
<td><font size="2">LightBlue V2.7</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">iPhone 7</td>
<td><font size="2">iOS 12.0.1</td>
<td><font size="2">LightBlue V2.7</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">iPhone 6</td>
<td><font size="2">iOS 10.3.1</td>
<td><font size="2">LightBlue V2.7</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">iPod Touch</td>
<td><font size="2">iOS 12.0</td>
<td><font size="2">LightBlue V2.7</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="6"><font size="2">HUAWEI华为</td>
<td><font size="2">Huawei nova 3e</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Huawei Honor Enjoy 7X</td>
<td><font size="2">Android 7.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Huawei Mate 10</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Huawei G9/P9 Lite</td>
<td><font size="2">Android 6.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Huawei nova</td>
<td><font size="2">Android 7.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Huawei Honor 4X</td>
<td><font size="2">Android 5.0.2</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td><font size="2">Sometimes the app cannot break bluetooth connection, so you need to manually switch on and off the bluetooth.</td>
</tr>
<tr>
<td rowspan="2"><font size="2">OPPO欧珀</td>
<td><font size="2">OPPO A83</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">OPPO R9s</td>
<td><font size="2">Android 6.0.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="5"><font size="2">Xiaomi小米</td>
<td><font size="2">Xiaomi Mi Max 2</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Xiaomi 5X</td>
<td><font size="2">Android 7.1.2</td>
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Xiaomi Mi Note 2</td>
<td><font size="2">Android 7.0</td>
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Xiaomi Redmi Note 4</td>
<td><font size="2">Android 6.0</td>
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Xiaomi Mi 5</td>
<td><font size="2">Android 7.0</td>
<td><font size="2"><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="2"><font size="2">vivo步步高</td>
<td><font size="2">vivo Y85</td>
<td><font size="2">Android 8.1.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">vivo X7</td>
<td><font size="2">Android 5.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="2"><font size="2">Lenovo & Motoria (联想)</td>
<td><font size="2">Lenovo S5</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Lenovo K5</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="1"><font size="2">ZTE & Nubia中兴</td>
<td><font size="2">Nubia Z17 Mini</td>
<td><font size="2">Android 6.0.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="2"><font size="2">Gionee金立</td>
<td><font size="2">Gionee S11</td>
<td><font size="2">Android 7.11</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Gionee GN9004</td>
<td><font size="2">Android 4.3</td>
<td><font size="2">nRF Connect V4.10*</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td><font size="2">Intallation of LightBlue failed, so nRF Connect was used alternatively.</td>
</tr>
<tr>
<td><font size="2">Google谷歌</td>
<td><font size="2">LG Nexus 4*</td>
<td><font size="2">Android 5.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td><font size="2">BLE scan performance of this phone is poor.</td>
</tr>
<tr>
<td><font size="2">Sony索尼</td>
<td><font size="2">Sony Xperia XZ</td>
<td><font size="2">Android 8.0.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">HTC宏达电</td>
<td><font size="2">HTC U11</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Essential</td>
<td><font size="2">Essential Phone</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="2"><font size="2">Meizu魅族</td>
<td><font size="2">Meilan Note 3</td>
<td><font size="2">Android 5.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Meilan E</td>
<td><font size="2">Android 5.2.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Smartisan锤子</td>
<td><font size="2">Smartisan Nut Pro 2</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Sharp夏普</td>
<td><font size="2">Sharp AQUOS S3 mini </td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Hisense海信</td>
<td><font size="2">HiSense Small Dolphin 2海信小海豚 2</td>
<td><font size="2">Android 7.1.2</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="2"><font size="2">360奇虎</td>
<td><font size="2">360 N6 Lite</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">360 N5</td>
<td><font size="2">Android 6.0.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="2"><font size="2">Xiaolajiao小辣椒</td>
<td><font size="2">Red Chilli 4A红辣椒 4A</td>
<td><font size="2">Android 3.2.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Red Chilli Enjoy 6A红辣椒畅玩 6A</td>
<td><font size="2">Android 5.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td rowspan="2"><font size="2"><font size="2">Letv乐视</td>
<td><font size="2">LeTV LeEcoo Le S3</td>
<td><font size="2">Android 6.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">LeTV LeEoo Le1 (X600)</td>
<td><font size="2">Android 5.0.2</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Coolpad酷派</td>
<td><font size="2">Coolpad Cool 1 dual</td>
<td><font size="2">Android 6.0.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Doov朵唯</td>
<td><font size="2">Doov A15S</td>
<td><font size="2">Android 5.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">AGM艾捷莫</td>
<td><font size="2">AGM X1</td>
<td><font size="2">Android 5.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">CMCC中国移动</td>
<td><font size="2">CMCC N3</td>
<td><font size="2">Android 7.1.2</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">Meitu美图</td>
<td><font size="2">Meitu M8s</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">K-Touch天语</td>
<td><font size="2">K-Touch X11</td>
<td><font size="2">Android 6.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">YEPEN誉品</td>
<td><font size="2">YEPEN I7S</td>
<td><font size="2">Android 6.0</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
<td><font size="2">MOTO</td>
<td><font size="2">Z2 Paly</td>
<td><font size="2">Android 7.1.1</td>
<td><font size="2">LightBlue V1.1.3</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">Pass</td>
<td><font size="2">100%</td>
<td><font size="2">Pass</td>
<td></td>
</tr>
<tr>
</table>

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "ble_compatibility_test.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,700 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/********************************************************************************
*
* This file is for gatt server. It can send adv data, and get connected by client.
*
*********************************************************************************/
#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 "ble_compatibility_test.h"
#include "esp_gatt_common_api.h"
#define DEBUG_ON 0
#if DEBUG_ON
#define EXAMPLE_DEBUG ESP_LOGI
#else
#define EXAMPLE_DEBUG( tag, format, ... )
#endif
#define EXAMPLE_TAG "BLE_COMP"
#define PROFILE_NUM 1
#define PROFILE_APP_IDX 0
#define ESP_APP_ID 0x55
#define SAMPLE_DEVICE_NAME "BLE_COMP_TEST"
#define SVC_INST_ID 0
/* The max length of characteristic value. When the gatt client write or prepare write,
* the data length must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
*/
#define GATTS_EXAMPLE_CHAR_VAL_LEN_MAX 500
#define LONG_CHAR_VAL_LEN 500
#define SHORT_CHAR_VAL_LEN 10
#define GATTS_NOTIFY_FIRST_PACKET_LEN_MAX 20
#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)
static uint8_t adv_config_done = 0;
uint16_t gatt_db_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;
//#define CONFIG_SET_RAW_ADV_DATA
#ifdef CONFIG_SET_RAW_ADV_DATA
static uint8_t raw_adv_data[] = {
/* flags */
0x02, 0x01, 0x06,
/* tx power*/
0x02, 0x0a, 0xeb,
/* service uuid */
0x03, 0x03, 0xFF, 0x00,
/* device name */
0x0E, 0x09, 'B', 'L', 'E', '_', 'C', 'O','M', 'P', '_', 'T','E', 'S', 'T'
};
static uint8_t raw_scan_rsp_data[] = {
/* flags */
0x02, 0x01, 0x06,
/* tx power */
0x02, 0x0a, 0xeb,
/* service uuid */
0x03, 0x03, 0xFF,0x00
};
#else
static uint8_t service_uuid[16] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value
0xfb, 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 = 0x20,
.max_interval = 0x40,
.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 = 0x20,
.max_interval = 0x40,
.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 = 16,
.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 = 0x40,
.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;
};
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 CHAR_1_SHORT_WR = 0xFF01;
static const uint16_t CHAR_2_LONG_WR = 0xFF02;
static const uint16_t CHAR_3_SHORT_NOTIFY = 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 uint16_t character_user_description = ESP_GATT_UUID_CHAR_DESCRIPTION;
static const uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ;
static const uint8_t char1_name[] = "Char_1_Short_WR";
static const uint8_t char2_name[] = "Char_2_Long_WR";
static const uint8_t char3_name[] = "Char_3_Short_Notify";
static const uint8_t char_ccc[2] = {0x00, 0x00};
static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44};
/* 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}},
/* Characteristic Value */
[IDX_CHAR_VAL_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_1_SHORT_WR, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE | ESP_GATT_PERM_READ_ENC_MITM,
SHORT_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
/* Characteristic User Descriptor */
[IDX_CHAR_CFG_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
sizeof(char1_name), sizeof(char1_name), (uint8_t *)char1_name}},
/* 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_write}},
/* Characteristic Value */
[IDX_CHAR_VAL_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_2_LONG_WR, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
LONG_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
/* Characteristic User Descriptor */
[IDX_CHAR_CFG_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
sizeof(char2_name), sizeof(char2_name), (uint8_t *)char2_name}},
/* 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_notify}},
/* Characteristic Value */
[IDX_CHAR_VAL_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_3_SHORT_NOTIFY, 0,
LONG_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
/* Characteristic User Descriptor */
[IDX_CHAR_CFG_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
sizeof(char3_name), sizeof(char3_name), (uint8_t *)char3_name}},
/* Characteristic Client Configuration Descriptor */
[IDX_CHAR_CFG_C_2] =
{{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(char_ccc), (uint8_t *)char_ccc}},
};
static void show_bonded_devices(void)
{
int dev_num = esp_ble_get_bond_device_num();
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
esp_ble_get_bond_device_list(&dev_num, dev_list);
EXAMPLE_DEBUG(EXAMPLE_TAG, "Bonded devices number : %d\n", dev_num);
EXAMPLE_DEBUG(EXAMPLE_TAG, "Bonded devices list : %d\n", dev_num);
for (int i = 0; i < dev_num; i++) {
#if DEBUG_ON
esp_log_buffer_hex(EXAMPLE_TAG, (void *)dev_list[i].bd_addr, sizeof(esp_bd_addr_t));
#endif
}
free(dev_list);
}
static void __attribute__((unused)) remove_all_bonded_devices(void)
{
int dev_num = esp_ble_get_bond_device_num();
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
esp_ble_get_bond_device_list(&dev_num, dev_list);
for (int i = 0; i < dev_num; i++) {
esp_ble_remove_bond_device(dev_list[i].bd_addr);
}
free(dev_list);
}
static 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:
/* advertising start complete event to indicate advertising start successfully or failed */
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(EXAMPLE_TAG, "advertising start failed");
}else{
ESP_LOGI(EXAMPLE_TAG, "(0) ***** advertising start successfully ***** \n");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(EXAMPLE_TAG, "Advertising stop failed");
}
else {
ESP_LOGI(EXAMPLE_TAG, "Stop adv successfully\n");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
EXAMPLE_DEBUG(EXAMPLE_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;
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT");
//esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
break;
case ESP_GAP_BLE_NC_REQ_EVT:
/* The app will receive this event when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
show the passkey number to the user to confirm it with the number displayed by peer device. */
ESP_LOGI(EXAMPLE_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_SEC_REQ_EVT:
/* send the positive(true) security response to the peer device to accept the security request.
If not accept the security request, should send the security response with negative(false) accept value*/
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
///show the passkey number to the user to input it in the peer device.
ESP_LOGI(EXAMPLE_TAG, "The passkey notify number:%d", param->ble_security.key_notif.passkey);
break;
case ESP_GAP_BLE_KEY_EVT:
//shows the ble key info share with peer device to the user.
EXAMPLE_DEBUG(EXAMPLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
break;
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
EXAMPLE_DEBUG(EXAMPLE_TAG, "remote BD_ADDR: %08x%04x",\
(bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
(bd_addr[4] << 8) + bd_addr[5]);
EXAMPLE_DEBUG(EXAMPLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
if (param->ble_security.auth_cmpl.success){
ESP_LOGI(EXAMPLE_TAG, "(1) ***** pair status = success ***** \n");
}
else {
ESP_LOGI(EXAMPLE_TAG, "***** pair status = fail, reason = 0x%x *****\n", param->ble_security.auth_cmpl.fail_reason);
}
show_bonded_devices();
break;
}
case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: {
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT status = %d", param->remove_bond_dev_cmpl.status);
#if DEBUG_ON
esp_log_buffer_hex(EXAMPLE_TAG, (void *)param->remove_bond_dev_cmpl.bd_addr, sizeof(esp_bd_addr_t));
#endif
EXAMPLE_DEBUG(EXAMPLE_TAG, "------------------------------------");
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)
{
EXAMPLE_DEBUG(EXAMPLE_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(EXAMPLE_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;
}
}
/*send response when param->write.need_rsp is true */
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(EXAMPLE_TAG, "Send response error");
}
free(gatt_rsp);
}else{
ESP_LOGE(EXAMPLE_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;
}
uint8_t long_write[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
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){
if(prepare_write_env->prepare_len == 256) {
bool long_write_success = true;
for(uint16_t i = 0; i < prepare_write_env->prepare_len; i ++) {
if(prepare_write_env->prepare_buf[i] != long_write[i%16]) {
long_write_success = false;
break;
}
}
if(long_write_success) {
ESP_LOGI(EXAMPLE_TAG, "(4) ***** long write success ***** \n");
}
}
}else{
ESP_LOGI(EXAMPLE_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;
}
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)
{
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(EXAMPLE_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(EXAMPLE_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(EXAMPLE_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
}
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
#else
//config adv data
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(EXAMPLE_TAG, "config adv data failed, error code = %x", ret);
}
adv_config_done |= ADV_CONFIG_FLAG;
//config scan response data
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(EXAMPLE_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(EXAMPLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
}
}
break;
case ESP_GATTS_READ_EVT:
//ESP_LOGE(EXAMPLE_TAG, "ESP_GATTS_READ_EVT, handle=0x%d, offset=%d", param->read.handle, param->read.offset);
if(gatt_db_handle_table[IDX_CHAR_VAL_A] == param->read.handle) {
ESP_LOGE(EXAMPLE_TAG, "(2) ***** read char1 ***** \n");
}
if(gatt_db_handle_table[IDX_CHAR_VAL_B] == param->read.handle) {
ESP_LOGE(EXAMPLE_TAG, "(5) ***** read char2 ***** \n");
}
break;
case ESP_GATTS_WRITE_EVT:
if (!param->write.is_prep){
// the data length of gattc write must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
if (gatt_db_handle_table[IDX_CHAR_CFG_C_2] == param->write.handle && param->write.len == 2){
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
uint8_t notify_data[2];
notify_data[0] = 0xAA;
notify_data[1] = 0xBB;
if (descr_value == 0x0001){
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatt_db_handle_table[IDX_CHAR_VAL_C],
sizeof(notify_data), notify_data, false);
ESP_LOGI(EXAMPLE_TAG, "(6) ***** send notify AA BB ***** \n");
}else if (descr_value == 0x0002){
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatt_db_handle_table[IDX_CHAR_VAL_C],
sizeof(notify_data), notify_data, true);
}
else if (descr_value == 0x0000){
ESP_LOGI(EXAMPLE_TAG, "notify/indicate disable ");
}else{
ESP_LOGE(EXAMPLE_TAG, "unknown descr value");
esp_log_buffer_hex(EXAMPLE_TAG, param->write.value, param->write.len);
}
}
if(gatt_db_handle_table[IDX_CHAR_VAL_A] == param->write.handle && param->write.len == 2) {
uint8_t write_data[2] = {0x88, 0x99};
if(memcmp(write_data, param->write.value, param->write.len) == 0) {
ESP_LOGI(EXAMPLE_TAG, "(3)***** short write success ***** \n");
}
}
/* 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;
case ESP_GATTS_EXEC_WRITE_EVT:
// the length of gattc prepare write data must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, Length=%d", prepare_write_env.prepare_len);
example_exec_write_event_env(&prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT:
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
break;
case ESP_GATTS_CONF_EVT:
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GATTS_CONF_EVT, status = %d", param->conf.status);
break;
case ESP_GATTS_START_EVT:
EXAMPLE_DEBUG(EXAMPLE_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
/* start security connect with peer device when receive the connect event sent by the master */
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", 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(EXAMPLE_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(EXAMPLE_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(EXAMPLE_TAG, "create attribute table successfully, the number handle = %d\n",param->add_attr_tab.num_handle);
memcpy(gatt_db_handle_table, param->add_attr_tab.handles, sizeof(gatt_db_handle_table));
esp_ble_gatts_start_service(gatt_db_handle_table[IDX_SVC]);
}
break;
}
default:
break;
}
}
static 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 is register event, store the gatts_if for each profile */
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
} else {
ESP_LOGE(EXAMPLE_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++) {
/* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
if (gatts_if == ESP_GATT_IF_NONE || gatts_if == heart_rate_profile_tab[idx].gatts_if) {
if (heart_rate_profile_tab[idx].gatts_cb) {
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
void app_main(void)
{
esp_err_t ret;
/* Initialize NVS. */
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
ESP_ERROR_CHECK(nvs_flash_erase());
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(EXAMPLE_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(EXAMPLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(EXAMPLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(EXAMPLE_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(EXAMPLE_TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(EXAMPLE_TAG, "gap register error, error code = %x", ret);
return;
}
ret = esp_ble_gatts_app_register(ESP_APP_ID);
if (ret){
ESP_LOGE(EXAMPLE_TAG, "gatts app register error, error code = %x", ret);
return;
}
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(33);
if (local_mtu_ret){
ESP_LOGE(EXAMPLE_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT; //set the IO capability to No output No input
uint8_t key_size = 16; //the key size should be 7~16 bytes
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint32_t passkey = 123456;
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
/* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
and the response key means which key you can distribute to the Master;
If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
and the init key means which key you can distribute to the slave. */
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
}

View File

@@ -0,0 +1,33 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Attributes State Machine */
enum
{
IDX_SVC,
IDX_CHAR_A,
IDX_CHAR_VAL_A,
IDX_CHAR_CFG_A,
IDX_CHAR_B,
IDX_CHAR_VAL_B,
IDX_CHAR_CFG_B,
IDX_CHAR_C,
IDX_CHAR_VAL_C,
IDX_CHAR_CFG_C,
IDX_CHAR_CFG_C_2,
HRS_IDX_NB,
};

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,14 @@
# Override some defaults so BT stack is enabled
# in this example
#
# BT config
#
CONFIG_BT_ENABLED=y
#
# ESP32-specific config
#
CONFIG_ESP32_ENABLE_STACK_BT=y
# CONFIG_ESP32_ENABLE_STACK_NONE is not set
CONFIG_MEMMAP_BT=y

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ble_eddystone_demo)

View File

@@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := ble_eddystone_demo
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,25 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP-IDF Eddystone demo
========================
This example demonstrates Eddystone-compatible BLE scanning of eddystone frame,including UID,URL.
Eddystone is an open beacon protocol specification from Google aimed at improving “proximity-based experiences”
with support for both Android and iOS smart device platforms.
Learn more on https://developers.google.com/beacons and https://github.com/google/eddystone.
esp_eddystone_protocol.h
==========================
This header file includes some eddystone-protocol related definitions.
esp_eddystone_api.h & esp_eddystone_api.c
===========================================
These files contains the decoding and decoded result of an eddystone frame packet.
You just need to include esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c for development.
esp_eddystone_demo.c
======================
This is an example/demo of using esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c files to resolve eddystone packet.

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "esp_eddystone_api.c"
"esp_eddystone_demo.c"
INCLUDE_DIRS "")

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,254 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/****************************************************************************
*
* This file is used to decode eddystone information.
*
****************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_gap_ble_api.h"
#include "esp_eddystone_protocol.h"
#include "esp_eddystone_api.h"
/* Declare static functions */
static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
static char* esp_eddystone_resolve_url_scheme(const uint8_t* url_start, const uint8_t* url_end);
static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
/* Eddystone-URL scheme prefixes */
static const char* eddystone_url_prefix[4] = {
"http://www.",
"https://www.",
"http://",
"https://"
};
/* Eddystone-URL HTTP URL encoding */
static const char* eddystone_url_encoding[14] = {
".com/",
".org/",
".edu/",
".net/",
".info/",
".biz/",
".gov/",
".com",
".org",
".edu",
".net",
".info",
".biz",
".gov"
};
/****************** Eddystone-UID **************
Byte offset Field Description
0 Frame Type Value = 0x00
1 Ranging Data Calibrated Tx power at 0 m
2 NID[0] 10-byte Namespace
3 NID[1]
4 NID[2]
5 NID[3]
6 NID[4]
7 NID[5]
8 NID[6]
9 NID[7]
10 NID[8]
11 NID[9]
12 BID[0] 6-byte Instance
13 BID[1]
14 BID[2]
15 BID[3]
16 BID[4]
17 BID[5]
18 RFU Reserved for future use, must be0x00
19 RFU Reserved for future use, must be0x00
*********************************************/
/* decode and store received UID */
static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
uint8_t pos = 0;
//1-byte Ranging Data + 10-byte Namespace + 6-byte Instance
if((len != EDDYSTONE_UID_DATA_LEN) && (len != (EDDYSTONE_UID_RFU_LEN+EDDYSTONE_UID_DATA_LEN))) {
//ERROR:uid len wrong
return -1;
}
res->inform.uid.ranging_data = buf[pos++];
for(int i=0; i<EDDYSTONE_UID_NAMESPACE_LEN; i++) {
res->inform.uid.namespace_id[i] = buf[pos++];
}
for(int i=0; i<EDDYSTONE_UID_INSTANCE_LEN; i++) {
res->inform.uid.instance_id[i] = buf[pos++];
}
return 0;
}
/* resolve received URL to url_res pointer */
static char* esp_eddystone_resolve_url_scheme(const uint8_t *url_start, const uint8_t *url_end)
{
int pos = 0;
static char url_buf[100] = {0};
const uint8_t *p = url_start;
pos += sprintf(&url_buf[pos], "%s", eddystone_url_prefix[*p++]);
for (; p <= url_end; p++) {
if (esp_eddystone_is_char_invalid((*p))) {
pos += sprintf(&url_buf[pos], "%s", eddystone_url_encoding[*p]);
} else {
pos += sprintf(&url_buf[pos], "%c", *p);
}
}
return url_buf;
}
/************************** Eddystone-URL *************
Frame Specification
Byte offset Field Description
0 Frame Type Value = 0x10
1 TX Power Calibrated Tx power at 0 m
2 URL Scheme Encoded Scheme Prefix
3+ Encoded URL Length 1-17
*******************************************************/
/* decode and store received URL, the pointer url_res points to the resolved url */
static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
char *url_res = NULL;
uint8_t pos = 0;
if(len-EDDYSTONE_URL_TX_POWER_LEN > EDDYSTONE_URL_MAX_LEN) {
//ERROR:too long url
return -1;
}
res->inform.url.tx_power = buf[pos++];
url_res = esp_eddystone_resolve_url_scheme(buf+pos, buf+len-1);
memcpy(&res->inform.url.url, url_res, strlen(url_res));
res->inform.url.url[strlen(url_res)] = '\0';
return 0;
}
/****************** eddystone-tlm ***************
* Unencrypted TLM Frame Specification
Byte offset Field Description
0 Frame Type Value = 0x20
1 Version TLM version, value = 0x00
2 VBATT[0] Battery voltage, 1 mV/bit
3 VBATT[1]
4 TEMP[0] Beacon temperature
5 TEMP[1]
6 ADV_CNT[0] Advertising PDU count
7 ADV_CNT[1]
8 ADV_CNT[2]
9 ADV_CNT[3]
10 SEC_CNT[0] Time since power-on or reboot
11 SEC_CNT[1]
12 SEC_CNT[2]
13 SEC_CNT[3]
************************************************/
/* decode and store received TLM */
static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
uint8_t pos = 0;
if(len > EDDYSTONE_TLM_DATA_LEN) {
//ERROR:TLM too long
return -1;
}
res->inform.tlm.version = buf[pos++];
res->inform.tlm.battery_voltage = big_endian_read_16(buf, pos);
pos += 2;
uint16_t temp = big_endian_read_16(buf, pos);
int8_t temp_integral = (int8_t)((temp >> 8) & 0xff);
float temp_decimal = (temp & 0xff) / 256.0;
res->inform.tlm.temperature = temp_integral + temp_decimal;
pos += 2;
res->inform.tlm.adv_count = big_endian_read_32(buf, pos);
pos += 4;
res->inform.tlm.time = big_endian_read_32(buf, pos);
return 0;
}
static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
static esp_err_t ret=-1;
switch(res->common.frame_type)
{
case EDDYSTONE_FRAME_TYPE_UID: {
ret = esp_eddystone_uid_received(buf, len, res);
break;
}
case EDDYSTONE_FRAME_TYPE_URL: {
ret = esp_eddystone_url_received(buf, len, res);
break;
}
case EDDYSTONE_FRAME_TYPE_TLM: {
ret = esp_eddystone_tlm_received(buf, len, res);
break;
}
default:
break;
}
return ret;
}
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
if (len == 0 || buf == NULL || res == NULL) {
return -1;
}
uint8_t pos=0;
while(res->common.srv_data_type != EDDYSTONE_SERVICE_UUID)
{
pos++;
if(pos >= len ) {
return -1;
}
uint8_t ad_type = buf[pos++];
switch(ad_type)
{
case ESP_BLE_AD_TYPE_FLAG: {
res->common.flags = buf[pos++];
break;
}
case ESP_BLE_AD_TYPE_16SRV_CMPL: {
uint16_t uuid = little_endian_read_16(buf, pos);
if(uuid != EDDYSTONE_SERVICE_UUID) {
return -1;
}
res->common.srv_uuid = uuid;
pos += 2;
break;
}
case ESP_BLE_AD_TYPE_SERVICE_DATA: {
uint16_t type = little_endian_read_16(buf, pos);
pos += 2;
uint8_t frame_type = buf[pos++];
if(type != EDDYSTONE_SERVICE_UUID || !(frame_type == EDDYSTONE_FRAME_TYPE_UID || frame_type == EDDYSTONE_FRAME_TYPE_URL ||
frame_type == EDDYSTONE_FRAME_TYPE_TLM)) {
return -1;
}
res->common.srv_data_type = type;
res->common.frame_type = frame_type;
break;
}
default:
break;
}
}
return esp_eddystone_get_inform(buf+pos, len-pos, res);
}

View File

@@ -0,0 +1,70 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#ifndef __ESP_EDDYSTONE_API_H__
#define __ESP_EDDYSTONE_API_H__
typedef struct {
struct {
uint8_t flags; /*<! AD flags data */
uint16_t srv_uuid; /*<! complete list of 16-bit service uuid*/
uint16_t srv_data_type; /*<! service data type */
uint8_t frame_type; /*<! Eddystone UID, URL or TLM */
} common;
union {
struct {
/*<! Eddystone-UID */
int8_t ranging_data; /*<! calibrated Tx power at 0m */
uint8_t namespace_id[10];
uint8_t instance_id[6];
} uid;
struct {
/*<! Eddystone-URL */
int8_t tx_power; /*<! calibrated Tx power at 0m */
char url[EDDYSTONE_URL_MAX_LEN]; /*<! the decoded URL */
} url;
struct {
/*<! Eddystone-TLM */
uint8_t version; /*<! TLM version,0x00 for now */
uint16_t battery_voltage; /*<! battery voltage in mV */
float temperature; /*<! beacon temperature in degrees Celsius */
uint32_t adv_count; /*<! adv pdu count since power-up */
uint32_t time; /*<! time since power-up, a 0.1 second resolution counter */
} tlm;
} inform;
} esp_eddystone_result_t;
/* utils */
static inline uint16_t little_endian_read_16(const uint8_t *buffer, uint8_t pos)
{
return ((uint16_t)buffer[pos]) | (((uint16_t)buffer[(pos)+1]) << 8);
}
static inline uint16_t big_endian_read_16(const uint8_t *buffer, uint8_t pos)
{
return (((uint16_t)buffer[pos]) << 8) | ((uint16_t)buffer[(pos)+1]);
}
static inline uint32_t big_endian_read_32(const uint8_t *buffer, uint8_t pos)
{
return (((uint32_t)buffer[pos]) << 24) | (((uint32_t)buffer[(pos)+1]) << 16) | (((uint32_t)buffer[(pos)+2]) << 8) | ((uint32_t)buffer[(pos)+3]);
}
/*
* The esp eddystone API.
* This function is called to decode eddystone information from adv_data.
* The res points to the result struct.
*
*/
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
//bool esp_eddystone_is_eddystone_packet(.....);
#endif /* __ESP_EDDYSTONE_API_H__ */

View File

@@ -0,0 +1,175 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/****************************************************************************
*
* This file is used for eddystone receiver.
*
****************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "esp_bt.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_defs.h"
#include "esp_gattc_api.h"
#include "esp_gap_ble_api.h"
#include "freertos/FreeRTOS.h"
#include "esp_eddystone_protocol.h"
#include "esp_eddystone_api.h"
static const char* DEMO_TAG = "EDDYSTONE_DEMO";
/* declare static functions */
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param);
static void esp_eddystone_show_inform(const esp_eddystone_result_t* res);
static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50,
.scan_window = 0x30,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
};
static void esp_eddystone_show_inform(const esp_eddystone_result_t* res)
{
switch(res->common.frame_type)
{
case EDDYSTONE_FRAME_TYPE_UID: {
ESP_LOGI(DEMO_TAG, "Eddystone UID inform:");
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.uid.ranging_data);
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Namespace ID:0x");
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.namespace_id, 10);
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Instance ID:0x");
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.instance_id, 6);
break;
}
case EDDYSTONE_FRAME_TYPE_URL: {
ESP_LOGI(DEMO_TAG, "Eddystone URL inform:");
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.url.tx_power);
ESP_LOGI(DEMO_TAG, "URL: %s", res->inform.url.url);
break;
}
case EDDYSTONE_FRAME_TYPE_TLM: {
ESP_LOGI(DEMO_TAG, "Eddystone TLM inform:");
ESP_LOGI(DEMO_TAG, "version: %d", res->inform.tlm.version);
ESP_LOGI(DEMO_TAG, "battery voltage: %d mV", res->inform.tlm.battery_voltage);
ESP_LOGI(DEMO_TAG, "beacon temperature in degrees Celsius: %6.1f", res->inform.tlm.temperature);
ESP_LOGI(DEMO_TAG, "adv pdu count since power-up: %d", res->inform.tlm.adv_count);
ESP_LOGI(DEMO_TAG, "time since power-up: %d s", (res->inform.tlm.time)/10);
break;
}
default:
break;
}
}
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
{
esp_err_t err;
switch(event)
{
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
uint32_t duration = 0;
esp_ble_gap_start_scanning(duration);
break;
}
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: {
if((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan start failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(DEMO_TAG,"Start scanning...");
}
break;
}
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
esp_ble_gap_cb_param_t* scan_result = (esp_ble_gap_cb_param_t*)param;
switch(scan_result->scan_rst.search_evt)
{
case ESP_GAP_SEARCH_INQ_RES_EVT: {
esp_eddystone_result_t eddystone_res;
memset(&eddystone_res, 0, sizeof(eddystone_res));
esp_err_t ret = esp_eddystone_decode(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len, &eddystone_res);
if (ret) {
// error:The received data is not an eddystone frame packet or a correct eddystone frame packet.
// just return
return;
} else {
// The received adv data is a correct eddystone frame packet.
// Here, we get the eddystone infomation in eddystone_res, we can use the data in res to do other things.
// For example, just print them:
ESP_LOGI(DEMO_TAG, "--------Eddystone Found----------");
esp_log_buffer_hex("EDDYSTONE_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi);
esp_eddystone_show_inform(&eddystone_res);
}
break;
}
default:
break;
}
break;
}
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:{
if((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan stop failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(DEMO_TAG,"Stop scan successfully");
}
break;
}
default:
break;
}
}
void esp_eddystone_appRegister(void)
{
esp_err_t status;
ESP_LOGI(DEMO_TAG,"Register callback");
/*<! register the scan callback function to the gap module */
if((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
ESP_LOGE(DEMO_TAG,"gap register error: %s", esp_err_to_name(status));
return;
}
}
void esp_eddystone_init(void)
{
esp_bluedroid_init();
esp_bluedroid_enable();
esp_eddystone_appRegister();
}
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
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();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
esp_eddystone_init();
/*<! set scan parameters */
esp_ble_gap_set_scan_params(&ble_scan_params);
}

View File

@@ -0,0 +1,112 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#ifndef __ESP_EDDYSTONE_PROTOCOL_H__
#define __ESP_EDDYSTONE_PROTOCOL_H__
#include "stdbool.h"
#include "stdint.h"
/* Eddystone definitions */
#define EDDYSTONE_SERVICE_UUID 0xFEAA
#define EDDYSTONE_FRAME_TYPE_UID 0x00
#define EDDYSTONE_FRAME_TYPE_URL 0x10
#define EDDYSTONE_FRAME_TYPE_TLM 0x20
#define EDDYSTONE_FRAME_TYPE_EID 0x30
//UID
#define EDDYSTONE_UID_RANG_DATA_LEN 1
#define EDDYSTONE_UID_NAMESPACE_LEN 10
#define EDDYSTONE_UID_INSTANCE_LEN 6
#define EDDYSTONE_UID_RFU_LEN 2
#define EDDYSTONE_UID_DATA_LEN (EDDYSTONE_UID_RANG_DATA_LEN + EDDYSTONE_UID_NAMESPACE_LEN + EDDYSTONE_UID_INSTANCE_LEN)
//TLM
#define EDDYSTONE_TLM_VERSION_LEN 1
#define EDDYSTONE_TLM_BATTERY_VOLTAGE_LEN 2
#define EDDYSTONE_TLM_TEMPERATURE_LEN 2
#define EDDYSTONE_TLM_ADV_COUNT_LEN 4
#define EDDYSTONE_TLM_TIME_LEN 4
#define EDDYSTONE_TLM_DATA_LEN (EDDYSTONE_TLM_VERSION_LEN + EDDYSTONE_TLM_BATTERY_VOLTAGE_LEN + \
EDDYSTONE_TLM_TEMPERATURE_LEN + EDDYSTONE_TLM_ADV_COUNT_LEN + EDDYSTONE_TLM_TIME_LEN)
//URL
#define EDDYSTONE_URL_SCHEME_LEN 1
#define EDDYSTONE_URL_ENCODED_MAX_LEN 17
#define EDDYSTONE_URL_MAX_LEN (EDDYSTONE_URL_SCHEME_LEN + EDDYSTONE_URL_ENCODED_MAX_LEN)
#define EDDYSTONE_URL_TX_POWER_LEN 1
/* Eddystone UID frame */
typedef struct {
int8_t ranging_data; /*<! calibrated Tx power at 0m */
uint8_t namespace_id[10];
uint8_t instance_id[6];
uint8_t reserved[2];
} __attribute__((packed))esp_eddystone_uid_t;
/* Eddystone URL frame */
typedef struct {
int8_t tx_power; /*<! calibrated Tx power at 0m */
uint8_t url_scheme; /*<! encoded scheme prefix */
uint8_t encoded_url[0]; /*<! length 1-17 */
} __attribute__((packed))esp_eddystone_url_t;
/* Eddystone TLM frame */
typedef struct {
uint8_t version; /*<! TLM version,0x00 for now */
uint16_t batt; /*<! battery voltage, 1mV/bit */
uint16_t temp; /*<! beacon temperature */
uint32_t adv_count; /*<! adv pdu count since power-on or reboot */
uint32_t time; /*<! time sence power-on or reboot, a 0.1 second resolution counter */
} __attribute__((packed)) esp_eddystone_tlm_t;
/* AD Structure of flags */
typedef struct {
uint8_t len;
uint8_t type;
uint8_t flags;
} __attribute__((packed)) esp_eddystone_flags_t;
/* AD Structure of complete 16-bit service uuid */
typedef struct {
uint8_t len;
uint8_t type;
uint16_t uuid; /*<! complete list of 16-bit service UUIDs data type value */
} __attribute__((packed)) esp_eddystone_uuid_t;
/* AD Structure of eddystone frame*/
typedef struct {
uint8_t len; /*<! length of eddystone data */
uint8_t type; /*<! service data type,must be 0x16 */
uint16_t uuid; /*<! 16-bit eddystone uuid */
uint8_t frame_type;
union {
esp_eddystone_uid_t uid;
esp_eddystone_url_t url;
esp_eddystone_tlm_t tlm;
} u[0];
} __attribute__((packed)) esp_eddystone_frame_t;
/* eddystone packet type */
typedef struct {
esp_eddystone_flags_t flags;
esp_eddystone_uuid_t uuid;
esp_eddystone_frame_t frame;
} __attribute__((packed)) esp_eddystone_packet_t;
/*
* URLs are written only with the graphic printable characters of the US-ASCII coded character set.
* The octets 00-20 and 7F-FF hexadecimal are not used.
* See “Excluded US-ASCII Characters” in RFC 2936.
*
*/
static inline bool esp_eddystone_is_char_invalid(int ch)
{
return (ch >= 0x00 && ch <= 0x20) || (ch >= 0x7f && ch <= 0xff);
}
#endif /* __ESP_EDDYSTONE_PROTOCOL_H__ */

View File

@@ -0,0 +1,6 @@
# Override some defaults so BT stack is enabled
# and WiFi disabled by default in this example
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hidd_demos)

View File

@@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := hidd_demos
COMPONENT_ADD_INCLUDEDIRS := components/include \
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,34 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
ESP-IDF BLE HID device demo
========================
This example Implemented BLE HID device profile related functions, in which the HID device has
4 Reports (1 is mouse, 2 is keyboard and LED, 3 is Consumer Devices, 4 is Vendor devices).
Users can choose different reports according to their own application scenarios.
BLE HID profile inheritance and USB HID class.
ble_hidd_demo_main.c
==========================
This file is the demo to show how to used the HID(you can used it to connected to the smart phone act as the consumer device then can used the button to
volume++ or volume-- etc., or connected to the Windows 10 PC act as a keyboard or mouse)
hidd_le_prf_int.h
==========================
This header file includes some HID profile related definitions.
esp_hidd_prf_api.h & esp_hidd_prf_api.c
===========================================
These files contains the the api of the HID profile
When you used the HID profile, you just need to added the esp_hidd_prf_api.h includes file and send the HID data used the function defined in the esp_hidd_prf_api.c file.
hid_dev.h & hid_dev.c
======================
These file define the HID spec related definitions
hid_device_le_prf.c
======================
This file is the HID profile definition file, it include the main function of the HID profile.
It mainly includes how to create HID service. If you send and receive HID data and convert the data to keyboard keys,
the mouse and consumer values are forwarded to the application.

View File

@@ -0,0 +1,7 @@
idf_component_register(SRCS "ble_hidd_demo_main.c"
"esp_hidd_prf_api.c"
"hid_dev.c"
"hid_device_le_prf.c"
INCLUDE_DIRS ".")
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable)

View File

@@ -0,0 +1,251 @@
/* This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this software is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#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_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_hidd_prf_api.h"
#include "esp_bt_defs.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "driver/gpio.h"
#include "hid_dev.h"
/**
* Brief:
* This example Implemented BLE HID device profile related functions, in which the HID device
* has 4 Reports (1 is mouse, 2 is keyboard and LED, 3 is Consumer Devices, 4 is Vendor devices).
* Users can choose different reports according to their own application scenarios.
* BLE HID profile inheritance and USB HID class.
*/
/**
* Note:
* 1. Win10 does not support vendor report , So SUPPORT_REPORT_VENDOR is always set to FALSE, it defines in hidd_le_prf_int.h
* 2. Update connection parameters are not allowed during iPhone HID encryption, slave turns
* off the ability to automatically update connection parameters during encryption.
* 3. After our HID device is connected, the iPhones write 1 to the Report Characteristic Configuration Descriptor,
* even if the HID encryption is not completed. This should actually be written 1 after the HID encryption is completed.
* we modify the permissions of the Report Characteristic Configuration Descriptor to `ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED`.
* if you got `GATT_INSUF_ENCRYPTION` error, please ignore.
*/
#define HID_DEMO_TAG "HID_DEMO"
static uint16_t hid_conn_id = 0;
static bool sec_conn = false;
static bool send_volum_up = false;
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param);
#define HIDD_DEVICE_NAME "HID"
static uint8_t hidd_service_uuid128[] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
};
static esp_ble_adv_data_t hidd_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 = 0x03c0, //HID Generic,
.manufacturer_len = 0,
.p_manufacturer_data = NULL,
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = sizeof(hidd_service_uuid128),
.p_service_uuid = hidd_service_uuid128,
.flag = 0x6,
};
static esp_ble_adv_params_t hidd_adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x30,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
//.peer_addr =
//.peer_addr_type =
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param)
{
switch(event) {
case ESP_HIDD_EVENT_REG_FINISH: {
if (param->init_finish.state == ESP_HIDD_INIT_OK) {
//esp_bd_addr_t rand_addr = {0x04,0x11,0x11,0x11,0x11,0x05};
esp_ble_gap_set_device_name(HIDD_DEVICE_NAME);
esp_ble_gap_config_adv_data(&hidd_adv_data);
}
break;
}
case ESP_BAT_EVENT_REG: {
break;
}
case ESP_HIDD_EVENT_DEINIT_FINISH:
break;
case ESP_HIDD_EVENT_BLE_CONNECT: {
ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_CONNECT");
hid_conn_id = param->connect.conn_id;
break;
}
case ESP_HIDD_EVENT_BLE_DISCONNECT: {
sec_conn = false;
ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_DISCONNECT");
esp_ble_gap_start_advertising(&hidd_adv_params);
break;
}
case ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT: {
ESP_LOGI(HID_DEMO_TAG, "%s, ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT", __func__);
ESP_LOG_BUFFER_HEX(HID_DEMO_TAG, param->vendor_write.data, param->vendor_write.length);
}
default:
break;
}
return;
}
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&hidd_adv_params);
break;
case ESP_GAP_BLE_SEC_REQ_EVT:
for(int i = 0; i < ESP_BD_ADDR_LEN; i++) {
ESP_LOGD(HID_DEMO_TAG, "%x:",param->ble_security.ble_req.bd_addr[i]);
}
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
break;
case ESP_GAP_BLE_AUTH_CMPL_EVT:
sec_conn = true;
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(HID_DEMO_TAG, "remote BD_ADDR: %08x%04x",\
(bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
(bd_addr[4] << 8) + bd_addr[5]);
ESP_LOGI(HID_DEMO_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
ESP_LOGI(HID_DEMO_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail");
if(!param->ble_security.auth_cmpl.success) {
ESP_LOGE(HID_DEMO_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason);
}
break;
default:
break;
}
}
void hid_demo_task(void *pvParameters)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
while(1) {
vTaskDelay(2000 / portTICK_PERIOD_MS);
if (sec_conn) {
ESP_LOGI(HID_DEMO_TAG, "Send the volume");
send_volum_up = true;
//uint8_t key_vaule = {HID_KEY_A};
//esp_hidd_send_keyboard_value(hid_conn_id, 0, &key_vaule, 1);
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_UP, true);
vTaskDelay(3000 / portTICK_PERIOD_MS);
if (send_volum_up) {
send_volum_up = false;
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_UP, false);
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_DOWN, true);
vTaskDelay(3000 / portTICK_PERIOD_MS);
esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_DOWN, false);
}
}
}
}
void app_main(void)
{
esp_err_t ret;
// 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(HID_DEMO_TAG, "%s initialize controller failed\n", __func__);
return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(HID_DEMO_TAG, "%s enable controller failed\n", __func__);
return;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
return;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
return;
}
if((ret = esp_hidd_profile_init()) != ESP_OK) {
ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__);
}
///register the callback function to the gap module
esp_ble_gap_register_callback(gap_event_handler);
esp_hidd_register_callbacks(hidd_event_callback);
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; //bonding with peer device after authentication
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //set the IO capability to No output No input
uint8_t key_size = 16; //the key size should be 7~16 bytes
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
/* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
and the response key means which key you can distribute to the Master;
If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
and the init key means which key you can distribute to the slave. */
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
xTaskCreate(&hid_demo_task, "hid_task", 2048, NULL, 5, NULL);
}

View File

@@ -0,0 +1,5 @@
#
# Main Makefile. This is basically the same as a component makefile.
#
hid_device_le_prf.o: CFLAGS += -Wno-unused-const-variable

View File

@@ -0,0 +1,145 @@
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_hidd_prf_api.h"
#include "hidd_le_prf_int.h"
#include "hid_dev.h"
#include <stdlib.h>
#include <string.h>
#include "esp_log.h"
// HID keyboard input report length
#define HID_KEYBOARD_IN_RPT_LEN 8
// HID LED output report length
#define HID_LED_OUT_RPT_LEN 1
// HID mouse input report length
#define HID_MOUSE_IN_RPT_LEN 5
// HID consumer control input report length
#define HID_CC_IN_RPT_LEN 2
esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks)
{
esp_err_t hidd_status;
if(callbacks != NULL) {
hidd_le_env.hidd_cb = callbacks;
} else {
return ESP_FAIL;
}
if((hidd_status = hidd_register_cb()) != ESP_OK) {
return hidd_status;
}
esp_ble_gatts_app_register(BATTRAY_APP_ID);
if((hidd_status = esp_ble_gatts_app_register(HIDD_APP_ID)) != ESP_OK) {
return hidd_status;
}
return hidd_status;
}
esp_err_t esp_hidd_profile_init(void)
{
if (hidd_le_env.enabled) {
ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized");
return ESP_FAIL;
}
// Reset the hid device target environment
memset(&hidd_le_env, 0, sizeof(hidd_le_env_t));
hidd_le_env.enabled = true;
return ESP_OK;
}
esp_err_t esp_hidd_profile_deinit(void)
{
uint16_t hidd_svc_hdl = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC];
if (!hidd_le_env.enabled) {
ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized");
return ESP_OK;
}
if(hidd_svc_hdl != 0) {
esp_ble_gatts_stop_service(hidd_svc_hdl);
esp_ble_gatts_delete_service(hidd_svc_hdl);
} else {
return ESP_FAIL;
}
/* register the HID device profile to the BTA_GATTS module*/
esp_ble_gatts_app_unregister(hidd_le_env.gatt_if);
return ESP_OK;
}
uint16_t esp_hidd_get_version(void)
{
return HIDD_VERSION;
}
void esp_hidd_send_consumer_value(uint16_t conn_id, uint8_t key_cmd, bool key_pressed)
{
uint8_t buffer[HID_CC_IN_RPT_LEN] = {0, 0};
if (key_pressed) {
ESP_LOGD(HID_LE_PRF_TAG, "hid_consumer_build_report");
hid_consumer_build_report(buffer, key_cmd);
}
ESP_LOGD(HID_LE_PRF_TAG, "buffer[0] = %x, buffer[1] = %x", buffer[0], buffer[1]);
hid_dev_send_report(hidd_le_env.gatt_if, conn_id,
HID_RPT_ID_CC_IN, HID_REPORT_TYPE_INPUT, HID_CC_IN_RPT_LEN, buffer);
return;
}
void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask, uint8_t *keyboard_cmd, uint8_t num_key)
{
if (num_key > HID_KEYBOARD_IN_RPT_LEN - 2) {
ESP_LOGE(HID_LE_PRF_TAG, "%s(), the number key should not be more than %d", __func__, HID_KEYBOARD_IN_RPT_LEN);
return;
}
uint8_t buffer[HID_KEYBOARD_IN_RPT_LEN] = {0};
buffer[0] = special_key_mask;
for (int i = 0; i < num_key; i++) {
buffer[i+2] = keyboard_cmd[i];
}
ESP_LOGD(HID_LE_PRF_TAG, "the key vaule = %d,%d,%d, %d, %d, %d,%d, %d", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]);
hid_dev_send_report(hidd_le_env.gatt_if, conn_id,
HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT, HID_KEYBOARD_IN_RPT_LEN, buffer);
return;
}
void esp_hidd_send_mouse_value(uint16_t conn_id, uint8_t mouse_button, int8_t mickeys_x, int8_t mickeys_y)
{
uint8_t buffer[HID_MOUSE_IN_RPT_LEN];
buffer[0] = mouse_button; // Buttons
buffer[1] = mickeys_x; // X
buffer[2] = mickeys_y; // Y
buffer[3] = 0; // Wheel
buffer[4] = 0; // AC Pan
hid_dev_send_report(hidd_le_env.gatt_if, conn_id,
HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT, HID_MOUSE_IN_RPT_LEN, buffer);
return;
}

View File

@@ -0,0 +1,168 @@
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __ESP_HIDD_API_H__
#define __ESP_HIDD_API_H__
#include "esp_bt_defs.h"
#include "esp_gatt_defs.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
ESP_HIDD_EVENT_REG_FINISH = 0,
ESP_BAT_EVENT_REG,
ESP_HIDD_EVENT_DEINIT_FINISH,
ESP_HIDD_EVENT_BLE_CONNECT,
ESP_HIDD_EVENT_BLE_DISCONNECT,
ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT,
} esp_hidd_cb_event_t;
/// HID config status
typedef enum {
ESP_HIDD_STA_CONN_SUCCESS = 0x00,
ESP_HIDD_STA_CONN_FAIL = 0x01,
} esp_hidd_sta_conn_state_t;
/// HID init status
typedef enum {
ESP_HIDD_INIT_OK = 0,
ESP_HIDD_INIT_FAILED = 1,
} esp_hidd_init_state_t;
/// HID deinit status
typedef enum {
ESP_HIDD_DEINIT_OK = 0,
ESP_HIDD_DEINIT_FAILED = 0,
} esp_hidd_deinit_state_t;
#define LEFT_CONTROL_KEY_MASK (1 << 0)
#define LEFT_SHIFT_KEY_MASK (1 << 1)
#define LEFT_ALT_KEY_MASK (1 << 2)
#define LEFT_GUI_KEY_MASK (1 << 3)
#define RIGHT_CONTROL_KEY_MASK (1 << 4)
#define RIGHT_SHIFT_KEY_MASK (1 << 5)
#define RIGHT_ALT_KEY_MASK (1 << 6)
#define RIGHT_GUI_KEY_MASK (1 << 7)
typedef uint8_t key_mask_t;
/**
* @brief HIDD callback parameters union
*/
typedef union {
/**
* @brief ESP_HIDD_EVENT_INIT_FINISH
*/
struct hidd_init_finish_evt_param {
esp_hidd_init_state_t state; /*!< Initial status */
esp_gatt_if_t gatts_if;
} init_finish; /*!< HID callback param of ESP_HIDD_EVENT_INIT_FINISH */
/**
* @brief ESP_HIDD_EVENT_DEINIT_FINISH
*/
struct hidd_deinit_finish_evt_param {
esp_hidd_deinit_state_t state; /*!< De-initial status */
} deinit_finish; /*!< HID callback param of ESP_HIDD_EVENT_DEINIT_FINISH */
/**
* @brief ESP_HIDD_EVENT_CONNECT
*/
struct hidd_connect_evt_param {
uint16_t conn_id;
esp_bd_addr_t remote_bda; /*!< HID Remote bluetooth connection index */
} connect; /*!< HID callback param of ESP_HIDD_EVENT_CONNECT */
/**
* @brief ESP_HIDD_EVENT_DISCONNECT
*/
struct hidd_disconnect_evt_param {
esp_bd_addr_t remote_bda; /*!< HID Remote bluetooth device address */
} disconnect; /*!< HID callback param of ESP_HIDD_EVENT_DISCONNECT */
/**
* @brief ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT
*/
struct hidd_vendor_write_evt_param {
uint16_t conn_id; /*!< HID connection index */
uint16_t report_id; /*!< HID report index */
uint16_t length; /*!< data length */
uint8_t *data; /*!< The pointer to the data */
} vendor_write; /*!< HID callback param of ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT */
} esp_hidd_cb_param_t;
/**
* @brief HID device event callback function type
* @param event : Event type
* @param param : Point to callback parameter, currently is union type
*/
typedef void (*esp_hidd_event_cb_t) (esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param);
/**
*
* @brief This function is called to receive hid device callback event
*
* @param[in] callbacks: callback functions
*
* @return ESP_OK - success, other - failed
*
*/
esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks);
/**
*
* @brief This function is called to initialize hid device profile
*
* @return ESP_OK - success, other - failed
*
*/
esp_err_t esp_hidd_profile_init(void);
/**
*
* @brief This function is called to de-initialize hid device profile
*
* @return ESP_OK - success, other - failed
*
*/
esp_err_t esp_hidd_profile_deinit(void);
/**
*
* @brief Get hidd profile version
*
* @return Most 8bit significant is Great version, Least 8bit is Sub version
*
*/
uint16_t esp_hidd_get_version(void);
void esp_hidd_send_consumer_value(uint16_t conn_id, uint8_t key_cmd, bool key_pressed);
void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask, uint8_t *keyboard_cmd, uint8_t num_key);
void esp_hidd_send_mouse_value(uint16_t conn_id, uint8_t mouse_button, int8_t mickeys_x, int8_t mickeys_y);
#ifdef __cplusplus
}
#endif
#endif /* __ESP_HIDD_API_H__ */

View File

@@ -0,0 +1,138 @@
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "hid_dev.h"
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "esp_log.h"
static hid_report_map_t *hid_dev_rpt_tbl;
static uint8_t hid_dev_rpt_tbl_Len;
static hid_report_map_t *hid_dev_rpt_by_id(uint8_t id, uint8_t type)
{
hid_report_map_t *rpt = hid_dev_rpt_tbl;
for (uint8_t i = hid_dev_rpt_tbl_Len; i > 0; i--, rpt++) {
if (rpt->id == id && rpt->type == type && rpt->mode == hidProtocolMode) {
return rpt;
}
}
return NULL;
}
void hid_dev_register_reports(uint8_t num_reports, hid_report_map_t *p_report)
{
hid_dev_rpt_tbl = p_report;
hid_dev_rpt_tbl_Len = num_reports;
return;
}
void hid_dev_send_report(esp_gatt_if_t gatts_if, uint16_t conn_id,
uint8_t id, uint8_t type, uint8_t length, uint8_t *data)
{
hid_report_map_t *p_rpt;
// get att handle for report
if ((p_rpt = hid_dev_rpt_by_id(id, type)) != NULL) {
// if notifications are enabled
ESP_LOGD(HID_LE_PRF_TAG, "%s(), send the report, handle = %d", __func__, p_rpt->handle);
esp_ble_gatts_send_indicate(gatts_if, conn_id, p_rpt->handle, length, data, false);
}
return;
}
void hid_consumer_build_report(uint8_t *buffer, consumer_cmd_t cmd)
{
if (!buffer) {
ESP_LOGE(HID_LE_PRF_TAG, "%s(), the buffer is NULL, hid build report failed.", __func__);
return;
}
switch (cmd) {
case HID_CONSUMER_CHANNEL_UP:
HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_UP);
break;
case HID_CONSUMER_CHANNEL_DOWN:
HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_DOWN);
break;
case HID_CONSUMER_VOLUME_UP:
HID_CC_RPT_SET_VOLUME_UP(buffer);
break;
case HID_CONSUMER_VOLUME_DOWN:
HID_CC_RPT_SET_VOLUME_DOWN(buffer);
break;
case HID_CONSUMER_MUTE:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_MUTE);
break;
case HID_CONSUMER_POWER:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_POWER);
break;
case HID_CONSUMER_RECALL_LAST:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_LAST);
break;
case HID_CONSUMER_ASSIGN_SEL:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_ASSIGN_SEL);
break;
case HID_CONSUMER_PLAY:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PLAY);
break;
case HID_CONSUMER_PAUSE:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PAUSE);
break;
case HID_CONSUMER_RECORD:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_RECORD);
break;
case HID_CONSUMER_FAST_FORWARD:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_FAST_FWD);
break;
case HID_CONSUMER_REWIND:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_REWIND);
break;
case HID_CONSUMER_SCAN_NEXT_TRK:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_NEXT_TRK);
break;
case HID_CONSUMER_SCAN_PREV_TRK:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_PREV_TRK);
break;
case HID_CONSUMER_STOP:
HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_STOP);
break;
default:
break;
}
return;
}

View File

@@ -0,0 +1,261 @@
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef HID_DEV_H__
#define HID_DEV_H__
#include "hidd_le_prf_int.h"
#ifdef __cplusplus
extern "C" {
#endif
/* HID Report type */
#define HID_TYPE_INPUT 1
#define HID_TYPE_OUTPUT 2
#define HID_TYPE_FEATURE 3
// HID Keyboard/Keypad Usage IDs (subset of the codes available in the USB HID Usage Tables spec)
#define HID_KEY_RESERVED 0 // No event inidicated
#define HID_KEY_A 4 // Keyboard a and A
#define HID_KEY_B 5 // Keyboard b and B
#define HID_KEY_C 6 // Keyboard c and C
#define HID_KEY_D 7 // Keyboard d and D
#define HID_KEY_E 8 // Keyboard e and E
#define HID_KEY_F 9 // Keyboard f and F
#define HID_KEY_G 10 // Keyboard g and G
#define HID_KEY_H 11 // Keyboard h and H
#define HID_KEY_I 12 // Keyboard i and I
#define HID_KEY_J 13 // Keyboard j and J
#define HID_KEY_K 14 // Keyboard k and K
#define HID_KEY_L 15 // Keyboard l and L
#define HID_KEY_M 16 // Keyboard m and M
#define HID_KEY_N 17 // Keyboard n and N
#define HID_KEY_O 18 // Keyboard o and O
#define HID_KEY_P 19 // Keyboard p and p
#define HID_KEY_Q 20 // Keyboard q and Q
#define HID_KEY_R 21 // Keyboard r and R
#define HID_KEY_S 22 // Keyboard s and S
#define HID_KEY_T 23 // Keyboard t and T
#define HID_KEY_U 24 // Keyboard u and U
#define HID_KEY_V 25 // Keyboard v and V
#define HID_KEY_W 26 // Keyboard w and W
#define HID_KEY_X 27 // Keyboard x and X
#define HID_KEY_Y 28 // Keyboard y and Y
#define HID_KEY_Z 29 // Keyboard z and Z
#define HID_KEY_1 30 // Keyboard 1 and !
#define HID_KEY_2 31 // Keyboard 2 and @
#define HID_KEY_3 32 // Keyboard 3 and #
#define HID_KEY_4 33 // Keyboard 4 and %
#define HID_KEY_5 34 // Keyboard 5 and %
#define HID_KEY_6 35 // Keyboard 6 and ^
#define HID_KEY_7 36 // Keyboard 7 and &
#define HID_KEY_8 37 // Keyboard 8 and *
#define HID_KEY_9 38 // Keyboard 9 and (
#define HID_KEY_0 39 // Keyboard 0 and )
#define HID_KEY_RETURN 40 // Keyboard Return (ENTER)
#define HID_KEY_ESCAPE 41 // Keyboard ESCAPE
#define HID_KEY_DELETE 42 // Keyboard DELETE (Backspace)
#define HID_KEY_TAB 43 // Keyboard Tab
#define HID_KEY_SPACEBAR 44 // Keyboard Spacebar
#define HID_KEY_MINUS 45 // Keyboard - and (underscore)
#define HID_KEY_EQUAL 46 // Keyboard = and +
#define HID_KEY_LEFT_BRKT 47 // Keyboard [ and {
#define HID_KEY_RIGHT_BRKT 48 // Keyboard ] and }
#define HID_KEY_BACK_SLASH 49 // Keyboard \ and |
#define HID_KEY_SEMI_COLON 51 // Keyboard ; and :
#define HID_KEY_SGL_QUOTE 52 // Keyboard ' and "
#define HID_KEY_GRV_ACCENT 53 // Keyboard Grave Accent and Tilde
#define HID_KEY_COMMA 54 // Keyboard , and <
#define HID_KEY_DOT 55 // Keyboard . and >
#define HID_KEY_FWD_SLASH 56 // Keyboard / and ?
#define HID_KEY_CAPS_LOCK 57 // Keyboard Caps Lock
#define HID_KEY_F1 58 // Keyboard F1
#define HID_KEY_F2 59 // Keyboard F2
#define HID_KEY_F3 60 // Keyboard F3
#define HID_KEY_F4 61 // Keyboard F4
#define HID_KEY_F5 62 // Keyboard F5
#define HID_KEY_F6 63 // Keyboard F6
#define HID_KEY_F7 64 // Keyboard F7
#define HID_KEY_F8 65 // Keyboard F8
#define HID_KEY_F9 66 // Keyboard F9
#define HID_KEY_F10 67 // Keyboard F10
#define HID_KEY_F11 68 // Keyboard F11
#define HID_KEY_F12 69 // Keyboard F12
#define HID_KEY_PRNT_SCREEN 70 // Keyboard Print Screen
#define HID_KEY_SCROLL_LOCK 71 // Keyboard Scroll Lock
#define HID_KEY_PAUSE 72 // Keyboard Pause
#define HID_KEY_INSERT 73 // Keyboard Insert
#define HID_KEY_HOME 74 // Keyboard Home
#define HID_KEY_PAGE_UP 75 // Keyboard PageUp
#define HID_KEY_DELETE_FWD 76 // Keyboard Delete Forward
#define HID_KEY_END 77 // Keyboard End
#define HID_KEY_PAGE_DOWN 78 // Keyboard PageDown
#define HID_KEY_RIGHT_ARROW 79 // Keyboard RightArrow
#define HID_KEY_LEFT_ARROW 80 // Keyboard LeftArrow
#define HID_KEY_DOWN_ARROW 81 // Keyboard DownArrow
#define HID_KEY_UP_ARROW 82 // Keyboard UpArrow
#define HID_KEY_NUM_LOCK 83 // Keypad Num Lock and Clear
#define HID_KEY_DIVIDE 84 // Keypad /
#define HID_KEY_MULTIPLY 85 // Keypad *
#define HID_KEY_SUBTRACT 86 // Keypad -
#define HID_KEY_ADD 87 // Keypad +
#define HID_KEY_ENTER 88 // Keypad ENTER
#define HID_KEYPAD_1 89 // Keypad 1 and End
#define HID_KEYPAD_2 90 // Keypad 2 and Down Arrow
#define HID_KEYPAD_3 91 // Keypad 3 and PageDn
#define HID_KEYPAD_4 92 // Keypad 4 and Lfet Arrow
#define HID_KEYPAD_5 93 // Keypad 5
#define HID_KEYPAD_6 94 // Keypad 6 and Right Arrow
#define HID_KEYPAD_7 95 // Keypad 7 and Home
#define HID_KEYPAD_8 96 // Keypad 8 and Up Arrow
#define HID_KEYPAD_9 97 // Keypad 9 and PageUp
#define HID_KEYPAD_0 98 // Keypad 0 and Insert
#define HID_KEYPAD_DOT 99 // Keypad . and Delete
#define HID_KEY_MUTE 127 // Keyboard Mute
#define HID_KEY_VOLUME_UP 128 // Keyboard Volume up
#define HID_KEY_VOLUME_DOWN 129 // Keyboard Volume down
#define HID_KEY_LEFT_CTRL 224 // Keyboard LeftContorl
#define HID_KEY_LEFT_SHIFT 225 // Keyboard LeftShift
#define HID_KEY_LEFT_ALT 226 // Keyboard LeftAlt
#define HID_KEY_LEFT_GUI 227 // Keyboard LeftGUI
#define HID_KEY_RIGHT_CTRL 228 // Keyboard LeftContorl
#define HID_KEY_RIGHT_SHIFT 229 // Keyboard LeftShift
#define HID_KEY_RIGHT_ALT 230 // Keyboard LeftAlt
#define HID_KEY_RIGHT_GUI 231 // Keyboard RightGUI
typedef uint8_t keyboard_cmd_t;
#define HID_MOUSE_LEFT 253
#define HID_MOUSE_MIDDLE 254
#define HID_MOUSE_RIGHT 255
typedef uint8_t mouse_cmd_t;
// HID Consumer Usage IDs (subset of the codes available in the USB HID Usage Tables spec)
#define HID_CONSUMER_POWER 48 // Power
#define HID_CONSUMER_RESET 49 // Reset
#define HID_CONSUMER_SLEEP 50 // Sleep
#define HID_CONSUMER_MENU 64 // Menu
#define HID_CONSUMER_SELECTION 128 // Selection
#define HID_CONSUMER_ASSIGN_SEL 129 // Assign Selection
#define HID_CONSUMER_MODE_STEP 130 // Mode Step
#define HID_CONSUMER_RECALL_LAST 131 // Recall Last
#define HID_CONSUMER_QUIT 148 // Quit
#define HID_CONSUMER_HELP 149 // Help
#define HID_CONSUMER_CHANNEL_UP 156 // Channel Increment
#define HID_CONSUMER_CHANNEL_DOWN 157 // Channel Decrement
#define HID_CONSUMER_PLAY 176 // Play
#define HID_CONSUMER_PAUSE 177 // Pause
#define HID_CONSUMER_RECORD 178 // Record
#define HID_CONSUMER_FAST_FORWARD 179 // Fast Forward
#define HID_CONSUMER_REWIND 180 // Rewind
#define HID_CONSUMER_SCAN_NEXT_TRK 181 // Scan Next Track
#define HID_CONSUMER_SCAN_PREV_TRK 182 // Scan Previous Track
#define HID_CONSUMER_STOP 183 // Stop
#define HID_CONSUMER_EJECT 184 // Eject
#define HID_CONSUMER_RANDOM_PLAY 185 // Random Play
#define HID_CONSUMER_SELECT_DISC 186 // Select Disk
#define HID_CONSUMER_ENTER_DISC 187 // Enter Disc
#define HID_CONSUMER_REPEAT 188 // Repeat
#define HID_CONSUMER_STOP_EJECT 204 // Stop/Eject
#define HID_CONSUMER_PLAY_PAUSE 205 // Play/Pause
#define HID_CONSUMER_PLAY_SKIP 206 // Play/Skip
#define HID_CONSUMER_VOLUME 224 // Volume
#define HID_CONSUMER_BALANCE 225 // Balance
#define HID_CONSUMER_MUTE 226 // Mute
#define HID_CONSUMER_BASS 227 // Bass
#define HID_CONSUMER_VOLUME_UP 233 // Volume Increment
#define HID_CONSUMER_VOLUME_DOWN 234 // Volume Decrement
typedef uint8_t consumer_cmd_t;
#define HID_CC_RPT_MUTE 1
#define HID_CC_RPT_POWER 2
#define HID_CC_RPT_LAST 3
#define HID_CC_RPT_ASSIGN_SEL 4
#define HID_CC_RPT_PLAY 5
#define HID_CC_RPT_PAUSE 6
#define HID_CC_RPT_RECORD 7
#define HID_CC_RPT_FAST_FWD 8
#define HID_CC_RPT_REWIND 9
#define HID_CC_RPT_SCAN_NEXT_TRK 10
#define HID_CC_RPT_SCAN_PREV_TRK 11
#define HID_CC_RPT_STOP 12
#define HID_CC_RPT_CHANNEL_UP 0x01
#define HID_CC_RPT_CHANNEL_DOWN 0x03
#define HID_CC_RPT_VOLUME_UP 0x40
#define HID_CC_RPT_VOLUME_DOWN 0x80
// HID Consumer Control report bitmasks
#define HID_CC_RPT_NUMERIC_BITS 0xF0
#define HID_CC_RPT_CHANNEL_BITS 0xCF
#define HID_CC_RPT_VOLUME_BITS 0x3F
#define HID_CC_RPT_BUTTON_BITS 0xF0
#define HID_CC_RPT_SELECTION_BITS 0xCF
// Macros for the HID Consumer Control 2-byte report
#define HID_CC_RPT_SET_NUMERIC(s, x) (s)[0] &= HID_CC_RPT_NUMERIC_BITS; \
(s)[0] = (x)
#define HID_CC_RPT_SET_CHANNEL(s, x) (s)[0] &= HID_CC_RPT_CHANNEL_BITS; \
(s)[0] |= ((x) & 0x03) << 4
#define HID_CC_RPT_SET_VOLUME_UP(s) (s)[0] &= HID_CC_RPT_VOLUME_BITS; \
(s)[0] |= 0x40
#define HID_CC_RPT_SET_VOLUME_DOWN(s) (s)[0] &= HID_CC_RPT_VOLUME_BITS; \
(s)[0] |= 0x80
#define HID_CC_RPT_SET_BUTTON(s, x) (s)[1] &= HID_CC_RPT_BUTTON_BITS; \
(s)[1] |= (x)
#define HID_CC_RPT_SET_SELECTION(s, x) (s)[1] &= HID_CC_RPT_SELECTION_BITS; \
(s)[1] |= ((x) & 0x03) << 4
// HID report mapping table
typedef struct
{
uint16_t handle; // Handle of report characteristic
uint16_t cccdHandle; // Handle of CCCD for report characteristic
uint8_t id; // Report ID
uint8_t type; // Report type
uint8_t mode; // Protocol mode (report or boot)
} hid_report_map_t;
// HID dev configuration structure
typedef struct
{
uint32_t idleTimeout; // Idle timeout in milliseconds
uint8_t hidFlags; // HID feature flags
} hid_dev_cfg_t;
void hid_dev_register_reports(uint8_t num_reports, hid_report_map_t *p_report);
void hid_dev_send_report(esp_gatt_if_t gatts_if, uint16_t conn_id,
uint8_t id, uint8_t type, uint8_t length, uint8_t *data);
void hid_consumer_build_report(uint8_t *buffer, consumer_cmd_t cmd);
void hid_keyboard_build_report(uint8_t *buffer, keyboard_cmd_t cmd);
void hid_mouse_build_report(uint8_t *buffer, mouse_cmd_t cmd);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* HID_DEV_H__ */

View File

@@ -0,0 +1,816 @@
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "hidd_le_prf_int.h"
#include <string.h>
#include "esp_log.h"
/// characteristic presentation information
struct prf_char_pres_fmt
{
/// Unit (The Unit is a UUID)
uint16_t unit;
/// Description
uint16_t description;
/// Format
uint8_t format;
/// Exponent
uint8_t exponent;
/// Name space
uint8_t name_space;
};
// HID report mapping table
static hid_report_map_t hid_rpt_map[HID_NUM_REPORTS];
// HID Report Map characteristic value
// Keyboard report descriptor (using format for Boot interface descriptor)
static const uint8_t hidReportMap[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report Id (1)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Buttons)
0x19, 0x01, // Usage Minimum (01) - Button 1
0x29, 0x03, // Usage Maximum (03) - Button 3
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x03, // Report Count (3)
0x81, 0x02, // Input (Data, Variable, Absolute) - Button states
0x75, 0x05, // Report Size (5)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Constant) - Padding or Reserved bits
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x38, // Usage (Wheel)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x03, // Report Count (3)
0x81, 0x06, // Input (Data, Variable, Relative) - X & Y coordinate
0xC0, // End Collection
0xC0, // End Collection
0x05, 0x01, // Usage Pg (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection: (Application)
0x85, 0x02, // Report Id (2)
//
0x05, 0x07, // Usage Pg (Key Codes)
0x19, 0xE0, // Usage Min (224)
0x29, 0xE7, // Usage Max (231)
0x15, 0x00, // Log Min (0)
0x25, 0x01, // Log Max (1)
//
// Modifier byte
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input: (Data, Variable, Absolute)
//
// Reserved byte
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input: (Constant)
//
// LED report
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Pg (LEDs)
0x19, 0x01, // Usage Min (1)
0x29, 0x05, // Usage Max (5)
0x91, 0x02, // Output: (Data, Variable, Absolute)
//
// LED report padding
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output: (Constant)
//
// Key arrays (6 bytes)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Log Min (0)
0x25, 0x65, // Log Max (101)
0x05, 0x07, // Usage Pg (Key Codes)
0x19, 0x00, // Usage Min (0)
0x29, 0x65, // Usage Max (101)
0x81, 0x00, // Input: (Data, Array)
//
0xC0, // End Collection
//
0x05, 0x0C, // Usage Pg (Consumer Devices)
0x09, 0x01, // Usage (Consumer Control)
0xA1, 0x01, // Collection (Application)
0x85, 0x03, // Report Id (3)
0x09, 0x02, // Usage (Numeric Key Pad)
0xA1, 0x02, // Collection (Logical)
0x05, 0x09, // Usage Pg (Button)
0x19, 0x01, // Usage Min (Button 1)
0x29, 0x0A, // Usage Max (Button 10)
0x15, 0x01, // Logical Min (1)
0x25, 0x0A, // Logical Max (10)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x00, // Input (Data, Ary, Abs)
0xC0, // End Collection
0x05, 0x0C, // Usage Pg (Consumer Devices)
0x09, 0x86, // Usage (Channel)
0x15, 0xFF, // Logical Min (-1)
0x25, 0x01, // Logical Max (1)
0x75, 0x02, // Report Size (2)
0x95, 0x01, // Report Count (1)
0x81, 0x46, // Input (Data, Var, Rel, Null)
0x09, 0xE9, // Usage (Volume Up)
0x09, 0xEA, // Usage (Volume Down)
0x15, 0x00, // Logical Min (0)
0x75, 0x01, // Report Size (1)
0x95, 0x02, // Report Count (2)
0x81, 0x02, // Input (Data, Var, Abs)
0x09, 0xE2, // Usage (Mute)
0x09, 0x30, // Usage (Power)
0x09, 0x83, // Usage (Recall Last)
0x09, 0x81, // Usage (Assign Selection)
0x09, 0xB0, // Usage (Play)
0x09, 0xB1, // Usage (Pause)
0x09, 0xB2, // Usage (Record)
0x09, 0xB3, // Usage (Fast Forward)
0x09, 0xB4, // Usage (Rewind)
0x09, 0xB5, // Usage (Scan Next)
0x09, 0xB6, // Usage (Scan Prev)
0x09, 0xB7, // Usage (Stop)
0x15, 0x01, // Logical Min (1)
0x25, 0x0C, // Logical Max (12)
0x75, 0x04, // Report Size (4)
0x95, 0x01, // Report Count (1)
0x81, 0x00, // Input (Data, Ary, Abs)
0x09, 0x80, // Usage (Selection)
0xA1, 0x02, // Collection (Logical)
0x05, 0x09, // Usage Pg (Button)
0x19, 0x01, // Usage Min (Button 1)
0x29, 0x03, // Usage Max (Button 3)
0x15, 0x01, // Logical Min (1)
0x25, 0x03, // Logical Max (3)
0x75, 0x02, // Report Size (2)
0x81, 0x00, // Input (Data, Ary, Abs)
0xC0, // End Collection
0x81, 0x03, // Input (Const, Var, Abs)
0xC0, // End Collectionq
#if (SUPPORT_REPORT_VENDOR == true)
0x06, 0xFF, 0xFF, // Usage Page(Vendor defined)
0x09, 0xA5, // Usage(Vendor Defined)
0xA1, 0x01, // Collection(Application)
0x85, 0x04, // Report Id (4)
0x09, 0xA6, // Usage(Vendor defined)
0x09, 0xA9, // Usage(Vendor defined)
0x75, 0x08, // Report Size
0x95, 0x7F, // Report Count = 127 Btyes
0x91, 0x02, // Output(Data, Variable, Absolute)
0xC0, // End Collection
#endif
};
/// Battery Service Attributes Indexes
enum
{
BAS_IDX_SVC,
BAS_IDX_BATT_LVL_CHAR,
BAS_IDX_BATT_LVL_VAL,
BAS_IDX_BATT_LVL_NTF_CFG,
BAS_IDX_BATT_LVL_PRES_FMT,
BAS_IDX_NB,
};
#define HI_UINT16(a) (((a) >> 8) & 0xFF)
#define LO_UINT16(a) ((a) & 0xFF)
#define PROFILE_NUM 1
#define PROFILE_APP_IDX 0
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb;
uint16_t gatts_if;
uint16_t app_id;
uint16_t conn_id;
};
hidd_le_env_t hidd_le_env;
// HID report map length
uint8_t hidReportMapLen = sizeof(hidReportMap);
uint8_t hidProtocolMode = HID_PROTOCOL_MODE_REPORT;
// HID report mapping table
//static hidRptMap_t hidRptMap[HID_NUM_REPORTS];
// HID Information characteristic value
static const uint8_t hidInfo[HID_INFORMATION_LEN] = {
LO_UINT16(0x0111), HI_UINT16(0x0111), // bcdHID (USB HID version)
0x00, // bCountryCode
HID_KBD_FLAGS // Flags
};
// HID External Report Reference Descriptor
static uint16_t hidExtReportRefDesc = ESP_GATT_UUID_BATTERY_LEVEL;
// HID Report Reference characteristic descriptor, mouse input
static uint8_t hidReportRefMouseIn[HID_REPORT_REF_LEN] =
{ HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT };
// HID Report Reference characteristic descriptor, key input
static uint8_t hidReportRefKeyIn[HID_REPORT_REF_LEN] =
{ HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT };
// HID Report Reference characteristic descriptor, LED output
static uint8_t hidReportRefLedOut[HID_REPORT_REF_LEN] =
{ HID_RPT_ID_LED_OUT, HID_REPORT_TYPE_OUTPUT };
#if (SUPPORT_REPORT_VENDOR == true)
static uint8_t hidReportRefVendorOut[HID_REPORT_REF_LEN] =
{HID_RPT_ID_VENDOR_OUT, HID_REPORT_TYPE_OUTPUT};
#endif
// HID Report Reference characteristic descriptor, Feature
static uint8_t hidReportRefFeature[HID_REPORT_REF_LEN] =
{ HID_RPT_ID_FEATURE, HID_REPORT_TYPE_FEATURE };
// HID Report Reference characteristic descriptor, consumer control input
static uint8_t hidReportRefCCIn[HID_REPORT_REF_LEN] =
{ HID_RPT_ID_CC_IN, HID_REPORT_TYPE_INPUT };
/*
* Heart Rate PROFILE ATTRIBUTES
****************************************************************************************
*/
/// hid Service uuid
static uint16_t hid_le_svc = ATT_SVC_HID;
uint16_t hid_count = 0;
esp_gatts_incl_svc_desc_t incl_svc = {0};
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
///the uuid definition
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t include_service_uuid = ESP_GATT_UUID_INCLUDE_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 uint16_t hid_info_char_uuid = ESP_GATT_UUID_HID_INFORMATION;
static const uint16_t hid_report_map_uuid = ESP_GATT_UUID_HID_REPORT_MAP;
static const uint16_t hid_control_point_uuid = ESP_GATT_UUID_HID_CONTROL_POINT;
static const uint16_t hid_report_uuid = ESP_GATT_UUID_HID_REPORT;
static const uint16_t hid_proto_mode_uuid = ESP_GATT_UUID_HID_PROTO_MODE;
static const uint16_t hid_kb_input_uuid = ESP_GATT_UUID_HID_BT_KB_INPUT;
static const uint16_t hid_kb_output_uuid = ESP_GATT_UUID_HID_BT_KB_OUTPUT;
static const uint16_t hid_mouse_input_uuid = ESP_GATT_UUID_HID_BT_MOUSE_INPUT;
static const uint16_t hid_repot_map_ext_desc_uuid = ESP_GATT_UUID_EXT_RPT_REF_DESCR;
static const uint16_t hid_report_ref_descr_uuid = ESP_GATT_UUID_RPT_REF_DESCR;
///the propoty definition
static const uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
static const uint8_t char_prop_write_nr = ESP_GATT_CHAR_PROP_BIT_WRITE_NR;
static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_READ;
static const uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
/// battary Service
static const uint16_t battary_svc = ESP_GATT_UUID_BATTERY_SERVICE_SVC;
static const uint16_t bat_lev_uuid = ESP_GATT_UUID_BATTERY_LEVEL;
static const uint8_t bat_lev_ccc[2] ={ 0x00, 0x00};
static const uint16_t char_format_uuid = ESP_GATT_UUID_CHAR_PRESENT_FORMAT;
static uint8_t battary_lev = 50;
/// Full HRS Database Description - Used to add attributes into the database
static const esp_gatts_attr_db_t bas_att_db[BAS_IDX_NB] =
{
// Battary Service Declaration
[BAS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(battary_svc), (uint8_t *)&battary_svc}},
// Battary level Characteristic Declaration
[BAS_IDX_BATT_LVL_CHAR] = {{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}},
// Battary level Characteristic Value
[BAS_IDX_BATT_LVL_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&bat_lev_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t),sizeof(uint8_t), &battary_lev}},
// Battary level Characteristic - Client Characteristic Configuration Descriptor
[BAS_IDX_BATT_LVL_NTF_CFG] = {{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(bat_lev_ccc), (uint8_t *)bat_lev_ccc}},
// Battary level report Characteristic Declaration
[BAS_IDX_BATT_LVL_PRES_FMT] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&char_format_uuid, ESP_GATT_PERM_READ,
sizeof(struct prf_char_pres_fmt), 0, NULL}},
};
/// Full Hid device Database Description - Used to add attributes into the database
static esp_gatts_attr_db_t hidd_le_gatt_db[HIDD_LE_IDX_NB] =
{
// HID Service Declaration
[HIDD_LE_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid,
ESP_GATT_PERM_READ_ENCRYPTED, sizeof(uint16_t), sizeof(hid_le_svc),
(uint8_t *)&hid_le_svc}},
// HID Service Declaration
[HIDD_LE_IDX_INCL_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&include_service_uuid,
ESP_GATT_PERM_READ,
sizeof(esp_gatts_incl_svc_desc_t), sizeof(esp_gatts_incl_svc_desc_t),
(uint8_t *)&incl_svc}},
// HID Information Characteristic Declaration
[HIDD_LE_IDX_HID_INFO_CHAR] = {{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}},
// HID Information Characteristic Value
[HIDD_LE_IDX_HID_INFO_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_info_char_uuid,
ESP_GATT_PERM_READ,
sizeof(hids_hid_info_t), sizeof(hidInfo),
(uint8_t *)&hidInfo}},
// HID Control Point Characteristic Declaration
[HIDD_LE_IDX_HID_CTNL_PT_CHAR] = {{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_nr}},
// HID Control Point Characteristic Value
[HIDD_LE_IDX_HID_CTNL_PT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_control_point_uuid,
ESP_GATT_PERM_WRITE,
sizeof(uint8_t), 0,
NULL}},
// Report Map Characteristic Declaration
[HIDD_LE_IDX_REPORT_MAP_CHAR] = {{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}},
// Report Map Characteristic Value
[HIDD_LE_IDX_REPORT_MAP_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_map_uuid,
ESP_GATT_PERM_READ,
HIDD_LE_REPORT_MAP_MAX_LEN, sizeof(hidReportMap),
(uint8_t *)&hidReportMap}},
// Report Map Characteristic - External Report Reference Descriptor
[HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_repot_map_ext_desc_uuid,
ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(uint16_t),
(uint8_t *)&hidExtReportRefDesc}},
// Protocol Mode Characteristic Declaration
[HIDD_LE_IDX_PROTO_MODE_CHAR] = {{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}},
// Protocol Mode Characteristic Value
[HIDD_LE_IDX_PROTO_MODE_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_proto_mode_uuid,
(ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE),
sizeof(uint8_t), sizeof(hidProtocolMode),
(uint8_t *)&hidProtocolMode}},
[HIDD_LE_IDX_REPORT_MOUSE_IN_CHAR] = {{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}},
[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
ESP_GATT_PERM_READ,
HIDD_LE_REPORT_MAX_LEN, 0,
NULL}},
[HIDD_LE_IDX_REPORT_MOUSE_IN_CCC] = {{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), 0,
NULL}},
[HIDD_LE_IDX_REPORT_MOUSE_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
ESP_GATT_PERM_READ,
sizeof(hidReportRefMouseIn), sizeof(hidReportRefMouseIn),
hidReportRefMouseIn}},
// Report Characteristic Declaration
[HIDD_LE_IDX_REPORT_KEY_IN_CHAR] = {{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}},
// Report Characteristic Value
[HIDD_LE_IDX_REPORT_KEY_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
ESP_GATT_PERM_READ,
HIDD_LE_REPORT_MAX_LEN, 0,
NULL}},
// Report KEY INPUT Characteristic - Client Characteristic Configuration Descriptor
[HIDD_LE_IDX_REPORT_KEY_IN_CCC] = {{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), 0,
NULL}},
// Report Characteristic - Report Reference Descriptor
[HIDD_LE_IDX_REPORT_KEY_IN_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
ESP_GATT_PERM_READ,
sizeof(hidReportRefKeyIn), sizeof(hidReportRefKeyIn),
hidReportRefKeyIn}},
// Report Characteristic Declaration
[HIDD_LE_IDX_REPORT_LED_OUT_CHAR] = {{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}},
[HIDD_LE_IDX_REPORT_LED_OUT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
HIDD_LE_REPORT_MAX_LEN, 0,
NULL}},
[HIDD_LE_IDX_REPORT_LED_OUT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
ESP_GATT_PERM_READ,
sizeof(hidReportRefLedOut), sizeof(hidReportRefLedOut),
hidReportRefLedOut}},
#if (SUPPORT_REPORT_VENDOR == true)
// Report Characteristic Declaration
[HIDD_LE_IDX_REPORT_VENDOR_OUT_CHAR] = {{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}},
[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
HIDD_LE_REPORT_MAX_LEN, 0,
NULL}},
[HIDD_LE_IDX_REPORT_VENDOR_OUT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
ESP_GATT_PERM_READ,
sizeof(hidReportRefVendorOut), sizeof(hidReportRefVendorOut),
hidReportRefVendorOut}},
#endif
// Report Characteristic Declaration
[HIDD_LE_IDX_REPORT_CC_IN_CHAR] = {{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}},
// Report Characteristic Value
[HIDD_LE_IDX_REPORT_CC_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
ESP_GATT_PERM_READ,
HIDD_LE_REPORT_MAX_LEN, 0,
NULL}},
// Report KEY INPUT Characteristic - Client Characteristic Configuration Descriptor
[HIDD_LE_IDX_REPORT_CC_IN_CCC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid,
(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED),
sizeof(uint16_t), 0,
NULL}},
// Report Characteristic - Report Reference Descriptor
[HIDD_LE_IDX_REPORT_CC_IN_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
ESP_GATT_PERM_READ,
sizeof(hidReportRefCCIn), sizeof(hidReportRefCCIn),
hidReportRefCCIn}},
// Boot Keyboard Input Report Characteristic Declaration
[HIDD_LE_IDX_BOOT_KB_IN_REPORT_CHAR] = {{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}},
// Boot Keyboard Input Report Characteristic Value
[HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_kb_input_uuid,
ESP_GATT_PERM_READ,
HIDD_LE_BOOT_REPORT_MAX_LEN, 0,
NULL}},
// Boot Keyboard Input Report Characteristic - Client Characteristic Configuration Descriptor
[HIDD_LE_IDX_BOOT_KB_IN_REPORT_NTF_CFG] = {{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), 0,
NULL}},
// Boot Keyboard Output Report Characteristic Declaration
[HIDD_LE_IDX_BOOT_KB_OUT_REPORT_CHAR] = {{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}},
// Boot Keyboard Output Report Characteristic Value
[HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_kb_output_uuid,
(ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE),
HIDD_LE_BOOT_REPORT_MAX_LEN, 0,
NULL}},
// Boot Mouse Input Report Characteristic Declaration
[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_CHAR] = {{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}},
// Boot Mouse Input Report Characteristic Value
[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_mouse_input_uuid,
ESP_GATT_PERM_READ,
HIDD_LE_BOOT_REPORT_MAX_LEN, 0,
NULL}},
// Boot Mouse Input Report Characteristic - Client Characteristic Configuration Descriptor
[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG] = {{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), 0,
NULL}},
// Report Characteristic Declaration
[HIDD_LE_IDX_REPORT_CHAR] = {{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}},
// Report Characteristic Value
[HIDD_LE_IDX_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid,
ESP_GATT_PERM_READ,
HIDD_LE_REPORT_MAX_LEN, 0,
NULL}},
// Report Characteristic - Report Reference Descriptor
[HIDD_LE_IDX_REPORT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid,
ESP_GATT_PERM_READ,
sizeof(hidReportRefFeature), sizeof(hidReportRefFeature),
hidReportRefFeature}},
};
static void hid_add_id_tbl(void);
void esp_hidd_prf_cb_hdl(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_ble_gap_config_local_icon (ESP_BLE_APPEARANCE_GENERIC_HID);
esp_hidd_cb_param_t hidd_param;
hidd_param.init_finish.state = param->reg.status;
if(param->reg.app_id == HIDD_APP_ID) {
hidd_le_env.gatt_if = gatts_if;
if(hidd_le_env.hidd_cb != NULL) {
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_REG_FINISH, &hidd_param);
hidd_le_create_service(hidd_le_env.gatt_if);
}
}
if(param->reg.app_id == BATTRAY_APP_ID) {
hidd_param.init_finish.gatts_if = gatts_if;
if(hidd_le_env.hidd_cb != NULL) {
(hidd_le_env.hidd_cb)(ESP_BAT_EVENT_REG, &hidd_param);
}
}
break;
}
case ESP_GATTS_CONF_EVT: {
break;
}
case ESP_GATTS_CREATE_EVT:
break;
case ESP_GATTS_CONNECT_EVT: {
esp_hidd_cb_param_t cb_param = {0};
ESP_LOGI(HID_LE_PRF_TAG, "HID connection establish, conn_id = %x",param->connect.conn_id);
memcpy(cb_param.connect.remote_bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
cb_param.connect.conn_id = param->connect.conn_id;
hidd_clcb_alloc(param->connect.conn_id, param->connect.remote_bda);
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_NO_MITM);
if(hidd_le_env.hidd_cb != NULL) {
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_CONNECT, &cb_param);
}
break;
}
case ESP_GATTS_DISCONNECT_EVT: {
if(hidd_le_env.hidd_cb != NULL) {
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_DISCONNECT, NULL);
}
hidd_clcb_dealloc(param->disconnect.conn_id);
break;
}
case ESP_GATTS_CLOSE_EVT:
break;
case ESP_GATTS_WRITE_EVT: {
#if (SUPPORT_REPORT_VENDOR == true)
esp_hidd_cb_param_t cb_param = {0};
if (param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] &&
hidd_le_env.hidd_cb != NULL) {
cb_param.vendor_write.conn_id = param->write.conn_id;
cb_param.vendor_write.report_id = HID_RPT_ID_VENDOR_OUT;
cb_param.vendor_write.length = param->write.len;
cb_param.vendor_write.data = param->write.value;
(hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT, &cb_param);
}
#endif
break;
}
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->add_attr_tab.num_handle == BAS_IDX_NB &&
param->add_attr_tab.svc_uuid.uuid.uuid16 == ESP_GATT_UUID_BATTERY_SERVICE_SVC &&
param->add_attr_tab.status == ESP_GATT_OK) {
incl_svc.start_hdl = param->add_attr_tab.handles[BAS_IDX_SVC];
incl_svc.end_hdl = incl_svc.start_hdl + BAS_IDX_NB -1;
ESP_LOGI(HID_LE_PRF_TAG, "%s(), start added the hid service to the stack database. incl_handle = %d",
__func__, incl_svc.start_hdl);
esp_ble_gatts_create_attr_tab(hidd_le_gatt_db, gatts_if, HIDD_LE_IDX_NB, 0);
}
if (param->add_attr_tab.num_handle == HIDD_LE_IDX_NB &&
param->add_attr_tab.status == ESP_GATT_OK) {
memcpy(hidd_le_env.hidd_inst.att_tbl, param->add_attr_tab.handles,
HIDD_LE_IDX_NB*sizeof(uint16_t));
ESP_LOGI(HID_LE_PRF_TAG, "hid svc handle = %x",hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
hid_add_id_tbl();
esp_ble_gatts_start_service(hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]);
} else {
esp_ble_gatts_start_service(param->add_attr_tab.handles[0]);
}
break;
}
default:
break;
}
}
void hidd_le_create_service(esp_gatt_if_t gatts_if)
{
/* Here should added the battery service first, because the hid service should include the battery service.
After finish to added the battery service then can added the hid service. */
esp_ble_gatts_create_attr_tab(bas_att_db, gatts_if, BAS_IDX_NB, 0);
}
void hidd_le_init(void)
{
// Reset the hid device target environment
memset(&hidd_le_env, 0, sizeof(hidd_le_env_t));
}
void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda)
{
uint8_t i_clcb = 0;
hidd_clcb_t *p_clcb = NULL;
for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) {
if (!p_clcb->in_use) {
p_clcb->in_use = true;
p_clcb->conn_id = conn_id;
p_clcb->connected = true;
memcpy (p_clcb->remote_bda, bda, ESP_BD_ADDR_LEN);
break;
}
}
return;
}
bool hidd_clcb_dealloc (uint16_t conn_id)
{
uint8_t i_clcb = 0;
hidd_clcb_t *p_clcb = NULL;
for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) {
memset(p_clcb, 0, sizeof(hidd_clcb_t));
return true;
}
return false;
}
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = {
[PROFILE_APP_IDX] = {
.gatts_cb = esp_hidd_prf_cb_hdl,
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
static 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 is register event, store the gatts_if for each profile */
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
} else {
ESP_LOGI(HID_LE_PRF_TAG, "Reg app failed, app_id %04x, status %d\n",
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 || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gatts_if == heart_rate_profile_tab[idx].gatts_if) {
if (heart_rate_profile_tab[idx].gatts_cb) {
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
esp_err_t hidd_register_cb(void)
{
esp_err_t status;
status = esp_ble_gatts_register_callback(gatts_event_handler);
return status;
}
void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value)
{
hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst;
if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle &&
hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle) {
esp_ble_gatts_set_attr_value(handle, val_len, value);
} else {
ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.",__func__);
}
return;
}
void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value)
{
hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst;
if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle &&
hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle){
esp_ble_gatts_get_attr_value(handle, length, (const uint8_t **)value);
} else {
ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.", __func__);
}
return;
}
static void hid_add_id_tbl(void)
{
// Mouse input report
hid_rpt_map[0].id = hidReportRefMouseIn[0];
hid_rpt_map[0].type = hidReportRefMouseIn[1];
hid_rpt_map[0].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL];
hid_rpt_map[0].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL];
hid_rpt_map[0].mode = HID_PROTOCOL_MODE_REPORT;
// Key input report
hid_rpt_map[1].id = hidReportRefKeyIn[0];
hid_rpt_map[1].type = hidReportRefKeyIn[1];
hid_rpt_map[1].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_KEY_IN_VAL];
hid_rpt_map[1].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_KEY_IN_CCC];
hid_rpt_map[1].mode = HID_PROTOCOL_MODE_REPORT;
// Consumer Control input report
hid_rpt_map[2].id = hidReportRefCCIn[0];
hid_rpt_map[2].type = hidReportRefCCIn[1];
hid_rpt_map[2].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_CC_IN_VAL];
hid_rpt_map[2].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_CC_IN_CCC];
hid_rpt_map[2].mode = HID_PROTOCOL_MODE_REPORT;
// LED output report
hid_rpt_map[3].id = hidReportRefLedOut[0];
hid_rpt_map[3].type = hidReportRefLedOut[1];
hid_rpt_map[3].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_LED_OUT_VAL];
hid_rpt_map[3].cccdHandle = 0;
hid_rpt_map[3].mode = HID_PROTOCOL_MODE_REPORT;
// Boot keyboard input report
// Use same ID and type as key input report
hid_rpt_map[4].id = hidReportRefKeyIn[0];
hid_rpt_map[4].type = hidReportRefKeyIn[1];
hid_rpt_map[4].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL];
hid_rpt_map[4].cccdHandle = 0;
hid_rpt_map[4].mode = HID_PROTOCOL_MODE_BOOT;
// Boot keyboard output report
// Use same ID and type as LED output report
hid_rpt_map[5].id = hidReportRefLedOut[0];
hid_rpt_map[5].type = hidReportRefLedOut[1];
hid_rpt_map[5].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL];
hid_rpt_map[5].cccdHandle = 0;
hid_rpt_map[5].mode = HID_PROTOCOL_MODE_BOOT;
// Boot mouse input report
// Use same ID and type as mouse input report
hid_rpt_map[6].id = hidReportRefMouseIn[0];
hid_rpt_map[6].type = hidReportRefMouseIn[1];
hid_rpt_map[6].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL];
hid_rpt_map[6].cccdHandle = 0;
hid_rpt_map[6].mode = HID_PROTOCOL_MODE_BOOT;
// Feature report
hid_rpt_map[7].id = hidReportRefFeature[0];
hid_rpt_map[7].type = hidReportRefFeature[1];
hid_rpt_map[7].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VAL];
hid_rpt_map[7].cccdHandle = 0;
hid_rpt_map[7].mode = HID_PROTOCOL_MODE_REPORT;
// Setup report ID map
hid_dev_register_reports(HID_NUM_REPORTS, hid_rpt_map);
}

View File

@@ -0,0 +1,344 @@
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __HID_DEVICE_LE_PRF__
#define __HID_DEVICE_LE_PRF__
#include <stdbool.h>
#include "esp_gatts_api.h"
#include "esp_gatt_defs.h"
#include "esp_hidd_prf_api.h"
#include "esp_gap_ble_api.h"
#include "hid_dev.h"
#define SUPPORT_REPORT_VENDOR false
//HID BLE profile log tag
#define HID_LE_PRF_TAG "HID_LE_PRF"
/// Maximal number of HIDS that can be added in the DB
#ifndef USE_ONE_HIDS_INSTANCE
#define HIDD_LE_NB_HIDS_INST_MAX (2)
#else
#define HIDD_LE_NB_HIDS_INST_MAX (1)
#endif
#define HIDD_GREAT_VER 0x01 //Version + Subversion
#define HIDD_SUB_VER 0x00 //Version + Subversion
#define HIDD_VERSION ((HIDD_GREAT_VER<<8)|HIDD_SUB_VER) //Version + Subversion
#define HID_MAX_APPS 1
// Number of HID reports defined in the service
#define HID_NUM_REPORTS 9
// HID Report IDs for the service
#define HID_RPT_ID_MOUSE_IN 1 // Mouse input report ID
#define HID_RPT_ID_KEY_IN 2 // Keyboard input report ID
#define HID_RPT_ID_CC_IN 3 //Consumer Control input report ID
#define HID_RPT_ID_VENDOR_OUT 4 // Vendor output report ID
#define HID_RPT_ID_LED_OUT 0 // LED output report ID
#define HID_RPT_ID_FEATURE 0 // Feature report ID
#define HIDD_APP_ID 0x1812//ATT_SVC_HID
#define BATTRAY_APP_ID 0x180f
#define ATT_SVC_HID 0x1812
/// Maximal number of Report Char. that can be added in the DB for one HIDS - Up to 11
#define HIDD_LE_NB_REPORT_INST_MAX (5)
/// Maximal length of Report Char. Value
#define HIDD_LE_REPORT_MAX_LEN (255)
/// Maximal length of Report Map Char. Value
#define HIDD_LE_REPORT_MAP_MAX_LEN (512)
/// Length of Boot Report Char. Value Maximal Length
#define HIDD_LE_BOOT_REPORT_MAX_LEN (8)
/// Boot KB Input Report Notification Configuration Bit Mask
#define HIDD_LE_BOOT_KB_IN_NTF_CFG_MASK (0x40)
/// Boot KB Input Report Notification Configuration Bit Mask
#define HIDD_LE_BOOT_MOUSE_IN_NTF_CFG_MASK (0x80)
/// Boot Report Notification Configuration Bit Mask
#define HIDD_LE_REPORT_NTF_CFG_MASK (0x20)
/* HID information flags */
#define HID_FLAGS_REMOTE_WAKE 0x01 // RemoteWake
#define HID_FLAGS_NORMALLY_CONNECTABLE 0x02 // NormallyConnectable
/* Control point commands */
#define HID_CMD_SUSPEND 0x00 // Suspend
#define HID_CMD_EXIT_SUSPEND 0x01 // Exit Suspend
/* HID protocol mode values */
#define HID_PROTOCOL_MODE_BOOT 0x00 // Boot Protocol Mode
#define HID_PROTOCOL_MODE_REPORT 0x01 // Report Protocol Mode
/* Attribute value lengths */
#define HID_PROTOCOL_MODE_LEN 1 // HID Protocol Mode
#define HID_INFORMATION_LEN 4 // HID Information
#define HID_REPORT_REF_LEN 2 // HID Report Reference Descriptor
#define HID_EXT_REPORT_REF_LEN 2 // External Report Reference Descriptor
// HID feature flags
#define HID_KBD_FLAGS HID_FLAGS_REMOTE_WAKE
/* HID Report type */
#define HID_REPORT_TYPE_INPUT 1
#define HID_REPORT_TYPE_OUTPUT 2
#define HID_REPORT_TYPE_FEATURE 3
/// HID Service Attributes Indexes
enum {
HIDD_LE_IDX_SVC,
// Included Service
HIDD_LE_IDX_INCL_SVC,
// HID Information
HIDD_LE_IDX_HID_INFO_CHAR,
HIDD_LE_IDX_HID_INFO_VAL,
// HID Control Point
HIDD_LE_IDX_HID_CTNL_PT_CHAR,
HIDD_LE_IDX_HID_CTNL_PT_VAL,
// Report Map
HIDD_LE_IDX_REPORT_MAP_CHAR,
HIDD_LE_IDX_REPORT_MAP_VAL,
HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF,
// Protocol Mode
HIDD_LE_IDX_PROTO_MODE_CHAR,
HIDD_LE_IDX_PROTO_MODE_VAL,
// Report mouse input
HIDD_LE_IDX_REPORT_MOUSE_IN_CHAR,
HIDD_LE_IDX_REPORT_MOUSE_IN_VAL,
HIDD_LE_IDX_REPORT_MOUSE_IN_CCC,
HIDD_LE_IDX_REPORT_MOUSE_REP_REF,
//Report Key input
HIDD_LE_IDX_REPORT_KEY_IN_CHAR,
HIDD_LE_IDX_REPORT_KEY_IN_VAL,
HIDD_LE_IDX_REPORT_KEY_IN_CCC,
HIDD_LE_IDX_REPORT_KEY_IN_REP_REF,
///Report Led output
HIDD_LE_IDX_REPORT_LED_OUT_CHAR,
HIDD_LE_IDX_REPORT_LED_OUT_VAL,
HIDD_LE_IDX_REPORT_LED_OUT_REP_REF,
#if (SUPPORT_REPORT_VENDOR == true)
/// Report Vendor
HIDD_LE_IDX_REPORT_VENDOR_OUT_CHAR,
HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL,
HIDD_LE_IDX_REPORT_VENDOR_OUT_REP_REF,
#endif
HIDD_LE_IDX_REPORT_CC_IN_CHAR,
HIDD_LE_IDX_REPORT_CC_IN_VAL,
HIDD_LE_IDX_REPORT_CC_IN_CCC,
HIDD_LE_IDX_REPORT_CC_IN_REP_REF,
// Boot Keyboard Input Report
HIDD_LE_IDX_BOOT_KB_IN_REPORT_CHAR,
HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL,
HIDD_LE_IDX_BOOT_KB_IN_REPORT_NTF_CFG,
// Boot Keyboard Output Report
HIDD_LE_IDX_BOOT_KB_OUT_REPORT_CHAR,
HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL,
// Boot Mouse Input Report
HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_CHAR,
HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL,
HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG,
// Report
HIDD_LE_IDX_REPORT_CHAR,
HIDD_LE_IDX_REPORT_VAL,
HIDD_LE_IDX_REPORT_REP_REF,
//HIDD_LE_IDX_REPORT_NTF_CFG,
HIDD_LE_IDX_NB,
};
/// Attribute Table Indexes
enum {
HIDD_LE_INFO_CHAR,
HIDD_LE_CTNL_PT_CHAR,
HIDD_LE_REPORT_MAP_CHAR,
HIDD_LE_REPORT_CHAR,
HIDD_LE_PROTO_MODE_CHAR,
HIDD_LE_BOOT_KB_IN_REPORT_CHAR,
HIDD_LE_BOOT_KB_OUT_REPORT_CHAR,
HIDD_LE_BOOT_MOUSE_IN_REPORT_CHAR,
HIDD_LE_CHAR_MAX //= HIDD_LE_REPORT_CHAR + HIDD_LE_NB_REPORT_INST_MAX,
};
///att read event table Indexs
enum {
HIDD_LE_READ_INFO_EVT,
HIDD_LE_READ_CTNL_PT_EVT,
HIDD_LE_READ_REPORT_MAP_EVT,
HIDD_LE_READ_REPORT_EVT,
HIDD_LE_READ_PROTO_MODE_EVT,
HIDD_LE_BOOT_KB_IN_REPORT_EVT,
HIDD_LE_BOOT_KB_OUT_REPORT_EVT,
HIDD_LE_BOOT_MOUSE_IN_REPORT_EVT,
HID_LE_EVT_MAX
};
/// Client Characteristic Configuration Codes
enum {
HIDD_LE_DESC_MASK = 0x10,
HIDD_LE_BOOT_KB_IN_REPORT_CFG = HIDD_LE_BOOT_KB_IN_REPORT_CHAR | HIDD_LE_DESC_MASK,
HIDD_LE_BOOT_MOUSE_IN_REPORT_CFG = HIDD_LE_BOOT_MOUSE_IN_REPORT_CHAR | HIDD_LE_DESC_MASK,
HIDD_LE_REPORT_CFG = HIDD_LE_REPORT_CHAR | HIDD_LE_DESC_MASK,
};
/// Features Flag Values
enum {
HIDD_LE_CFG_KEYBOARD = 0x01,
HIDD_LE_CFG_MOUSE = 0x02,
HIDD_LE_CFG_PROTO_MODE = 0x04,
HIDD_LE_CFG_MAP_EXT_REF = 0x08,
HIDD_LE_CFG_BOOT_KB_WR = 0x10,
HIDD_LE_CFG_BOOT_MOUSE_WR = 0x20,
};
/// Report Char. Configuration Flag Values
enum {
HIDD_LE_CFG_REPORT_IN = 0x01,
HIDD_LE_CFG_REPORT_OUT = 0x02,
//HOGPD_CFG_REPORT_FEAT can be used as a mask to check Report type
HIDD_LE_CFG_REPORT_FEAT = 0x03,
HIDD_LE_CFG_REPORT_WR = 0x10,
};
/// Pointer to the connection clean-up function
#define HIDD_LE_CLEANUP_FNCT (NULL)
/*
* TYPE DEFINITIONS
****************************************************************************************
*/
/// HIDD Features structure
typedef struct {
/// Service Features
uint8_t svc_features;
/// Number of Report Char. instances to add in the database
uint8_t report_nb;
/// Report Char. Configuration
uint8_t report_char_cfg[HIDD_LE_NB_REPORT_INST_MAX];
} hidd_feature_t;
typedef struct {
bool in_use;
bool congest;
uint16_t conn_id;
bool connected;
esp_bd_addr_t remote_bda;
uint32_t trans_id;
uint8_t cur_srvc_id;
} hidd_clcb_t;
// HID report mapping table
typedef struct {
uint16_t handle; // Handle of report characteristic
uint16_t cccdHandle; // Handle of CCCD for report characteristic
uint8_t id; // Report ID
uint8_t type; // Report type
uint8_t mode; // Protocol mode (report or boot)
} hidRptMap_t;
typedef struct {
/// hidd profile id
uint8_t app_id;
/// Notified handle
uint16_t ntf_handle;
///Attribute handle Table
uint16_t att_tbl[HIDD_LE_IDX_NB];
/// Supported Features
hidd_feature_t hidd_feature[HIDD_LE_NB_HIDS_INST_MAX];
/// Current Protocol Mode
uint8_t proto_mode[HIDD_LE_NB_HIDS_INST_MAX];
/// Number of HIDS added in the database
uint8_t hids_nb;
uint8_t pending_evt;
uint16_t pending_hal;
} hidd_inst_t;
/// Report Reference structure
typedef struct
{
///Report ID
uint8_t report_id;
///Report Type
uint8_t report_type;
}hids_report_ref_t;
/// HID Information structure
typedef struct
{
/// bcdHID
uint16_t bcdHID;
/// bCountryCode
uint8_t bCountryCode;
/// Flags
uint8_t flags;
}hids_hid_info_t;
/* service engine control block */
typedef struct {
hidd_clcb_t hidd_clcb[HID_MAX_APPS]; /* connection link*/
esp_gatt_if_t gatt_if;
bool enabled;
bool is_take;
bool is_primery;
hidd_inst_t hidd_inst;
esp_hidd_event_cb_t hidd_cb;
uint8_t inst_id;
} hidd_le_env_t;
extern hidd_le_env_t hidd_le_env;
extern uint8_t hidProtocolMode;
void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda);
bool hidd_clcb_dealloc (uint16_t conn_id);
void hidd_le_create_service(esp_gatt_if_t gatts_if);
void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value);
void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value);
esp_err_t hidd_register_cb(void);
#endif ///__HID_DEVICE_LE_PRF__

View File

@@ -0,0 +1,6 @@
# Override some defaults so BT stack is enabled
# by default in this example
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ble_ibeacon_demo)

View File

@@ -0,0 +1,10 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := ble_ibeacon_demo
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,116 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
# ESP-IDF iBeacon demo
From welcoming people as they arrive at a sporting event to providing information about a nearby museum exhibit, iBeacon opens a new world of possibilities for location awareness, and countless opportunities for interactivity between iOS devices and iBeacon hardware.
## Using Example
iBeacon is a trademark of Apple Inc.
Before building devices which use iBeacon technology, visit https://developer.apple.com/ibeacon/ to obtain a license.
### iBeacon Mode
This example demonstrates iBeacon-compatible BLE advertising, and scanning of iBeacons:
- **IBEACON_SENDER**: demo to send iBeacon-compatible advertising data.
- **IBEACON_RECEIVER**: demo to receive and resolve iBeacon advertising data.
Which demo will be run depends on the menuconfig, developers can set it in `iBeacon Example Configuration`.
The default mode is iBeacon Sender.
### Menuconfig
Before compiling the demodevelopers also need to configure the project:
```c
idf.py menuconfig
```
And then enter `Component config->Bluetooth->Bluedroid Enable`
Because the number of peripherals may be very large, developers can enable the **BLE Scan Duplicate Options**, the maximum number of devices in scan duplicate filter depends on the free heap size, when the cache is full, it is cleared.
### Event Processing
In the iBeacon receiver demo, the scan result will be posted to `ESP_GAP_SEARCH_INQ_RES_EVT` event:
```c
switch (scan_result->scan_rst.search_evt) {
case ESP_GAP_SEARCH_INQ_RES_EVT:
/* Search for BLE iBeacon Packet */
......
break;
default:
break;
}
```
### Build and Flash
Build each project and flash it to the board, then run monitor tool to view serial output:
```
idp.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
The iBeacon sender will broadcast iBeacon packet after initializing the Bluetooth protocol stack, and the iBeacon receiver will scan the iBeacon packet.
### iBeacon Sender
```
I (384) boot: Loaded app from partition at offset 0x10000
I (384) boot: Disabling RNG early entropy source...
I (386) cpu_start: Pro cpu up.
I (389) cpu_start: Starting app cpu, entry point is 0x40081010
I (0) cpu_start: App cpu up.
I (400) heap_init: Initializing. RAM available for dynamic allocation:
I (406) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (413) heap_init: At 3FFCCCA8 len 00013358 (76 KiB): DRAM
I (419) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (425) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (431) heap_init: At 40090E58 len 0000F1A8 (60 KiB): IRAM
I (438) cpu_start: Pro cpu start user code
I (120) cpu_start: Starting scheduler on PRO CPU
I (0) cpu_start: Starting scheduler on APP CPU
I (244) BTDM_INIT: BT controller compile version [44d04c1]
I (244) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (624) phy: phy_version: 3910, c0c45a3, May 21 2018, 18:07:06, 0, 0
I (654) IBEACON_DEMO: register callback
```
### iBeacon Receiver
```
I (384) boot: Loaded app from partition at offset 0x10000
I (384) boot: Disabling RNG early entropy source...
I (385) cpu_start: Pro cpu up.\0x1b[0m
I (389) cpu_start: Starting app cpu, entry point is 0x40081010
I (0) cpu_start: App cpu up.
I (400) heap_init: Initializing. RAM available for dynamic allocation:
I (406) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (412) heap_init: At 3FFCCC88 len 00013378 (76 KiB): DRAM
I (418) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (425) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (431) heap_init: At 40090E58 len 0000F1A8 (60 KiB): IRAM
I (437) cpu_start: Pro cpu start user code\0x1b[0m
I (120) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (243) BTDM_INIT: BT controller compile version [44d04c1]
I (243) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (633) phy: phy_version: 3910, c0c45a3, May 21 2018, 18:07:06, 0, 0
I (663) IBEACON_DEMO: register callback
I (329203) IBEACON_DEMO: ----------iBeacon Found----------
I (329203) IBEACON_DEMO: Device address:: 30 ae a4 00 42 82
I (329203) IBEACON_DEMO: Proximity UUID:: fd a5 06 93 a4 e2 4f b1 af cf c6 eb 07 64 78 25
```

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "esp_ibeacon_api.c"
"ibeacon_demo.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,26 @@
menu "iBeacon Example Configuration"
choice IBEACON_MODE
bool "iBeacon Mode"
default IBEACON_SENDER
help
Select the iBeacon Mode.
config IBEACON_SENDER
bool "iBeacon Sender Mode"
help
Select the iBeacon Sender Mode.
config IBEACON_RECEIVER
bool "iBeacon Receiver Mode"
help
Select the iBeacon Receiver Mode.
endchoice
config IBEACON_MODE
int
default 0 if IBEACON_SENDER
default 1 if IBEACON_RECEIVER
endmenu

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,72 @@
/*
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/****************************************************************************
*
* This file is for iBeacon APIs. It supports both iBeacon encode and decode.
*
* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology,
* visit https://developer.apple.com/ibeacon/ to obtain a license.
*
****************************************************************************/
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "esp_gap_ble_api.h"
#include "esp_ibeacon_api.h"
const uint8_t uuid_zeros[ESP_UUID_LEN_128] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/* For iBeacon packet format, please refer to Apple "Proximity Beacon Specification" doc */
/* Constant part of iBeacon data */
esp_ble_ibeacon_head_t ibeacon_common_head = {
.flags = {0x02, 0x01, 0x06},
.length = 0x1A,
.type = 0xFF,
.company_id = 0x004C,
.beacon_type = 0x1502
};
/* Vendor part of iBeacon data*/
esp_ble_ibeacon_vendor_t vendor_config = {
.proximity_uuid = ESP_UUID,
.major = ENDIAN_CHANGE_U16(ESP_MAJOR), //Major=ESP_MAJOR
.minor = ENDIAN_CHANGE_U16(ESP_MINOR), //Minor=ESP_MINOR
.measured_power = 0xC5
};
bool esp_ble_is_ibeacon_packet (uint8_t *adv_data, uint8_t adv_data_len){
bool result = false;
if ((adv_data != NULL) && (adv_data_len == 0x1E)){
if (!memcmp(adv_data, (uint8_t*)&ibeacon_common_head, sizeof(ibeacon_common_head))){
result = true;
}
}
return result;
}
esp_err_t esp_ble_config_ibeacon_data (esp_ble_ibeacon_vendor_t *vendor_config, esp_ble_ibeacon_t *ibeacon_adv_data){
if ((vendor_config == NULL) || (ibeacon_adv_data == NULL) || (!memcmp(vendor_config->proximity_uuid, uuid_zeros, sizeof(uuid_zeros)))){
return ESP_ERR_INVALID_ARG;
}
memcpy(&ibeacon_adv_data->ibeacon_head, &ibeacon_common_head, sizeof(esp_ble_ibeacon_head_t));
memcpy(&ibeacon_adv_data->ibeacon_vendor, vendor_config, sizeof(esp_ble_ibeacon_vendor_t));
return ESP_OK;
}

Some files were not shown because too many files have changed in this diff Show More