Initial commit

IMU and bubble level working
This commit is contained in:
Brent Perteet
2025-06-14 16:38:53 -05:00
commit f59eb660cf
21 changed files with 6704 additions and 0 deletions

12
main/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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 redrawn
lv_obj_invalidate(bubble);
lvgl_port_unlock();
}

13
main/bubble.h Normal file
View 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
View 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
View 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 keypadderived 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
&notifiedBits,
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
View File

@@ -0,0 +1,7 @@
#ifndef GUI_H
#define GUI_H
void gui_start(void);
#endif

4
main/idf_component.yml Normal file
View 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
View 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 activelow, set this to 0. If activehigh, 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 stablecount 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 individualbutton 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 debouncedpressed:
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 pullup (assuming activelow 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 & PRESSTYPE 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
View 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
View 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, &reg_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, &regval);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "CTRL2 value: 0x%02x", regval);
}
// ret = i2c_read_reg(LSM6DSV_SFLP_ODR, &regval);
// 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, &regval);
// // 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, &regval);
// 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, &reg, 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, &reg, 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, &reg, 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
View 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

File diff suppressed because it is too large Load Diff

249
main/main.c Normal file
View 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
View 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
View 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