Initial commit
IMU and bubble level working
This commit is contained in:
12
main/CMakeLists.txt
Normal file
12
main/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
idf_component_register(SRCS "system.c" "bubble.c" "keypad.c" "main.c"
|
||||
"bt_app_core.c"
|
||||
"gui.c"
|
||||
"lsm6dsv.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES "driver"
|
||||
"esp_lcd"
|
||||
"lvgl"
|
||||
"esp_lvgl_port"
|
||||
"esp_timer"
|
||||
"nvs_flash"
|
||||
"bt")
|
||||
9
main/Kconfig.projbuild
Normal file
9
main/Kconfig.projbuild
Normal file
@@ -0,0 +1,9 @@
|
||||
menu "A2DP Example Configuration"
|
||||
config EXAMPLE_SSP_ENABLED
|
||||
bool "Secure Simple Pairing"
|
||||
depends on BT_CLASSIC_ENABLED
|
||||
default y
|
||||
help
|
||||
This enables the Secure Simple Pairing. If disable this option,
|
||||
Bluedroid will only support Legacy Pairing
|
||||
endmenu
|
||||
921
main/bt_app_core.c
Normal file
921
main/bt_app_core.c
Normal file
@@ -0,0 +1,921 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOSConfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "bt_app_core.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_a2dp_api.h"
|
||||
#include "esp_avrc_api.h"
|
||||
|
||||
/* log tags */
|
||||
#define BT_AV_TAG "BT_AV"
|
||||
#define BT_RC_CT_TAG "RC_CT"
|
||||
|
||||
/* device name */
|
||||
#define TARGET_DEVICE_NAME "ESP_SPEAKER"
|
||||
#define LOCAL_DEVICE_NAME "ESP_A2DP_SRC"
|
||||
|
||||
/* AVRCP used transaction label */
|
||||
#define APP_RC_CT_TL_GET_CAPS (0)
|
||||
#define APP_RC_CT_TL_RN_VOLUME_CHANGE (1)
|
||||
|
||||
enum {
|
||||
BT_APP_STACK_UP_EVT = 0x0000, /* event for stack up */
|
||||
BT_APP_HEART_BEAT_EVT = 0xff00, /* event for heart beat */
|
||||
};
|
||||
|
||||
/* A2DP global states */
|
||||
enum {
|
||||
APP_AV_STATE_IDLE,
|
||||
APP_AV_STATE_DISCOVERING,
|
||||
APP_AV_STATE_DISCOVERED,
|
||||
APP_AV_STATE_UNCONNECTED,
|
||||
APP_AV_STATE_CONNECTING,
|
||||
APP_AV_STATE_CONNECTED,
|
||||
APP_AV_STATE_DISCONNECTING,
|
||||
};
|
||||
|
||||
/* sub states of APP_AV_STATE_CONNECTED */
|
||||
enum {
|
||||
APP_AV_MEDIA_STATE_IDLE,
|
||||
APP_AV_MEDIA_STATE_STARTING,
|
||||
APP_AV_MEDIA_STATE_STARTED,
|
||||
APP_AV_MEDIA_STATE_STOPPING,
|
||||
};
|
||||
|
||||
/*********************************
|
||||
* STATIC FUNCTION DECLARATIONS
|
||||
********************************/
|
||||
|
||||
/* application task handler */
|
||||
static void bt_app_task_handler(void *arg);
|
||||
/* message sender for Work queue */
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg);
|
||||
/* handler for dispatched message */
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg);
|
||||
|
||||
/* handler for bluetooth stack enabled events */
|
||||
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param);
|
||||
|
||||
/* avrc controller event handler */
|
||||
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
|
||||
|
||||
/* GAP callback function */
|
||||
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param);
|
||||
|
||||
/* callback function for A2DP source */
|
||||
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param);
|
||||
|
||||
/* callback function for A2DP source audio data stream */
|
||||
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len);
|
||||
|
||||
/* callback function for AVRCP controller */
|
||||
static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param);
|
||||
|
||||
/* handler for heart beat timer */
|
||||
static void bt_app_a2d_heart_beat(TimerHandle_t arg);
|
||||
|
||||
/* A2DP application state machine */
|
||||
static void bt_app_av_sm_hdlr(uint16_t event, void *param);
|
||||
|
||||
/* utils for transfer BLuetooth Deveice Address into string form */
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size);
|
||||
|
||||
/* A2DP application state machine handler for each state */
|
||||
static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param);
|
||||
static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param);
|
||||
static void bt_app_av_state_connected_hdlr(uint16_t event, void *param);
|
||||
static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param);
|
||||
|
||||
/*********************************
|
||||
* STATIC VARIABLE DEFINITIONS
|
||||
********************************/
|
||||
static QueueHandle_t s_bt_app_task_queue = NULL;
|
||||
static TaskHandle_t s_bt_app_task_handle = NULL;
|
||||
|
||||
|
||||
static esp_bd_addr_t s_peer_bda = {0}; /* Bluetooth Device Address of peer device*/
|
||||
static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; /* Bluetooth Device Name of peer device*/
|
||||
static int s_a2d_state = APP_AV_STATE_IDLE; /* A2DP global state */
|
||||
static int s_media_state = APP_AV_MEDIA_STATE_IDLE; /* sub states of APP_AV_STATE_CONNECTED */
|
||||
static int s_intv_cnt = 0; /* count of heart beat intervals */
|
||||
static int s_connecting_intv = 0; /* count of heart beat intervals for connecting */
|
||||
static uint32_t s_pkt_cnt = 0; /* count of packets */
|
||||
static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; /* AVRC target notification event capability bit mask */
|
||||
static TimerHandle_t s_tmr;
|
||||
|
||||
/*********************************
|
||||
* STATIC FUNCTION DEFINITIONS
|
||||
********************************/
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
|
||||
{
|
||||
if (bda == NULL || str == NULL || size < 18) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len)
|
||||
{
|
||||
uint8_t *rmt_bdname = NULL;
|
||||
uint8_t rmt_bdname_len = 0;
|
||||
|
||||
if (!eir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* get complete or short local name from eir data */
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
|
||||
if (!rmt_bdname) {
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
|
||||
}
|
||||
|
||||
if (rmt_bdname) {
|
||||
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
|
||||
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
|
||||
}
|
||||
|
||||
if (bdname) {
|
||||
memcpy(bdname, rmt_bdname, rmt_bdname_len);
|
||||
bdname[rmt_bdname_len] = '\0';
|
||||
}
|
||||
if (bdname_len) {
|
||||
*bdname_len = rmt_bdname_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
char bda_str[18];
|
||||
uint32_t cod = 0; /* class of device */
|
||||
int32_t rssi = -129; /* invalid value */
|
||||
uint8_t *eir = NULL;
|
||||
esp_bt_gap_dev_prop_t *p;
|
||||
|
||||
/* handle the discovery results */
|
||||
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++) {
|
||||
p = param->disc_res.prop + i;
|
||||
switch (p->type) {
|
||||
case ESP_BT_GAP_DEV_PROP_COD:
|
||||
cod = *(uint32_t *)(p->val);
|
||||
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_RSSI:
|
||||
rssi = *(int8_t *)(p->val);
|
||||
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_EIR:
|
||||
eir = (uint8_t *)(p->val);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_BDNAME:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* search for device with MAJOR service class as "rendering" in COD */
|
||||
if (!esp_bt_gap_is_valid_cod(cod) ||
|
||||
!(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Scanned device: %s", bda2str(param->disc_res.bda, bda_str, 18));
|
||||
ESP_LOGI(BT_AV_TAG, "--Class of Device: 0x%"PRIx32, cod);
|
||||
ESP_LOGI(BT_AV_TAG, "--RSSI: %"PRId32, rssi);
|
||||
|
||||
/* search for target device in its Extended Inqury Response */
|
||||
if (eir) {
|
||||
get_name_from_eir(eir, s_peer_bdname, NULL);
|
||||
//if (strcmp((char *)s_peer_bdname, TARGET_DEVICE_NAME) == 0)
|
||||
{
|
||||
ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname);
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERED;
|
||||
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
ESP_LOGI(BT_AV_TAG, "Cancel device discovery ...");
|
||||
esp_bt_gap_cancel_discovery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
/* when device discovered a result, this event comes */
|
||||
case ESP_BT_GAP_DISC_RES_EVT: {
|
||||
if (s_a2d_state == APP_AV_STATE_DISCOVERING) {
|
||||
filter_inquiry_scan_result(param);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* when discovery state changed, this event comes */
|
||||
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
|
||||
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
|
||||
if (s_a2d_state == APP_AV_STATE_DISCOVERED) {
|
||||
s_a2d_state = APP_AV_STATE_CONNECTING;
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery stopped.");
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname);
|
||||
/* connect source to peer device specified by Bluetooth Device Address */
|
||||
esp_a2d_source_connect(s_peer_bda);
|
||||
} else {
|
||||
/* not discovered, continue to discover */
|
||||
ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover...");
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
}
|
||||
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "Discovery started.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* when authentication completed, this event comes */
|
||||
case ESP_BT_GAP_AUTH_CMPL_EVT: {
|
||||
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(BT_AV_TAG, "authentication failed, status: %d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* when Legacy Pairing pin code requested, this event comes */
|
||||
case ESP_BT_GAP_PIN_REQ_EVT: {
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit: %d", param->pin_req.min_16_digit);
|
||||
if (param->pin_req.min_16_digit) {
|
||||
ESP_LOGI(BT_AV_TAG, "Input pin code: 0000 0000 0000 0000");
|
||||
esp_bt_pin_code_t pin_code = {0};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "Input pin code: 1234");
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_EXAMPLE_SSP_ENABLED == true)
|
||||
/* when Security Simple Pairing user confirmation requested, this event comes */
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %"PRIu32, param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
/* when Security Simple Pairing passkey notified, this event comes */
|
||||
case ESP_BT_GAP_KEY_NOTIF_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey: %"PRIu32, param->key_notif.passkey);
|
||||
break;
|
||||
/* when Security Simple Pairing passkey requested, this event comes */
|
||||
case ESP_BT_GAP_KEY_REQ_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
break;
|
||||
#endif
|
||||
|
||||
/* when GAP mode changed, this event comes */
|
||||
case ESP_BT_GAP_MODE_CHG_EVT:
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode: %d", param->mode_chg.mode);
|
||||
break;
|
||||
case ESP_BT_GAP_GET_DEV_NAME_CMPL_EVT:
|
||||
if (param->get_dev_name_cmpl.status == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_GET_DEV_NAME_CMPL_EVT device name: %s", param->get_dev_name_cmpl.name);
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_GET_DEV_NAME_CMPL_EVT failed, state: %d", param->get_dev_name_cmpl.status);
|
||||
}
|
||||
break;
|
||||
/* other */
|
||||
default: {
|
||||
ESP_LOGI(BT_AV_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event);
|
||||
|
||||
switch (event) {
|
||||
/* when stack up worked, this event comes */
|
||||
case BT_APP_STACK_UP_EVT: {
|
||||
char *dev_name = LOCAL_DEVICE_NAME;
|
||||
esp_bt_gap_set_device_name(dev_name);
|
||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||
|
||||
esp_avrc_ct_init();
|
||||
esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
|
||||
|
||||
esp_avrc_rn_evt_cap_mask_t evt_set = {0};
|
||||
esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE);
|
||||
ESP_ERROR_CHECK(esp_avrc_tg_set_rn_evt_cap(&evt_set));
|
||||
|
||||
esp_a2d_source_init();
|
||||
esp_a2d_register_callback(&bt_app_a2d_cb);
|
||||
esp_a2d_source_register_data_callback(bt_app_a2d_data_cb);
|
||||
|
||||
/* Avoid the state error of s_a2d_state caused by the connection initiated by the peer device. */
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
|
||||
esp_bt_gap_get_device_name();
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Starting device discovery...");
|
||||
s_a2d_state = APP_AV_STATE_DISCOVERING;
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
|
||||
/* create and start heart beat timer */
|
||||
do {
|
||||
int tmr_id = 0;
|
||||
s_tmr = xTimerCreate("connTmr", (10000 / portTICK_PERIOD_MS),
|
||||
pdTRUE, (void *) &tmr_id, bt_app_a2d_heart_beat);
|
||||
xTimerStart(s_tmr, portMAX_DELAY);
|
||||
} while (0);
|
||||
break;
|
||||
}
|
||||
/* other */
|
||||
default: {
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
|
||||
{
|
||||
bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL);
|
||||
}
|
||||
|
||||
static uint8_t btData[512];
|
||||
|
||||
static void fillBuffer(uint8_t *data, int32_t len)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Generate a continuous 440Hz sine wave */
|
||||
static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len)
|
||||
{
|
||||
static double phase = 0.0;
|
||||
const double frequency = 440.0; // A4 tone (440 Hz)
|
||||
const double sample_rate = 44100.0; // 44.1 kHz sample rate
|
||||
const double phase_increment = 2.0 * M_PI * frequency / sample_rate;
|
||||
const int16_t amplitude = 32767; // Maximum amplitude for 16-bit audio
|
||||
|
||||
//ESP_LOGI(BT_AV_TAG, "len: %d", (int)len);
|
||||
// Process stereo samples (each sample is 16-bit)
|
||||
for (int i = 0; i < len / 4; i++) {
|
||||
// Generate the sample value
|
||||
int16_t sample = (int16_t)(amplitude * sin(phase));
|
||||
|
||||
// Write the same sample to both left and right channels
|
||||
// Left channel
|
||||
data[4*i] = sample & 0xFF; // Low byte
|
||||
data[4*i + 1] = (sample >> 8); // High byte
|
||||
// Right channel
|
||||
data[4*i + 2] = sample & 0xFF; // Low byte
|
||||
data[4*i + 3] = (sample >> 8); // High byte
|
||||
|
||||
// Update phase for next sample
|
||||
phase += phase_increment;
|
||||
if (phase >= 2.0 * M_PI) {
|
||||
phase -= 2.0 * M_PI; // Keep phase in [0, 2π)
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static void bt_app_a2d_heart_beat(TimerHandle_t arg)
|
||||
{
|
||||
bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static void bt_app_av_sm_hdlr(uint16_t event, void *param)
|
||||
{
|
||||
ESP_LOGI(BT_AV_TAG, "%s state: %d, event: 0x%x", __func__, s_a2d_state, event);
|
||||
|
||||
/* select handler according to different states */
|
||||
switch (s_a2d_state) {
|
||||
case APP_AV_STATE_DISCOVERING:
|
||||
case APP_AV_STATE_DISCOVERED:
|
||||
break;
|
||||
case APP_AV_STATE_UNCONNECTED:
|
||||
bt_app_av_state_unconnected_hdlr(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_CONNECTING:
|
||||
bt_app_av_state_connecting_hdlr(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_CONNECTED:
|
||||
bt_app_av_state_connected_hdlr(event, param);
|
||||
break;
|
||||
case APP_AV_STATE_DISCONNECTING:
|
||||
bt_app_av_state_disconnecting_hdlr(event, param);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s invalid state: %d", __func__, s_a2d_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
/* handle the events of interest in unconnected state */
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
break;
|
||||
case BT_APP_HEART_BEAT_EVT: {
|
||||
uint8_t *bda = s_peer_bda;
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||
bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
esp_a2d_source_connect(s_peer_bda);
|
||||
s_a2d_state = APP_AV_STATE_CONNECTING;
|
||||
s_connecting_intv = 0;
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
|
||||
/* handle the events of interest in connecting state */
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp connected");
|
||||
s_a2d_state = APP_AV_STATE_CONNECTED;
|
||||
s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
} else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
break;
|
||||
case BT_APP_HEART_BEAT_EVT:
|
||||
/**
|
||||
* Switch state to APP_AV_STATE_UNCONNECTED
|
||||
* when connecting lasts more than 2 heart beat intervals.
|
||||
*/
|
||||
if (++s_connecting_intv >= 2) {
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
s_connecting_intv = 0;
|
||||
}
|
||||
break;
|
||||
case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_media_proc(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
|
||||
switch (s_media_state) {
|
||||
case APP_AV_MEDIA_STATE_IDLE: {
|
||||
if (event == BT_APP_HEART_BEAT_EVT) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY);
|
||||
} else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY &&
|
||||
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media ready, starting ...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START);
|
||||
s_media_state = APP_AV_MEDIA_STATE_STARTING;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STARTING: {
|
||||
if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START &&
|
||||
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media start successfully.");
|
||||
s_intv_cnt = 0;
|
||||
s_media_state = APP_AV_MEDIA_STATE_STARTED;
|
||||
} else {
|
||||
/* not started successfully, transfer to idle state */
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media start failed.");
|
||||
s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STARTED: {
|
||||
if (event == BT_APP_HEART_BEAT_EVT) {
|
||||
/* stop media after 10 heart beat intervals */
|
||||
if (++s_intv_cnt >= 10) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media suspending...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_SUSPEND);
|
||||
s_media_state = APP_AV_MEDIA_STATE_STOPPING;
|
||||
s_intv_cnt = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APP_AV_MEDIA_STATE_STOPPING: {
|
||||
if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_SUSPEND &&
|
||||
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media suspend successfully, disconnecting...");
|
||||
s_media_state = APP_AV_MEDIA_STATE_IDLE;
|
||||
esp_a2d_source_disconnect(s_peer_bda);
|
||||
s_a2d_state = APP_AV_STATE_DISCONNECTING;
|
||||
} else {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp media suspending...");
|
||||
esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_SUSPEND);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_connected_hdlr(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
|
||||
/* handle the events of interest in connected state */
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) {
|
||||
s_pkt_cnt = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
// not supposed to occur for A2DP source
|
||||
break;
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
case BT_APP_HEART_BEAT_EVT: {
|
||||
bt_app_av_media_proc(event, param);
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param)
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = NULL;
|
||||
|
||||
/* handle the events of interest in disconnecing state */
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(BT_AV_TAG, "a2dp disconnected");
|
||||
s_a2d_state = APP_AV_STATE_UNCONNECTED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
case ESP_A2D_MEDIA_CTRL_ACK_EVT:
|
||||
case BT_APP_HEART_BEAT_EVT:
|
||||
break;
|
||||
case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: {
|
||||
a2d = (esp_a2d_cb_param_t *)(param);
|
||||
ESP_LOGI(BT_AV_TAG, "%s, delay value: 0x%u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* callback function for AVRCP controller */
|
||||
static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
|
||||
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
|
||||
case ESP_AVRC_CT_METADATA_RSP_EVT:
|
||||
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
|
||||
case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
|
||||
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
|
||||
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
|
||||
bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_av_volume_changed(void)
|
||||
{
|
||||
if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap,
|
||||
ESP_AVRC_RN_VOLUME_CHANGE)) {
|
||||
esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, ESP_AVRC_RN_VOLUME_CHANGE, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
|
||||
{
|
||||
switch (event_id) {
|
||||
/* when volume changed locally on target, this event comes */
|
||||
case ESP_AVRC_RN_VOLUME_CHANGE: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume);
|
||||
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5);
|
||||
esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5);
|
||||
bt_av_volume_changed();
|
||||
break;
|
||||
}
|
||||
/* other */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* AVRC controller event handler */
|
||||
static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event);
|
||||
esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param);
|
||||
|
||||
switch (event) {
|
||||
/* when connection state changed, this event comes */
|
||||
case ESP_AVRC_CT_CONNECTION_STATE_EVT: {
|
||||
uint8_t *bda = rc->conn_stat.remote_bda;
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state event: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
|
||||
|
||||
if (rc->conn_stat.connected) {
|
||||
esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS);
|
||||
} else {
|
||||
s_avrc_peer_rn_cap.bits = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* when passthrough responded, this event comes */
|
||||
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough response: key_code 0x%x, key_state %d, rsp_code %d", rc->psth_rsp.key_code,
|
||||
rc->psth_rsp.key_state, rc->psth_rsp.rsp_code);
|
||||
break;
|
||||
}
|
||||
/* when metadata responded, this event comes */
|
||||
case ESP_AVRC_CT_METADATA_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata response: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
|
||||
free(rc->meta_rsp.attr_text);
|
||||
break;
|
||||
}
|
||||
/* when notification changed, this event comes */
|
||||
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
|
||||
bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter);
|
||||
break;
|
||||
}
|
||||
/* when indicate feature of remote device, this event comes */
|
||||
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %"PRIx32", TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
|
||||
break;
|
||||
}
|
||||
/* when get supported notification events capability of peer device, this event comes */
|
||||
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
|
||||
rc->get_rn_caps_rsp.evt_set.bits);
|
||||
s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits;
|
||||
|
||||
bt_av_volume_changed();
|
||||
break;
|
||||
}
|
||||
/* when set absolute volume responded, this event comes */
|
||||
case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume response: volume %d", rc->set_volume_rsp.volume);
|
||||
break;
|
||||
}
|
||||
/* other */
|
||||
default: {
|
||||
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pdTRUE != xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bt_app_work_dispatched(bt_app_msg_t *msg)
|
||||
{
|
||||
if (msg->cb) {
|
||||
msg->cb(msg->event, msg->param);
|
||||
}
|
||||
}
|
||||
|
||||
static void bt_app_task_handler(void *arg)
|
||||
{
|
||||
bt_app_msg_t msg;
|
||||
|
||||
for (;;) {
|
||||
/* receive message from work queue and handle it */
|
||||
if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (TickType_t)portMAX_DELAY)) {
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s, signal: 0x%x, event: 0x%x", __func__, msg.sig, msg.event);
|
||||
|
||||
switch (msg.sig) {
|
||||
case BT_APP_SIG_WORK_DISPATCH:
|
||||
bt_app_work_dispatched(&msg);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled signal: %d", __func__, msg.sig);
|
||||
break;
|
||||
}
|
||||
|
||||
if (msg.param) {
|
||||
free(msg.param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************
|
||||
* EXTERN FUNCTION DEFINITIONS
|
||||
********************************/
|
||||
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
|
||||
{
|
||||
ESP_LOGD(BT_APP_CORE_TAG, "%s event: 0x%x, param len: %d", __func__, event, param_len);
|
||||
|
||||
bt_app_msg_t msg;
|
||||
memset(&msg, 0, sizeof(bt_app_msg_t));
|
||||
|
||||
msg.sig = BT_APP_SIG_WORK_DISPATCH;
|
||||
msg.event = event;
|
||||
msg.cb = p_cback;
|
||||
|
||||
if (param_len == 0) {
|
||||
return bt_app_send_msg(&msg);
|
||||
} else if (p_params && param_len > 0) {
|
||||
if ((msg.param = malloc(param_len)) != NULL) {
|
||||
memcpy(msg.param, p_params, param_len);
|
||||
/* check if caller has provided a copy callback to do the deep copy */
|
||||
if (p_copy_cback) {
|
||||
p_copy_cback(msg.param, p_params, param_len);
|
||||
}
|
||||
return bt_app_send_msg(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void bt_app_task_start_up(void)
|
||||
{
|
||||
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
|
||||
xTaskCreate(bt_app_task_handler, "BtAppTask", 4096, NULL, 10, &s_bt_app_task_handle);
|
||||
}
|
||||
|
||||
void bt_app_task_shut_down(void)
|
||||
{
|
||||
if (s_bt_app_task_handle) {
|
||||
vTaskDelete(s_bt_app_task_handle);
|
||||
s_bt_app_task_handle = NULL;
|
||||
}
|
||||
if (s_bt_app_task_queue) {
|
||||
vQueueDelete(s_bt_app_task_queue);
|
||||
s_bt_app_task_queue = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void bt_app_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
char bda_str[18] = {0};
|
||||
|
||||
fillBuffer(btData, 512);
|
||||
|
||||
/*
|
||||
* This example only uses the functions of Classical Bluetooth.
|
||||
* So release the controller memory for Bluetooth Low Energy.
|
||||
*/
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s initialize controller failed", __func__);
|
||||
return;
|
||||
}
|
||||
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s enable controller failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
|
||||
#if (CONFIG_EXAMPLE_SSP_ENABLED == false)
|
||||
bluedroid_cfg.ssp_en = false;
|
||||
#endif
|
||||
if ((ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_bluedroid_enable() != ESP_OK) {
|
||||
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
#if (CONFIG_EXAMPLE_SSP_ENABLED == true)
|
||||
/* set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
esp_bt_gap_set_pin(pin_type, 0, pin_code);
|
||||
|
||||
ESP_LOGI(BT_AV_TAG, "Own address:[%s]", bda2str((uint8_t *)esp_bt_dev_get_address(), bda_str, sizeof(bda_str)));
|
||||
bt_app_task_start_up();
|
||||
/* Bluetooth device name, connection mode and profile set up */
|
||||
bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_STACK_UP_EVT, NULL, 0, NULL);
|
||||
}
|
||||
70
main/bt_app_core.h
Normal file
70
main/bt_app_core.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#ifndef __BT_APP_CORE_H__
|
||||
#define __BT_APP_CORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* log tag */
|
||||
#define BT_APP_CORE_TAG "BT_APP_CORE"
|
||||
|
||||
/* signal for dispatcher */
|
||||
#define BT_APP_SIG_WORK_DISPATCH (0x01)
|
||||
|
||||
/**
|
||||
* @brief handler for the dispatched work
|
||||
*
|
||||
* @param [in] event message event id
|
||||
* @param [in] param pointer to the parameter
|
||||
*/
|
||||
typedef void (* bt_app_cb_t) (uint16_t event, void *param);
|
||||
|
||||
/* message to be sent */
|
||||
typedef struct {
|
||||
uint16_t sig; /*!< signal to bt_app_task */
|
||||
uint16_t event; /*!< message event id */
|
||||
bt_app_cb_t cb; /*!< context switch callback */
|
||||
void *param; /*!< parameter area needs to be last */
|
||||
} bt_app_msg_t;
|
||||
|
||||
/**
|
||||
* @brief parameter deep-copy function to be customized
|
||||
*
|
||||
* @param [in] p_dest pointer to the destination
|
||||
* @param [in] p_src pointer to the source
|
||||
* @param [in] len data length in byte
|
||||
*/
|
||||
typedef void (* bt_app_copy_cb_t) (void *p_dest, void *p_src, int len);
|
||||
|
||||
/**
|
||||
* @brief work dispatcher for the application task
|
||||
*
|
||||
* @param [in] p_cback handler for the dispatched work (event handler)
|
||||
* @param [in] event message event id
|
||||
* @param [in] p_params pointer to the parameter
|
||||
* @param [in] param_len length of the parameter
|
||||
* @param [in] p_copy_cback parameter deep-copy function
|
||||
*
|
||||
* @return true if work dispatch successfully, false otherwise
|
||||
*/
|
||||
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
|
||||
|
||||
/**
|
||||
* @brief start up the application task
|
||||
*/
|
||||
void bt_app_task_start_up(void);
|
||||
|
||||
/**
|
||||
* @brief shut down the application task
|
||||
*/
|
||||
void bt_app_task_shut_down(void);
|
||||
|
||||
void bt_app_init(void);
|
||||
|
||||
#endif /* __BT_APP_CORE_H__ */
|
||||
132
main/bubble.c
Normal file
132
main/bubble.c
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "bubble.h"
|
||||
|
||||
typedef struct {
|
||||
float min_val;
|
||||
float max_val;
|
||||
float cur_val;
|
||||
} bubble_level_dsc_t;
|
||||
|
||||
|
||||
static void bubble_draw_cb(lv_event_t * e)
|
||||
{
|
||||
lvgl_port_lock(0);
|
||||
|
||||
lv_obj_t * obj = lv_event_get_target(e);
|
||||
bubble_level_dsc_t * d = lv_obj_get_user_data(obj);
|
||||
if(!d) return;
|
||||
|
||||
// 1) Get the LVGL draw layer (clipping is automatic)
|
||||
lv_layer_t * layer = lv_event_get_layer(e);
|
||||
|
||||
// 2) Figure out the object's inner rectangle
|
||||
lv_area_t coords;
|
||||
lv_obj_get_coords(obj, &coords);
|
||||
lv_area_t inner = {
|
||||
.x1 = coords.x1 + 2, .y1 = coords.y1 + 2,
|
||||
.x2 = coords.x2 - 2, .y2 = coords.y2 - 2
|
||||
};
|
||||
|
||||
// 3) Draw the border
|
||||
lv_draw_rect_dsc_t rect_dsc;
|
||||
lv_draw_rect_dsc_init(&rect_dsc);
|
||||
rect_dsc.border_color = lv_color_hex(0xAAAAAA);
|
||||
rect_dsc.border_width = 2;
|
||||
rect_dsc.bg_opa = LV_OPA_TRANSP;
|
||||
lv_draw_rect(layer, &rect_dsc, &inner);
|
||||
|
||||
// 4) Draw guide lines at the center
|
||||
lv_coord_t mid_x = (inner.x1 + inner.x2) / 2;
|
||||
lv_coord_t mid_y = (inner.y1 + inner.y2) / 2;
|
||||
|
||||
lv_draw_line_dsc_t line_dsc;
|
||||
lv_draw_line_dsc_init(&line_dsc);
|
||||
line_dsc.color = lv_color_hex(0x444444);
|
||||
line_dsc.width = 1;
|
||||
|
||||
// Set up points and convert to 'precise' type
|
||||
lv_point_t tl = { inner.x1 + 10, mid_y };
|
||||
lv_point_t tr = { inner.x2 - 10, mid_y };
|
||||
line_dsc.p1 = lv_point_to_precise(&tl);
|
||||
line_dsc.p2 = lv_point_to_precise(&tr);
|
||||
lv_draw_line(layer, &line_dsc);
|
||||
|
||||
lv_point_t tt = { mid_x, inner.y1 + 10 };
|
||||
lv_point_t tb = { mid_x, inner.y2 - 10 };
|
||||
line_dsc.p1 = lv_point_to_precise(&tt);
|
||||
line_dsc.p2 = lv_point_to_precise(&tb);
|
||||
lv_draw_line(layer, &line_dsc);
|
||||
|
||||
// 5) Compute bubble position (normalized between min_val → max_val)
|
||||
float norm = (d->cur_val - d->min_val) / (d->max_val - d->min_val);
|
||||
norm = norm < 0 ? 0 : (norm > 1 ? 1 : norm);
|
||||
|
||||
const lv_coord_t bubble_d = 16;
|
||||
lv_coord_t avail_w = (inner.x2 - inner.x1 + 1) - bubble_d;
|
||||
lv_coord_t bx = inner.x1 + (lv_coord_t)(norm * avail_w);
|
||||
lv_coord_t by = mid_y - (bubble_d / 2);
|
||||
|
||||
// 6) Draw the bubble as a filled circle (rounded rect)
|
||||
lv_draw_rect_dsc_t circ_dsc;
|
||||
lv_draw_rect_dsc_init(&circ_dsc);
|
||||
circ_dsc.bg_color = lv_color_hex(0x00CC00);
|
||||
circ_dsc.bg_opa = LV_OPA_COVER;
|
||||
circ_dsc.radius = bubble_d / 2;
|
||||
circ_dsc.border_opa = LV_OPA_TRANSP;
|
||||
|
||||
lv_area_t circ_area = {
|
||||
.x1 = bx,
|
||||
.y1 = by,
|
||||
.x2 = bx + bubble_d - 1,
|
||||
.y2 = by + bubble_d - 1
|
||||
};
|
||||
lv_draw_rect(layer, &circ_dsc, &circ_area);
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
|
||||
lv_obj_t * bubble_create(lv_obj_t * parent,
|
||||
lv_coord_t width, lv_coord_t height,
|
||||
float min_val, float max_val, float initial)
|
||||
{
|
||||
// 1) Create a container object (no special style)
|
||||
lv_obj_t * obj = lv_obj_create(parent);
|
||||
lv_obj_set_size(obj, width, height);
|
||||
|
||||
// (Optional) center it or let the caller position it:
|
||||
// lv_obj_center(obj);
|
||||
|
||||
// 2) Allocate or attach our descriptor
|
||||
// In LVGL v9 you can use USER_DATA: need to enable LV_USE_USER_DATA = 1
|
||||
bubble_level_dsc_t * dsc = lv_malloc(sizeof(bubble_level_dsc_t));
|
||||
if(!dsc) return NULL; /* handle out-of-memory */
|
||||
dsc->min_val = min_val;
|
||||
dsc->max_val = max_val;
|
||||
dsc->cur_val = initial;
|
||||
lv_obj_set_user_data(obj, dsc);
|
||||
|
||||
// 3) Register our draw callback
|
||||
lv_obj_add_event_cb(obj, bubble_draw_cb, LV_EVENT_DRAW_MAIN, NULL);
|
||||
|
||||
// 4) Make sure the object does NOT get a default background fill
|
||||
lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, 0);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
void bubble_setValue(lv_obj_t * bubble, float v)
|
||||
{
|
||||
lvgl_port_lock(0);
|
||||
// 1) Retrieve the descriptor
|
||||
bubble_level_dsc_t * d = lv_obj_get_user_data(bubble);
|
||||
if(!d) return;
|
||||
|
||||
// 2) Update the stored value
|
||||
d->cur_val = v;
|
||||
|
||||
// 3) Tell LVGL that object must be re‐drawn
|
||||
lv_obj_invalidate(bubble);
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
13
main/bubble.h
Normal file
13
main/bubble.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef BUBBLE_H
|
||||
#define BUBBLE_H
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
|
||||
lv_obj_t * bubble_create(lv_obj_t * parent,
|
||||
lv_coord_t width, lv_coord_t height,
|
||||
float min_val, float max_val, float initial);
|
||||
|
||||
void bubble_setValue(lv_obj_t * bubble, float v);
|
||||
|
||||
#endif
|
||||
28
main/gpio.h
Normal file
28
main/gpio.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef GPIO_H
|
||||
#define GPIO_H
|
||||
|
||||
// Define keypad buttons
|
||||
#define PIN_NUM_BUTTON_0 36
|
||||
#define PIN_NUM_BUTTON_1 39
|
||||
#define PIN_NUM_LED_1 32
|
||||
#define PIN_NUM_nON 26
|
||||
|
||||
|
||||
// Define GPIO pins
|
||||
#ifdef DEVKIT
|
||||
#define PIN_NUM_MOSI 23 // SDA pin for LCD
|
||||
#define PIN_NUM_CLK 18 // SCL pin for LCD
|
||||
#define PIN_NUM_CS 2 // CS pin
|
||||
#define PIN_NUM_DC 12 // Data/Command pin (RS)
|
||||
#define PIN_NUM_RST 13 // Reset pin
|
||||
#define PIN_NUM_BK_LIGHT -1 // Backlight control pin, -1 if not used
|
||||
#else
|
||||
#define PIN_NUM_MOSI 23 // SDA pin for LCD
|
||||
#define PIN_NUM_CLK 18 // SCL pin for LCD
|
||||
#define PIN_NUM_CS 12 // CS pin
|
||||
#define PIN_NUM_DC 15 // Data/Command pin (RS)
|
||||
#define PIN_NUM_RST 13 // Reset pin
|
||||
#define PIN_NUM_BK_LIGHT 5 // Backlight control pin, -1 if not used
|
||||
#endif
|
||||
|
||||
#endif
|
||||
400
main/gui.c
Normal file
400
main/gui.c
Normal file
@@ -0,0 +1,400 @@
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_vendor.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "lvgl.h"
|
||||
#include "esp_lvgl_port.h"
|
||||
|
||||
#include "gui.h"
|
||||
#include "gpio.h"
|
||||
#include "keypad.h"
|
||||
#include "bubble.h"
|
||||
#include "system.h"
|
||||
|
||||
#define DEVKIT
|
||||
#undef DEVKIT
|
||||
|
||||
static const char *TAG = "gui";
|
||||
|
||||
// LCD Pin Configuration
|
||||
#define LCD_PIXEL_CLOCK_HZ (5 * 1000 * 1000)
|
||||
#define LCD_SPI_HOST SPI2_HOST
|
||||
|
||||
#define PIN_NUM_nON 26
|
||||
|
||||
|
||||
|
||||
// ST7735S properties
|
||||
#define LCD_H_RES 160
|
||||
#define LCD_V_RES 80
|
||||
#define LCD_CMD_BITS 8
|
||||
#define LCD_PARAM_BITS 8
|
||||
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_RGB
|
||||
#define LCD_BITS_PER_PIXEL 16
|
||||
|
||||
static esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
static lv_disp_t *disp = NULL;
|
||||
static lv_obj_t *imu_label = NULL; // Label for IMU data
|
||||
|
||||
static lv_style_t style_mono8;
|
||||
|
||||
static void gui_task(void);
|
||||
static void createBubble(lv_obj_t * scr);
|
||||
|
||||
#define MAX_ITEMS 10
|
||||
#define VISIBLE_ITEMS 3
|
||||
#define MENU_MAX_STRING_LENGTH 30
|
||||
|
||||
static const char *menu_items[MAX_ITEMS] = {
|
||||
"V-Moda Crossfade", "Item 2", "Item 3", "Item 4",
|
||||
"Item 5", "Item 6", "Item 7", "Item 8",
|
||||
"Item 9", "Item 10"
|
||||
};
|
||||
|
||||
static lv_obj_t *list;
|
||||
static lv_obj_t *buttons[MAX_ITEMS];
|
||||
static int selected_index = 0;
|
||||
static lv_obj_t *_bubble = NULL;
|
||||
|
||||
|
||||
|
||||
// Update highlight based on selected_index
|
||||
void update_highlight() {
|
||||
|
||||
lvgl_port_lock(0);
|
||||
for (int i = 0; i < MAX_ITEMS; i++) {
|
||||
if (buttons[i]) {
|
||||
lv_obj_remove_state(buttons[i], LV_STATE_FOCUSED);
|
||||
}
|
||||
}
|
||||
//if (buttons[selected_index])
|
||||
{
|
||||
lv_obj_add_state(buttons[selected_index], LV_STATE_FOCUSED);
|
||||
lv_obj_scroll_to_view(buttons[selected_index], LV_ANIM_ON);
|
||||
}
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
// Called when the GPIO button is pressed
|
||||
void scroll_down() {
|
||||
if (selected_index < MAX_ITEMS - 1) {
|
||||
selected_index++;
|
||||
update_highlight();
|
||||
}
|
||||
}
|
||||
|
||||
void scroll_up() {
|
||||
if (selected_index > 0) {
|
||||
selected_index--;
|
||||
update_highlight();
|
||||
}
|
||||
}
|
||||
|
||||
static void create_menu(lv_obj_t *parent)
|
||||
{
|
||||
lvgl_port_lock(0);
|
||||
|
||||
list = lv_list_create(parent);
|
||||
lv_obj_set_size(list, 160, 80);
|
||||
lv_obj_center(list);
|
||||
lv_obj_set_style_pad_row(list, 2, 0);
|
||||
lv_obj_set_scroll_dir(list, LV_DIR_VER);
|
||||
lv_obj_set_scrollbar_mode(list, LV_SCROLLBAR_MODE_AUTO);
|
||||
|
||||
for (int i = 0; i < MAX_ITEMS; i++) {
|
||||
buttons[i] = lv_list_add_btn(list, NULL, menu_items[i]);
|
||||
}
|
||||
|
||||
// Apply style to show selection highlight
|
||||
static lv_style_t style_focus;
|
||||
lv_style_init(&style_focus);
|
||||
lv_style_set_bg_color(&style_focus, lv_palette_main(LV_PALETTE_RED));
|
||||
lv_style_set_bg_opa(&style_focus, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&style_focus, lv_color_white());
|
||||
|
||||
for (int i = 0; i < MAX_ITEMS; i++) {
|
||||
lv_obj_add_style(buttons[i], &style_focus, LV_STATE_FOCUSED);
|
||||
}
|
||||
|
||||
update_highlight();
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
static bool notify_lvgl_flush_ready(void *user_ctx) {
|
||||
if (disp) {
|
||||
lv_display_flush_ready(disp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void create_lvgl_demo(void)
|
||||
{
|
||||
|
||||
lvgl_port_lock(0);
|
||||
// Create a screen with black background
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
//lv_obj_set_style_bg_color(scr, lv_color_black(), LV_PART_MAIN);
|
||||
//lv_obj_set_style_bg_color(scr, lv_palette_main(LV_PALETTE_RED), LV_PART_MAIN);
|
||||
|
||||
//lv_style_init(&style_mono8);
|
||||
//lv_style_set_text_font(&style_mono8, &lv_font_unscii_8);
|
||||
|
||||
//create_menu(scr);
|
||||
createBubble(scr);
|
||||
|
||||
lvgl_port_unlock();
|
||||
|
||||
#if 0
|
||||
// Create a label for IMU data
|
||||
imu_label = lv_label_create(scr);
|
||||
lv_obj_set_style_text_font(imu_label, &lv_font_montserrat_20, LV_PART_MAIN);
|
||||
lv_obj_set_style_text_color(imu_label, lv_color_white(), LV_PART_MAIN);
|
||||
lv_obj_align(imu_label, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_label_set_text(imu_label, "SOUNDSHOT!");
|
||||
|
||||
// // Create a button
|
||||
// lv_obj_t *btn = lv_btn_create(scr);
|
||||
// lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
|
||||
// lv_obj_set_size(btn, 70, 30);
|
||||
|
||||
// // Add label to button
|
||||
// label = lv_label_create(btn);
|
||||
// lv_label_set_text(label, "Button");
|
||||
// lv_obj_center(label);
|
||||
|
||||
// // Create a slider
|
||||
// lv_obj_t *slider = lv_slider_create(scr);
|
||||
// lv_obj_align(slider, LV_ALIGN_BOTTOM_MID, 0, -20);
|
||||
// lv_obj_set_width(slider, 70);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void lcd_init(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initialize SPI bus");
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = PIN_NUM_CLK,
|
||||
.mosi_io_num = PIN_NUM_MOSI,
|
||||
.miso_io_num = -1,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = LCD_H_RES * LCD_V_RES * 2
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = {
|
||||
.dc_gpio_num = PIN_NUM_DC,
|
||||
.cs_gpio_num = PIN_NUM_CS,
|
||||
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
|
||||
.lcd_cmd_bits = LCD_CMD_BITS,
|
||||
.lcd_param_bits = LCD_PARAM_BITS,
|
||||
.spi_mode = 3,
|
||||
.trans_queue_depth = 10,
|
||||
//.on_color_trans_done = notify_lvgl_flush_ready,
|
||||
.user_ctx = NULL,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &io_handle));
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = PIN_NUM_RST,
|
||||
.rgb_endian = LCD_RGB_ENDIAN_BGR,
|
||||
.bits_per_pixel = LCD_BITS_PER_PIXEL,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
|
||||
|
||||
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 1, 26)); // ST7735S typically needs these offsets
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||||
}
|
||||
|
||||
static void lvgl_init(void)
|
||||
{
|
||||
lv_init();
|
||||
|
||||
|
||||
#if 1
|
||||
const lvgl_port_cfg_t lvgl_cfg = {
|
||||
.task_priority = 4, // LVGL task priority
|
||||
.task_stack = 32768, // LVGL task stack size
|
||||
.task_affinity = -1, // LVGL task can run on any core
|
||||
.task_max_sleep_ms = 500, // Maximum sleep in LVGL task
|
||||
.timer_period_ms = 5 // LVGL timer period
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(lvgl_port_init(&lvgl_cfg));
|
||||
#endif
|
||||
|
||||
const lvgl_port_display_cfg_t disp_cfg = {
|
||||
.io_handle = io_handle,
|
||||
.panel_handle = panel_handle,
|
||||
.buffer_size = LCD_H_RES * LCD_V_RES * 2,
|
||||
.double_buffer = true,
|
||||
.hres = LCD_H_RES,
|
||||
.vres = LCD_V_RES,
|
||||
.monochrome = false,
|
||||
.rotation = {
|
||||
.swap_xy = true,
|
||||
.mirror_x = true,
|
||||
.mirror_y = false,
|
||||
},
|
||||
.flags = {
|
||||
//.buff_dma = true,
|
||||
.swap_bytes = true,
|
||||
}
|
||||
};
|
||||
|
||||
disp = lvgl_port_add_disp(&disp_cfg);
|
||||
//lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void createBubble(lv_obj_t * scr)
|
||||
{
|
||||
|
||||
// 2) Create a bubble level of size 200×60, with range [−30°, +30°], initial 0°:
|
||||
lv_obj_t * level = bubble_create(scr, 150, 40, -10.0f, +10.0f, 0.0f);
|
||||
lv_obj_align(level, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
// 3) … Later, when you read your accelerometer or keypad‐derived angle …
|
||||
float new_angle = 10.0f;
|
||||
bubble_setValue(level, new_angle);
|
||||
|
||||
// 4) You can call bubble_level_set_value(level, …) as often as you like.
|
||||
// Each call invalidates the object and LVGL will call the draw callback
|
||||
// (usually on the next tick, or immediately if LVGL is idle).
|
||||
|
||||
_bubble = level;
|
||||
}
|
||||
|
||||
void ui_test()
|
||||
{
|
||||
lv_obj_t *label = lv_label_create(lv_screen_active());
|
||||
lv_label_set_text(label, "Hello, LVGL 9.2!");
|
||||
lv_obj_center(label);
|
||||
}
|
||||
|
||||
void gui_start(void)
|
||||
{
|
||||
// Initialize LCD
|
||||
lcd_init();
|
||||
|
||||
// Initialize LVGL
|
||||
lvgl_init();
|
||||
|
||||
// Create UI
|
||||
create_lvgl_demo();
|
||||
|
||||
|
||||
keypad_start();
|
||||
|
||||
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
|
||||
|
||||
xTaskCreate(gui_task, "gui_task", 4096, NULL, 5, NULL);
|
||||
|
||||
system_notifyAll(EM_EVENT_NEW_DATA);
|
||||
|
||||
}
|
||||
|
||||
static uint32_t waitForKeyPress(void)
|
||||
{
|
||||
QueueHandle_t q = keypad_getQueue();
|
||||
uint32_t ev = 0;
|
||||
|
||||
if (xQueueReceive(q, &ev, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
return ev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void handleMainMenu(void)
|
||||
{
|
||||
lvgl_port_lock(0);
|
||||
// Create a screen with black background
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
|
||||
//create_menu(scr);
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
static void gui_task(void)
|
||||
{
|
||||
system_subscribe(xTaskGetCurrentTaskHandle());
|
||||
|
||||
// Grab queue handle
|
||||
QueueHandle_t q = keypad_getQueue();
|
||||
uint32_t ev = 0;
|
||||
|
||||
|
||||
while (1)
|
||||
{
|
||||
|
||||
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
|
||||
{
|
||||
switch (ev) {
|
||||
case (KEY0 << KEY_SHORT_PRESS):
|
||||
scroll_down();
|
||||
ESP_LOGI(TAG, "MAIN: Button 1 SHORT");
|
||||
break;
|
||||
case (KEY0 << KEY_LONG_PRESS):
|
||||
ESP_LOGI(TAG, "MAIN: Button 1 LONG");
|
||||
break;
|
||||
case (KEY1 << KEY_SHORT_PRESS):
|
||||
scroll_up();
|
||||
ESP_LOGI(TAG, "MAIN: Button 2 SHORT");
|
||||
break;
|
||||
case (KEY1 << KEY_LONG_PRESS):
|
||||
ESP_LOGI(TAG, "MAIN: Button 2 LONG");
|
||||
gpio_set_level(PIN_NUM_nON, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint32_t notifiedBits = 0;
|
||||
// clear on entry (first param), wait for any bit, block forever
|
||||
xTaskNotifyWait(
|
||||
0xFFFFFFFF, // clear any old bits on entry
|
||||
0xFFFFFFFF, // clear bits on exit
|
||||
¬ifiedBits,
|
||||
portMAX_DELAY);
|
||||
|
||||
if (notifiedBits & EM_EVENT_NEW_DATA)
|
||||
{
|
||||
ImuData_t d = system_getImuData();
|
||||
|
||||
//ESP_LOGI(TAG, "Angle: %.2f", a);
|
||||
bubble_setValue(_bubble, -d.filtered[ANGLE_XY]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
7
main/gui.h
Normal file
7
main/gui.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef GUI_H
|
||||
#define GUI_H
|
||||
|
||||
|
||||
void gui_start(void);
|
||||
|
||||
#endif
|
||||
4
main/idf_component.yml
Normal file
4
main/idf_component.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
lvgl/lvgl:
|
||||
version: "^9.2.0"
|
||||
espressif/esp_lvgl_port: "^2.6.0"
|
||||
200
main/keypad.c
Normal file
200
main/keypad.c
Normal file
@@ -0,0 +1,200 @@
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "keypad.h"
|
||||
|
||||
// ───────────── CONFIGURATION ─────────────
|
||||
|
||||
typedef enum {
|
||||
BUTTON1_GPIO = GPIO_NUM_36,
|
||||
BUTTON2_GPIO = GPIO_NUM_39,
|
||||
} button_gpio_t;
|
||||
|
||||
#define BUTTON_COUNT 2
|
||||
|
||||
// If your buttons are active‐low, set this to 0. If active‐high, set it to 1.
|
||||
#define KEYPAD_INACTIVE 1
|
||||
#define KEYPAD_ACTIVE 0
|
||||
|
||||
// Now we run the task every 50 ms and assume that is enough to debounce:
|
||||
#define DEBOUNCE_INTERVAL_MS 50 // Poll once every 50 ms
|
||||
// No more stable‐count logic—one sample/50 ms is considered “debounced.”
|
||||
|
||||
// Short vs. long press threshold (in ms):
|
||||
#define LONG_PRESS_MS 1000 // ≥1000 ms = “long press”
|
||||
|
||||
// Queue length for pending button events:
|
||||
#define BTN_EVT_QUEUE_LEN 10
|
||||
|
||||
// ───────────── EVENT ENUM & QUEUE HANDLE ─────────────
|
||||
|
||||
typedef struct
|
||||
{
|
||||
keycode_t key;
|
||||
gpio_num_t gpio;
|
||||
int prevState;
|
||||
int currentState;
|
||||
TickType_t activeTime;
|
||||
uint32_t event;
|
||||
} button_t;
|
||||
|
||||
static button_t _buttons[2];
|
||||
|
||||
// The queue handle that the debounce task will push events into:
|
||||
static QueueHandle_t s_btn_evt_queue = NULL;
|
||||
|
||||
// Call this to get the queue, so the main task can xQueueReceive():
|
||||
QueueHandle_t keypad_getQueue(void) {
|
||||
return s_btn_evt_queue;
|
||||
}
|
||||
|
||||
// ───────────── INTERNAL STATE ─────────────
|
||||
|
||||
// Current debounced state: 0 = released, 1 = pressed
|
||||
static int s_debounced[2] = { 0, 0 };
|
||||
|
||||
// Tick count when each button was debounced to “pressed”
|
||||
static TickType_t s_press_tick[2] = { 0, 0 };
|
||||
|
||||
// If we suppress an individual‐button event (because it was part of a “both” press), this is true
|
||||
static bool s_suppress_event[2] = { false, false };
|
||||
|
||||
// Tracks if we are currently in “both buttons pressed” phase:
|
||||
static bool s_was_both = false;
|
||||
|
||||
// Tick count when “both buttons” first became debounced‐pressed:
|
||||
static TickType_t s_both_press_tick = 0;
|
||||
|
||||
// ───────────── TAG FOR LOGGING ─────────────
|
||||
|
||||
static const char *TAG = "btn_debounce";
|
||||
|
||||
// ───────────── GPIO SETUP ─────────────
|
||||
|
||||
static void init_button_gpio(void)
|
||||
{
|
||||
// Configure both as inputs with pull‐up (assuming active‐low buttons)
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << BUTTON1_GPIO) | (1ULL << BUTTON2_GPIO),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
|
||||
_buttons[0].activeTime = 0;
|
||||
_buttons[0].gpio = BUTTON1_GPIO;
|
||||
_buttons[0].key = KEY0;
|
||||
_buttons[0].prevState = KEYPAD_INACTIVE;
|
||||
_buttons[0].currentState = KEYPAD_INACTIVE;
|
||||
|
||||
_buttons[1].activeTime = 0;
|
||||
_buttons[1].gpio = BUTTON2_GPIO;
|
||||
_buttons[1].key = KEY1;
|
||||
_buttons[1].prevState = KEYPAD_INACTIVE;
|
||||
_buttons[1].currentState = KEYPAD_INACTIVE;
|
||||
|
||||
}
|
||||
|
||||
// ───────────── BUTTON DEBOUNCE & PRESS‐TYPE TASK ─────────────
|
||||
|
||||
static void vButtonDebounceTask(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
TickType_t xLastWake = now;
|
||||
const TickType_t long_press_thresh = pdMS_TO_TICKS(LONG_PRESS_MS);
|
||||
|
||||
while (1) {
|
||||
|
||||
now = xTaskGetTickCount();
|
||||
|
||||
for (int i=0; i < BUTTON_COUNT; ++i)
|
||||
{
|
||||
button_t *button = &_buttons[i];
|
||||
|
||||
button->currentState = gpio_get_level(button->gpio);
|
||||
|
||||
if (button->currentState == KEYPAD_ACTIVE)
|
||||
{
|
||||
// just pressed
|
||||
if (button->prevState == KEYPAD_INACTIVE)
|
||||
{
|
||||
button->activeTime = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((now - button->activeTime) >= long_press_thresh)
|
||||
{
|
||||
if (button->event == 0)
|
||||
{
|
||||
ESP_LOGI(TAG, "%d LONG", button->key);
|
||||
button->event = (button->key << KEY_LONG_PRESS);
|
||||
xQueueSend(s_btn_evt_queue, &button->event, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
if (button->currentState == KEYPAD_INACTIVE)
|
||||
{
|
||||
// just released
|
||||
if (button->prevState == KEYPAD_ACTIVE)
|
||||
{
|
||||
if (button->event == 0)
|
||||
{
|
||||
ESP_LOGI(TAG, "%d SHORT", button->key);
|
||||
button->event = (button->key << KEY_SHORT_PRESS);
|
||||
xQueueSend(s_btn_evt_queue, &button->event, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(TAG, "%d LONG RELEASE", button->key);
|
||||
}
|
||||
button->event = 0;
|
||||
|
||||
}
|
||||
button->activeTime = 0;
|
||||
}
|
||||
|
||||
button->prevState = button->currentState;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 7) Wait 50 ms and repeat
|
||||
vTaskDelayUntil(&xLastWake, pdMS_TO_TICKS(DEBOUNCE_INTERVAL_MS));
|
||||
xLastWake = now;
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────── PUBLIC “START” FUNCTION ─────────────
|
||||
|
||||
void keypad_start(void)
|
||||
{
|
||||
// 1) Create the queue for button events
|
||||
s_btn_evt_queue = xQueueCreate(BTN_EVT_QUEUE_LEN, sizeof(uint32_t));
|
||||
if (s_btn_evt_queue == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create button event queue");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) Initialize GPIOs & initial state
|
||||
init_button_gpio();
|
||||
|
||||
// 3) Launch the FreeRTOS task (low priority)
|
||||
xTaskCreate(vButtonDebounceTask,
|
||||
"btn_debounce",
|
||||
2048,
|
||||
NULL,
|
||||
tskIDLE_PRIORITY + 1,
|
||||
NULL);
|
||||
|
||||
}
|
||||
|
||||
22
main/keypad.h
Normal file
22
main/keypad.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef _KEYPAD_H
|
||||
#define _KEYPAD_H
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
KEY0 = (1 << 0),
|
||||
KEY1 = (1 << 1),
|
||||
} keycode_t;
|
||||
|
||||
|
||||
#define KEY_SHORT_PRESS 0
|
||||
#define KEY_LONG_PRESS 4
|
||||
|
||||
|
||||
|
||||
void keypad_start(void);
|
||||
QueueHandle_t keypad_getQueue(void);
|
||||
|
||||
#endif
|
||||
349
main/lsm6dsv.c
Normal file
349
main/lsm6dsv.c
Normal file
@@ -0,0 +1,349 @@
|
||||
#include <math.h>
|
||||
#include "lsm6dsv.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char *TAG = "LSM6DSV";
|
||||
static const int I2C_PORT = 0;
|
||||
|
||||
// I2C helper functions
|
||||
static esp_err_t i2c_write_reg(uint8_t reg_addr, uint8_t data) {
|
||||
uint8_t write_buf[2] = {reg_addr, data};
|
||||
return i2c_master_write_to_device(I2C_PORT, LSM6DSV_ADDR, write_buf, sizeof(write_buf), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
static esp_err_t i2c_read_reg(uint8_t reg_addr, uint8_t *data) {
|
||||
return i2c_master_write_read_device(I2C_PORT, LSM6DSV_ADDR, ®_addr, 1, data, 1, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts uint16_t half-precision number into a uint32_t single-precision number.
|
||||
* @param h half-precision number
|
||||
* @param f pointer where the result of the conversion is written
|
||||
* @retval 0
|
||||
*/
|
||||
esp_err_t npy_halfbits_to_floatbits(uint16_t h, uint32_t *f)
|
||||
{
|
||||
uint16_t h_exp, h_sig;
|
||||
uint32_t f_sgn, f_exp, f_sig;
|
||||
|
||||
h_exp = (h & 0x7c00u);
|
||||
f_sgn = ((uint32_t)h & 0x8000u) << 16;
|
||||
switch (h_exp) {
|
||||
case 0x0000u: /* 0 or subnormal */
|
||||
h_sig = (h & 0x03ffu);
|
||||
/* Signed zero */
|
||||
if (h_sig == 0) {
|
||||
*f = f_sgn;
|
||||
return ESP_OK;
|
||||
}
|
||||
/* Subnormal */
|
||||
h_sig <<= 1;
|
||||
while ((h_sig & 0x0400u) == 0) {
|
||||
h_sig <<= 1;
|
||||
h_exp++;
|
||||
}
|
||||
f_exp = ((uint32_t)(127 - 15 - h_exp)) << 23;
|
||||
f_sig = ((uint32_t)(h_sig & 0x03ffu)) << 13;
|
||||
*f = f_sgn + f_exp + f_sig;
|
||||
return ESP_OK;
|
||||
case 0x7c00u: /* inf or NaN */
|
||||
/* All-ones exponent and a copy of the significand */
|
||||
*f = f_sgn + 0x7f800000u + (((uint32_t)(h & 0x03ffu)) << 13);
|
||||
return ESP_OK;
|
||||
default: /* normalized */
|
||||
/* Just need to adjust the exponent and shift */
|
||||
*f = f_sgn + (((uint32_t)(h & 0x7fffu) + 0x1c000u) << 13);
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t npy_half_to_float(uint16_t h, float *f)
|
||||
{
|
||||
union {
|
||||
float ret;
|
||||
uint32_t retbits;
|
||||
} conv;
|
||||
npy_halfbits_to_floatbits(h, &conv.retbits);
|
||||
*f = conv.ret;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute quaternions.
|
||||
* @param quat results of the computation
|
||||
* @param sflp raw value of the quaternions
|
||||
* @retval 0
|
||||
*/
|
||||
esp_err_t sflp2q(float quat[4], uint16_t sflp[3])
|
||||
{
|
||||
float sumsq = 0;
|
||||
|
||||
npy_half_to_float(sflp[0], &quat[0]);
|
||||
npy_half_to_float(sflp[1], &quat[1]);
|
||||
npy_half_to_float(sflp[2], &quat[2]);
|
||||
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
sumsq += quat[i] * quat[i];
|
||||
}
|
||||
|
||||
if (sumsq > 1.0f) {
|
||||
float n = sqrtf(sumsq);
|
||||
quat[0] /= n;
|
||||
quat[1] /= n;
|
||||
quat[2] /= n;
|
||||
sumsq = 1.0f;
|
||||
}
|
||||
|
||||
quat[3] = sqrtf(1.0f - sumsq);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void quaternion_to_euler(float qx, float qy, float qz, float qw, float *roll, float *pitch, float *yaw) {
|
||||
// Roll (X-axis rotation)
|
||||
*roll = atan2f(2.0f * (qw * qx + qy * qz), 1.0f - 2.0f * (qx * qx + qy * qy));
|
||||
|
||||
// Pitch (Y-axis rotation)
|
||||
*pitch = asinf(2.0f * (qw * qy - qz * qx));
|
||||
|
||||
// Yaw (Z-axis rotation)
|
||||
*yaw = atan2f(2.0f * (qw * qz + qx * qy), 1.0f - 2.0f * (qy * qy + qz * qz));
|
||||
}
|
||||
|
||||
esp_err_t lsm6dsv_init(int scl_pin, int sda_pin) {
|
||||
// Configure I2C
|
||||
i2c_config_t conf = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = sda_pin,
|
||||
.scl_io_num = scl_pin,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 400000 // 400 KHz
|
||||
};
|
||||
|
||||
esp_err_t ret = i2c_param_config(I2C_PORT, &conf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "I2C parameter configuration failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = i2c_driver_install(I2C_PORT, I2C_MODE_MASTER, 0, 0, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "I2C driver installation failed");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Verify device ID
|
||||
uint8_t who_am_i;
|
||||
ret = i2c_read_reg(LSM6DSV_WHO_AM_I, &who_am_i);
|
||||
if (ret != ESP_OK || who_am_i != 0x70) { // 0x70 is the expected WHO_AM_I value
|
||||
ESP_LOGE(TAG, "Failed to verify device ID or incorrect device (expected: 0x70, got: 0x%02x)", who_am_i);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Configure accelerometer (±2g, 104 Hz)
|
||||
ret = i2c_write_reg(LSM6DSV_CTRL1, 0x44); // ODR = 104 Hz (0x04), ±2g (0x40)
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure accelerometer");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Verify accelerometer configuration
|
||||
uint8_t ctrl1_val;
|
||||
ret = i2c_read_reg(LSM6DSV_CTRL1, &ctrl1_val);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "CTRL1 value: 0x%02x", ctrl1_val);
|
||||
}
|
||||
|
||||
// Configure gyroscope (±250 dps, 104 Hz)
|
||||
ret = i2c_write_reg(LSM6DSV_CTRL2, 0x04); // ODR = 104 Hz (0x04), ±250 dps (0x40)
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure gyroscope");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Verify gyroscope configuration
|
||||
uint8_t regval, tmp1;
|
||||
ret = i2c_read_reg(LSM6DSV_CTRL2, ®val);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "CTRL2 value: 0x%02x", regval);
|
||||
}
|
||||
|
||||
|
||||
// ret = i2c_read_reg(LSM6DSV_SFLP_ODR, ®val);
|
||||
// regval |= SFLP_ODR_15HZ;
|
||||
// ret = i2c_write_reg(LSM6DSV_SFLP_ODR, regval);
|
||||
|
||||
// // get current value of FUNC_CFG_ACCESS
|
||||
// ret = i2c_read_reg(LSM6DSV_FUNC_CFG_ACCESS, ®val);
|
||||
|
||||
// // enable embedded function access
|
||||
// regval |= LSM6DSV_EMB_FUNC_REG_ACCESS;
|
||||
// ret = i2c_write_reg(LSM6DSV_FUNC_CFG_ACCESS, regval);
|
||||
|
||||
|
||||
// // enable game vector FIDO
|
||||
// ret = i2c_write_reg(LSM6DSV_EMB_FUNC_FIFO_EN_A, SFLP_GAME_FIFO_EN);
|
||||
|
||||
// // enable sensor fusion algorithm
|
||||
// ret = i2c_write_reg(LSM6DSV_EMB_FUNC_EN_A, SFLP_GAME_EN);
|
||||
// ret = i2c_write_reg(LSM6DSV_EMB_FUNC_INIT_A, SFLP_GAME_INIT);
|
||||
|
||||
|
||||
// // restore FUNC_CFG_ACCESS
|
||||
// regval &= ~LSM6DSV_EMB_FUNC_REG_ACCESS;
|
||||
// ret = i2c_write_reg(LSM6DSV_FUNC_CFG_ACCESS, regval);
|
||||
|
||||
|
||||
// ret = i2c_write_reg(LSM6DSV_FIFO_CTRL4, 0x06);
|
||||
|
||||
// ret = i2c_read_reg(LSM6DSV_FIFO_CTRL4, ®val);
|
||||
// if (ret == ESP_OK) {
|
||||
// ESP_LOGI(TAG, "FIFO_CTRL4 value: 0x%02x", regval);
|
||||
// }
|
||||
|
||||
// Add a small delay after configuration
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
|
||||
ESP_LOGI(TAG, "LSM6DSV initialized successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lsm6dsv_read_data(lsm6dsv_data_t *data) {
|
||||
if (data == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_err_t ret;
|
||||
uint8_t buffer[12];
|
||||
uint8_t reg = LSM6DSV_OUTX_L_A;
|
||||
|
||||
// Check status register
|
||||
uint8_t status;
|
||||
ret = i2c_read_reg(LSM6DSV_STATUS, &status);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGD(TAG, "Status register: 0x%02x", status);
|
||||
if (!(status & 0x01)) { // Check if accelerometer data is ready
|
||||
ESP_LOGW(TAG, "Accelerometer data not ready");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
// Read accelerometer data (6 bytes)
|
||||
ret = i2c_master_write_read_device(I2C_PORT, LSM6DSV_ADDR, ®, 1, buffer, 6, pdMS_TO_TICKS(100));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read accelerometer data");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "ACC raw: x=0x%02x%02x y=0x%02x%02x z=0x%02x%02x",
|
||||
buffer[1], buffer[0], buffer[3], buffer[2], buffer[5], buffer[4]);
|
||||
|
||||
// Check if gyroscope data is ready
|
||||
ret = i2c_read_reg(LSM6DSV_STATUS, &status);
|
||||
if (ret == ESP_OK) {
|
||||
if (!(status & 0x02)) { // Check if gyroscope data is ready
|
||||
ESP_LOGW(TAG, "Gyroscope data not ready");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
// Read gyroscope data (6 bytes)
|
||||
reg = LSM6DSV_OUTX_L_G;
|
||||
ret = i2c_master_write_read_device(I2C_PORT, LSM6DSV_ADDR, ®, 1, buffer + 6, 6, pdMS_TO_TICKS(100));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read gyroscope data");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "GYRO raw: x=0x%02x%02x y=0x%02x%02x z=0x%02x%02x",
|
||||
buffer[7], buffer[6], buffer[9], buffer[8], buffer[11], buffer[10]);
|
||||
|
||||
// Convert accelerometer data (±2g scale)
|
||||
const float acc_scale = 2.0f / 32768.0f; // ±2g range
|
||||
data->acc_x = -(int16_t)(buffer[0] | (buffer[1] << 8)) * acc_scale;
|
||||
data->acc_y = -(int16_t)(buffer[2] | (buffer[3] << 8)) * acc_scale;
|
||||
data->acc_z = -(int16_t)(buffer[4] | (buffer[5] << 8)) * acc_scale;
|
||||
|
||||
// Convert gyroscope data (±250 dps scale)
|
||||
const float gyro_scale = 250.0f / 32768.0f; // ±250 dps range
|
||||
data->gyro_x = (int16_t)(buffer[6] | (buffer[7] << 8)) * gyro_scale;
|
||||
data->gyro_y = (int16_t)(buffer[8] | (buffer[9] << 8)) * gyro_scale;
|
||||
data->gyro_z = (int16_t)(buffer[10] | (buffer[11] << 8)) * gyro_scale;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t lsm6dsv_fifo_ready(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
uint8_t status;
|
||||
|
||||
// Check if fifo data is ready
|
||||
ret = i2c_read_reg(LSM6DSV_FIFO_STATUS2, &status);
|
||||
if (ret == ESP_OK) {
|
||||
//ESP_LOGI(TAG, "FIFO_STATUS2: %d", status);
|
||||
}
|
||||
|
||||
ret = i2c_read_reg(LSM6DSV_FIFO_STATUS1, &status);
|
||||
if (ret == ESP_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
esp_err_t lsm6dsv_fifo_read(lsm6dsv_fifo_t *fifo)
|
||||
{
|
||||
esp_err_t ret;
|
||||
uint8_t data;
|
||||
uint8_t reg;
|
||||
ret = i2c_read_reg(LSM6DSV_FIFO_DATA_OUT_TAG, &data);
|
||||
|
||||
reg = LSM6DSV_FIFO_DATA_OUT_X_L;
|
||||
ret = i2c_master_write_read_device(I2C_PORT, LSM6DSV_ADDR, ®, 1, fifo->data, 6, pdMS_TO_TICKS(100));
|
||||
|
||||
if (ret == ESP_OK)
|
||||
{
|
||||
fifo->count = (data & 0x06) >> 1;
|
||||
fifo->tag = (data & 0xf8) >> 3;
|
||||
|
||||
fifo->v[0] = (uint16_t)(fifo->data[0] | (fifo->data[1] << 8));
|
||||
fifo->v[1] = (uint16_t)(fifo->data[2] | (fifo->data[3] << 8));
|
||||
fifo->v[2] = (uint16_t)(fifo->data[4] | (fifo->data[5] << 8));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t lsm6dsv_getAttitude(lsm6dsv_fifo_t *fifo, float *roll, float *pitch, float *yaw)
|
||||
{
|
||||
float q[4];
|
||||
sflp2q(q, fifo->v);
|
||||
|
||||
float w = q[3];
|
||||
float x = q[0];
|
||||
float y = q[1];
|
||||
float z = q[2];
|
||||
|
||||
// Yaw (z-axis rotation)
|
||||
*yaw = atan2(2.0f * (x * y + w * z), w * w + x * x - y * y - z * z);
|
||||
|
||||
// Pitch (y-axis rotation)
|
||||
*pitch = -asin(2.0f * (x * z - w * y));
|
||||
|
||||
// Roll (x-axis rotation)
|
||||
*roll = atan2(2.0f * (w * x + y * z), w * w - x * x - y * y + z * z);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
}
|
||||
99
main/lsm6dsv.h
Normal file
99
main/lsm6dsv.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
// LSM6DSV Register Addresses
|
||||
#define LSM6DSV_ADDR 0x6A // I2C address (SA0=0)
|
||||
|
||||
#define LSM6DSV_EMB_FUNC_EN_A 0x04
|
||||
#define SFLP_GAME_EN (1 << 1)
|
||||
|
||||
#define LSM6DSV_FUNC_CFG_ACCESS 0x01
|
||||
#define LSM6DSV_EMB_FUNC_REG_ACCESS (1 << 7)
|
||||
|
||||
// FIFO Registers
|
||||
#define LSM6DSV_FIFO_CTRL1 0x07 // FIFO Control Register 1
|
||||
#define LSM6DSV_FIFO_CTRL2 0x08 // FIFO Control Register 2
|
||||
#define LSM6DSV_FIFO_CTRL3 0x09 // FIFO Control Register 3
|
||||
#define LSM6DSV_FIFO_CTRL4 0x0A // FIFO Control Register 4
|
||||
|
||||
#define FIFO_MODE_CONTINUOUS 0x06
|
||||
|
||||
#define LSM6DSV_FIFO_STATUS1 0x1B // FIFO Status Register 1
|
||||
#define LSM6DSV_FIFO_STATUS2 0x1C // FIFO Status Register 2
|
||||
|
||||
#define LSM6DSV_FIFO_DATA_OUT_TAG 0x78 // FIFO Data Output Register
|
||||
#define LSM6DSV_FIFO_DATA_OUT_X_L 0x79 // FIFO Data Output Register
|
||||
#define LSM6DSV_FIFO_DATA_OUT_X_H 0x7A // FIFO Data Output Register
|
||||
#define LSM6DSV_FIFO_DATA_OUT_Y_L 0x7B // FIFO Data Output Register
|
||||
#define LSM6DSV_FIFO_DATA_OUT_Y_H 0x7C // FIFO Data Output Register
|
||||
#define LSM6DSV_FIFO_DATA_OUT_Z_L 0x7D // FIFO Data Output Register
|
||||
#define LSM6DSV_FIFO_DATA_OUT_Z_H 0x7E // FIFO Data Output Register
|
||||
|
||||
|
||||
|
||||
|
||||
#define LSM6DSV_WHO_AM_I 0x0F // Device ID register
|
||||
#define LSM6DSV_CTRL1 0x10 // Control register 1
|
||||
#define LSM6DSV_CTRL2 0x11 // Control register 2
|
||||
#define LSM6DSV_FIFO_STATUS1 0x1B
|
||||
#define LSM6DSV_STATUS 0x1E // Status register
|
||||
#define LSM6DSV_OUTX_L_A 0x28 // Accelerometer X-axis LSB
|
||||
#define LSM6DSV_OUTX_H_A 0x29 // Accelerometer X-axis MSB
|
||||
#define LSM6DSV_OUTY_L_A 0x2A // Accelerometer Y-axis LSB
|
||||
#define LSM6DSV_OUTY_H_A 0x2B // Accelerometer Y-axis MSB
|
||||
#define LSM6DSV_OUTZ_L_A 0x2C // Accelerometer Z-axis LSB
|
||||
#define LSM6DSV_OUTZ_H_A 0x2D // Accelerometer Z-axis MSB
|
||||
#define LSM6DSV_OUTX_L_G 0x22 // Gyroscope X-axis LSB
|
||||
#define LSM6DSV_OUTX_H_G 0x23 // Gyroscope X-axis MSB
|
||||
#define LSM6DSV_OUTY_L_G 0x24 // Gyroscope Y-axis LSB
|
||||
#define LSM6DSV_OUTY_H_G 0x25 // Gyroscope Y-axis MSB
|
||||
#define LSM6DSV_OUTZ_L_G 0x26 // Gyroscope Z-axis LSB
|
||||
#define LSM6DSV_OUTZ_H_G 0x27 // Gyroscope Z-axis MSB
|
||||
|
||||
#define LSM6DSV_SFLP_ODR 0x5E
|
||||
#define SFLP_ODR_15HZ (0 << 3)
|
||||
#define SFLP_ODR_30HZ (1 << 3)
|
||||
#define SFLP_ODR_60HZ (2 << 3)
|
||||
#define SFLP_ODR_120HZ (3 << 3)
|
||||
#define SFLP_ODR_240HZ (4 << 3)
|
||||
#define SFLP_ODR_480HZ (5 << 3)
|
||||
|
||||
|
||||
#define LSM6DSV_EMB_FUNC_FIFO_EN_A 0x44
|
||||
#define SFLP_GAME_FIFO_EN (1 << 1)
|
||||
|
||||
#define LSM6DSV_EMB_FUNC_INIT_A 0x66
|
||||
#define SFLP_GAME_INIT (1 << 1)
|
||||
|
||||
|
||||
|
||||
typedef struct lsm6dsv_fifo_s
|
||||
{
|
||||
uint8_t tag;
|
||||
uint8_t count;
|
||||
uint8_t data[6];
|
||||
uint16_t v[3];
|
||||
|
||||
} lsm6dsv_fifo_t;
|
||||
|
||||
// Sensor data structure
|
||||
typedef struct {
|
||||
float acc_x;
|
||||
float acc_y;
|
||||
float acc_z;
|
||||
float gyro_x;
|
||||
float gyro_y;
|
||||
float gyro_z;
|
||||
} lsm6dsv_data_t;
|
||||
|
||||
// Function declarations
|
||||
esp_err_t lsm6dsv_init(int scl_pin, int sda_pin);
|
||||
esp_err_t lsm6dsv_read_data(lsm6dsv_data_t *data);
|
||||
|
||||
uint8_t lsm6dsv_fifo_ready(void);
|
||||
|
||||
esp_err_t lsm6dsv_fifo_read(lsm6dsv_fifo_t *fifo);
|
||||
|
||||
esp_err_t lsm6dsv_getAttitude(lsm6dsv_fifo_t *fifo, float *roll, float *pitch, float *yaw);
|
||||
1128
main/lv_conf.h
Normal file
1128
main/lv_conf.h
Normal file
File diff suppressed because it is too large
Load Diff
249
main/main.c
Normal file
249
main/main.c
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "math.h"
|
||||
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
|
||||
#include "bt_app_core.h"
|
||||
#include "lsm6dsv.h"
|
||||
#include "gui.h"
|
||||
#include "gpio.h"
|
||||
#include "keypad.h"
|
||||
#include "system.h"
|
||||
|
||||
|
||||
|
||||
|
||||
/*********************************
|
||||
* STATIC FUNCTION DECLARATIONS
|
||||
********************************/
|
||||
typedef struct {
|
||||
float alpha; // smoothing factor, 0<alpha<1
|
||||
float prev_output; // y[n-1]
|
||||
} LowPassFilter;
|
||||
|
||||
// Initialize the filter. alpha = smoothing factor.
|
||||
// e.g. alpha = dt/(RC+dt) if you derive from cutoff freq & sample time.
|
||||
// init_output can be 0 or your first sample to avoid startup glitch.
|
||||
static inline void LPF_Init(LowPassFilter *f, float alpha, float init_output) {
|
||||
f->alpha = alpha;
|
||||
f->prev_output = init_output;
|
||||
}
|
||||
|
||||
// Run one input sample through the filter.
|
||||
// Returns y[n] = alpha * x[n] + (1-alpha) * y[n-1].
|
||||
static inline float LPF_Update(LowPassFilter *f, float input) {
|
||||
float out = f->alpha * input + (1.0f - f->alpha) * f->prev_output;
|
||||
f->prev_output = out;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/*********************************
|
||||
* STATIC VARIABLE DEFINITIONS
|
||||
********************************/
|
||||
static const char *TAG = "main";
|
||||
|
||||
/*********************************
|
||||
* STATIC FUNCTION DEFINITIONS
|
||||
********************************/
|
||||
|
||||
static void init_gpio(void)
|
||||
{
|
||||
gpio_config_t io_conf;
|
||||
|
||||
|
||||
// Configure output GPIO
|
||||
io_conf.pin_bit_mask = (1ULL << PIN_NUM_LED_1);
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
|
||||
|
||||
|
||||
#if 1
|
||||
// Configure output power signal
|
||||
gpio_set_level(PIN_NUM_nON, 1);
|
||||
|
||||
io_conf.pin_bit_mask = (1ULL << PIN_NUM_nON);
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Configure LCD Backlight GPIO
|
||||
io_conf.pin_bit_mask = (1ULL << PIN_NUM_BK_LIGHT);
|
||||
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// IMU task to read sensor data
|
||||
static void imu_task(void *pvParameters) {
|
||||
lsm6dsv_data_t imu_data;
|
||||
char data_str[128];
|
||||
|
||||
float roll, pitch, yaw;
|
||||
lsm6dsv_fifo_t fifo;
|
||||
|
||||
ImuData_t imu;
|
||||
|
||||
LowPassFilter lpf;
|
||||
LPF_Init(&lpf, 0.2, 0);
|
||||
|
||||
while (1) {
|
||||
|
||||
// uint8_t samples = lsm6dsv_fifo_ready();
|
||||
|
||||
//ESP_LOGI(TAG, "Samples: %d", samples);
|
||||
// while (samples)
|
||||
// {
|
||||
// lsm6dsv_fifo_read(&fifo);
|
||||
|
||||
|
||||
|
||||
// // snprintf(data_str, sizeof(data_str),
|
||||
// // "tag: %d, count: %d, (%d, %d, %d)",
|
||||
// // fifo.tag, fifo.count, x, y, z);
|
||||
|
||||
// // ESP_LOGI(TAG, "%s", data_str);
|
||||
|
||||
// samples--;
|
||||
// }
|
||||
|
||||
// lsm6dsv_getAttitude(&fifo, &roll, &pitch, &yaw);
|
||||
|
||||
// snprintf(data_str, sizeof(data_str),
|
||||
// "r: %0.3f, p: %0.3f, y: %0.3f",
|
||||
// roll, pitch, yaw);
|
||||
|
||||
// ESP_LOGI(TAG, "%s", data_str);
|
||||
|
||||
float xz;
|
||||
float yz;
|
||||
float xy;
|
||||
|
||||
#if 1
|
||||
if (lsm6dsv_read_data(&imu_data) == ESP_OK)
|
||||
{
|
||||
// snprintf(data_str, sizeof(data_str),
|
||||
// "Acc: %.2f, %.2f, %.2f; "
|
||||
// "Gyro: %.2f, %.2f, %.2f (%d)\n",
|
||||
// imu_data.acc_x, imu_data.acc_y, imu_data.acc_z,
|
||||
// imu_data.gyro_x, imu_data.gyro_y, imu_data.gyro_z, lsm6dsv_fifo_ready());
|
||||
|
||||
|
||||
|
||||
|
||||
#if 1
|
||||
imu.raw[ANGLE_XZ] = atan2f((float)imu_data.acc_z, (float)imu_data.acc_x) * 180 / M_PI;
|
||||
imu.raw[ANGLE_YZ] = atan2f((float)imu_data.acc_z, (float)imu_data.acc_y) * 180 / M_PI;
|
||||
imu.raw[ANGLE_XY] = atan2f((float)imu_data.acc_x, (float)imu_data.acc_y) * 180 / M_PI;
|
||||
|
||||
|
||||
|
||||
imu.filtered[ANGLE_XY] = LPF_Update(&lpf, imu.raw[ANGLE_XY]);
|
||||
|
||||
system_setImuData(imu);
|
||||
|
||||
// snprintf(data_str, sizeof(data_str),
|
||||
// "(%.2f, %.2f, %.2f)", xy, yz, xy);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
snprintf(data_str, sizeof(data_str),
|
||||
"(%.1f, %.1f, %.1f) (%.2f, %.2f, %.2f)",
|
||||
xz, yz, xy,
|
||||
(float)imu_data.acc_x, (float)imu_data.acc_y, (float)imu_data.acc_z);
|
||||
ESP_LOGI(TAG, "%s", data_str);
|
||||
#endif
|
||||
// Update label text in LVGL task
|
||||
//lv_label_set_text(imu_label, data_str);
|
||||
|
||||
}
|
||||
#endif
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // Update every 100ms
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************
|
||||
* MAIN ENTRY POINT
|
||||
********************************/
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
|
||||
/* initialize NVS — it is used to store PHY calibration data */
|
||||
// esp_err_t 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_gpio();
|
||||
|
||||
system_init();
|
||||
|
||||
|
||||
|
||||
// Initialize IMU
|
||||
ESP_ERROR_CHECK(lsm6dsv_init(22, 21)); // SCL = IO14, SDA = IO15
|
||||
|
||||
|
||||
|
||||
// Create IMU task
|
||||
TaskHandle_t h = xTaskCreate(imu_task, "imu_task", 4096, NULL, 5, NULL);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
gui_start();
|
||||
|
||||
|
||||
|
||||
//bt_app_init();
|
||||
|
||||
|
||||
|
||||
// Main loop - LVGL task will run automatically
|
||||
while (1) {
|
||||
|
||||
// int level = gpio_get_level(PIN_NUM_BUTTON_1); // Read input GPIO
|
||||
// gpio_set_level(PIN_NUM_LED_1, level);
|
||||
|
||||
//gpio_set_level(PIN_NUM_nON, (level ? 0 : 1));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
//gui_service();
|
||||
}
|
||||
}
|
||||
104
main/system.c
Normal file
104
main/system.c
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "system.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static SystemState_t _systemState;
|
||||
|
||||
static EventGroupHandle_t _systemEvent;
|
||||
static EventManager_t _eventManager;
|
||||
|
||||
void system_init(void)
|
||||
{
|
||||
EventGroupHandle_t evt = xEventGroupCreate();
|
||||
|
||||
_eventManager.count = 0;
|
||||
_eventManager.mutex = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
void system_setImuData(ImuData_t imu)
|
||||
{
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
|
||||
_systemState.imu = imu;
|
||||
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
|
||||
//ESP_LOGI("g", "New IMU Data: %.2f", xy);
|
||||
|
||||
system_notifyAll(EM_EVENT_NEW_DATA);
|
||||
}
|
||||
|
||||
ImuData_t system_getImuData(void)
|
||||
{
|
||||
ImuData_t imu;
|
||||
xSemaphoreTake(_eventManager.mutex, portMAX_DELAY);
|
||||
|
||||
imu = _systemState.imu;
|
||||
|
||||
xSemaphoreGive(_eventManager.mutex);
|
||||
|
||||
return imu;
|
||||
}
|
||||
|
||||
void system_waitForEvent(void)
|
||||
{
|
||||
}
|
||||
|
||||
SystemState_t *system_getState(void)
|
||||
{
|
||||
return &_systemState;
|
||||
}
|
||||
|
||||
|
||||
BaseType_t system_subscribe(TaskHandle_t task) {
|
||||
|
||||
EventManager_t *em = &_eventManager;
|
||||
|
||||
ESP_LOGI("g", "Subscribe: %p", task);
|
||||
if (xSemaphoreTake(em->mutex, portMAX_DELAY) == pdFALSE) {
|
||||
return pdFAIL;
|
||||
}
|
||||
if (em->count < EM_MAX_SUBSCRIBERS) {
|
||||
// avoid duplicates?
|
||||
ESP_LOGI("g", "PASS");
|
||||
em->subscribers[em->count++] = task;
|
||||
xSemaphoreGive(em->mutex);
|
||||
return pdPASS;
|
||||
}
|
||||
xSemaphoreGive(em->mutex);
|
||||
return pdFAIL; // full
|
||||
}
|
||||
|
||||
BaseType_t system_unsubscribe(TaskHandle_t task) {
|
||||
|
||||
EventManager_t *em = &_eventManager;
|
||||
|
||||
BaseType_t removed = pdFAIL;
|
||||
if (xSemaphoreTake(em->mutex, portMAX_DELAY) == pdTRUE) {
|
||||
for (size_t i = 0; i < em->count; ++i) {
|
||||
if (em->subscribers[i] == task) {
|
||||
// shift down
|
||||
for (size_t j = i; j + 1 < em->count; ++j)
|
||||
em->subscribers[j] = em->subscribers[j+1];
|
||||
em->count--;
|
||||
removed = pdPASS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(em->mutex);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
void system_notifyAll(uint32_t eventBits) {
|
||||
EventManager_t *em = &_eventManager;
|
||||
if (xSemaphoreTake(em->mutex, portMAX_DELAY) == pdTRUE) {
|
||||
for (size_t i = 0; i < em->count; ++i) {
|
||||
// set the bits in each task's notification value
|
||||
ESP_LOGI("g", "Notify: %p", em->subscribers[i]);
|
||||
xTaskNotify(em->subscribers[i],
|
||||
eventBits,
|
||||
eSetBits);
|
||||
}
|
||||
xSemaphoreGive(em->mutex);
|
||||
}
|
||||
}
|
||||
56
main/system.h
Normal file
56
main/system.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef SYSTEM_H
|
||||
#define SYSTEM_H
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
enum
|
||||
{
|
||||
ANGLE_XY = 0,
|
||||
ANGLE_XZ,
|
||||
ANGLE_YZ,
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float raw[3];
|
||||
float filtered[3];
|
||||
} ImuData_t;
|
||||
|
||||
typedef struct SystemState_s
|
||||
{
|
||||
ImuData_t imu;
|
||||
|
||||
EventGroupHandle_t event;
|
||||
} SystemState_t;
|
||||
|
||||
|
||||
|
||||
#define EM_MAX_SUBSCRIBERS 8 // tweak as needed
|
||||
#define EM_EVENT_NEW_DATA (1UL<<0)
|
||||
#define EM_EVENT_ERROR (1UL<<1)
|
||||
// …add more event bit-masks here…
|
||||
|
||||
typedef struct {
|
||||
TaskHandle_t subscribers[EM_MAX_SUBSCRIBERS];
|
||||
size_t count;
|
||||
SemaphoreHandle_t mutex;
|
||||
} EventManager_t;
|
||||
|
||||
void system_init(void);
|
||||
void system_setImuData(ImuData_t imu);
|
||||
|
||||
ImuData_t system_getImuData(void);
|
||||
|
||||
// Subscribe (register) current task to receive events
|
||||
BaseType_t system_subscribe(TaskHandle_t task);
|
||||
|
||||
// Unsubscribe if you ever need
|
||||
BaseType_t system_unsubscribe(TaskHandle_t task);
|
||||
|
||||
// Notify all subscribers of one or more event bits
|
||||
void system_notifyAll(uint32_t eventBits);
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user