Files
2025-04-15 15:25:14 +02:00

559 lines
16 KiB
C

/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_check.h>
#include <esp_spiffs.h>
#include <esp_vfs_fat.h>
#include <driver/gpio.h>
#include <driver/i2c_master.h>
#include <driver/spi_master.h>
#include <driver/sdmmc_host.h>
#include <driver/sdspi_host.h>
#include <driver/ledc.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_ops.h>
#include "bsp/m5stack_core.h"
#include "bsp/display.h"
#include "esp_lcd_ili9341.h"
#include "bsp_err_check.h"
#include "button_gpio.h"
static const char *TAG = "M5Stack";
#define BSP_IP5306_ADDR 0x75
#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
static lv_display_t *disp;
static lv_indev_t *disp_indev = NULL;
/* Button definitions */
typedef enum {
BSP_BUTTON_PREV, // Button left
BSP_BUTTON_ENTER, // Button middle
BSP_BUTTON_NEXT, // Button right
BSP_BUTTON_NUM
} bsp_button_t;
#endif // (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
static i2c_master_bus_handle_t i2c_handle = NULL;
static bool i2c_initialized = false;
static i2c_master_dev_handle_t ip5306_h = NULL;
static bool spi_initialized = false;
static sdmmc_card_t *bsp_sdcard = NULL; // Global uSD card handler
esp_err_t bsp_i2c_init(void)
{
/* I2C was initialized before */
if (i2c_initialized) {
return ESP_OK;
}
const i2c_master_bus_config_t i2c_config = {
.i2c_port = BSP_I2C_NUM,
.scl_io_num = BSP_I2C_SCL,
.sda_io_num = BSP_I2C_SDA,
.clk_source = I2C_CLK_SRC_DEFAULT,
};
BSP_ERROR_CHECK_RETURN_ERR(i2c_new_master_bus(&i2c_config, &i2c_handle));
const i2c_device_config_t ip5306_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = BSP_IP5306_ADDR,
.scl_speed_hz = CONFIG_BSP_I2C_CLK_SPEED_HZ,
};
BSP_ERROR_CHECK_RETURN_ERR(i2c_master_bus_add_device(i2c_handle, &ip5306_config, &ip5306_h));
i2c_initialized = true;
return ESP_OK;
}
esp_err_t bsp_i2c_deinit(void)
{
if (i2c_initialized) {
// Delete I2C device for IP5306 first
if (i2c_handle != NULL) {
i2c_master_bus_rm_device(ip5306_h);
ip5306_h = NULL;
}
// Then delete the I2C master bus
BSP_ERROR_CHECK_RETURN_ERR(i2c_del_master_bus(i2c_handle));
i2c_handle = NULL;
i2c_initialized = false;
}
return ESP_OK;
}
esp_err_t bsp_feature_enable(bsp_feature_t feature, bool enable)
{
esp_err_t err = ESP_OK;
/* Initialize I2C */
BSP_ERROR_CHECK_RETURN_ERR(bsp_i2c_init());
switch (feature) {
case BSP_FEATURE_LCD:
// backlight is controlled by GPIO
if (enable) {
err = bsp_display_backlight_on();
} else {
err = bsp_display_backlight_off();
}
break;
}
return err;
}
static esp_err_t bsp_spi_init(uint32_t max_transfer_sz)
{
/* SPI was initialized before */
if (spi_initialized) {
return ESP_OK;
}
ESP_LOGD(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = {
.sclk_io_num = BSP_LCD_PCLK,
.mosi_io_num = BSP_LCD_MOSI,
.miso_io_num = BSP_LCD_MISO,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.max_transfer_sz = max_transfer_sz,
};
ESP_RETURN_ON_ERROR(spi_bus_initialize(BSP_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");
spi_initialized = true;
return ESP_OK;
}
esp_err_t bsp_spiffs_mount(void)
{
esp_vfs_spiffs_conf_t conf = {
.base_path = CONFIG_BSP_SPIFFS_MOUNT_POINT,
.partition_label = CONFIG_BSP_SPIFFS_PARTITION_LABEL,
.max_files = CONFIG_BSP_SPIFFS_MAX_FILES,
#ifdef CONFIG_BSP_SPIFFS_FORMAT_ON_MOUNT_FAIL
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif
};
esp_err_t ret_val = esp_vfs_spiffs_register(&conf);
BSP_ERROR_CHECK_RETURN_ERR(ret_val);
size_t total = 0, used = 0;
ret_val = esp_spiffs_info(conf.partition_label, &total, &used);
if (ret_val != ESP_OK) {
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret_val));
} else {
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
}
return ret_val;
}
esp_err_t bsp_spiffs_unmount(void)
{
return esp_vfs_spiffs_unregister(CONFIG_BSP_SPIFFS_PARTITION_LABEL);
}
sdmmc_card_t *bsp_sdcard_get_handle(void)
{
return bsp_sdcard;
}
void bsp_sdcard_get_sdmmc_host(const int slot, sdmmc_host_t *config)
{
assert(config);
memset(config, 0, sizeof(sdmmc_host_t));
ESP_LOGE(TAG, "SD card MMC mode is not supported by HW (Shared SPI)!");
}
void bsp_sdcard_get_sdspi_host(const int slot, sdmmc_host_t *config)
{
assert(config);
sdmmc_host_t host_config = SDSPI_HOST_DEFAULT();
host_config.slot = slot;
memcpy(config, &host_config, sizeof(sdmmc_host_t));
}
void bsp_sdcard_sdmmc_get_slot(const int slot, sdmmc_slot_config_t *config)
{
assert(config);
memset(config, 0, sizeof(sdmmc_slot_config_t));
ESP_LOGE(TAG, "SD card MMC mode is not supported by HW (Shared SPI)!");
}
void bsp_sdcard_sdspi_get_slot(const spi_host_device_t spi_host, sdspi_device_config_t *config)
{
assert(config);
memset(config, 0, sizeof(sdspi_device_config_t));
config->gpio_cs = BSP_SD_SPI_CS;
config->gpio_cd = SDSPI_SLOT_NO_CD;
config->gpio_wp = SDSPI_SLOT_NO_WP;
config->gpio_int = GPIO_NUM_NC;
config->host_id = spi_host;
}
esp_err_t bsp_sdcard_sdmmc_mount(bsp_sdcard_cfg_t *cfg)
{
ESP_LOGE(TAG, "SD card MMC mode is not supported by HW (Shared SPI)!");
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t bsp_sdcard_sdspi_mount(bsp_sdcard_cfg_t *cfg)
{
sdmmc_host_t sdhost = {0};
sdspi_device_config_t sdslot = {0};
const esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_BSP_SD_FORMAT_ON_MOUNT_FAIL
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
assert(cfg);
ESP_RETURN_ON_ERROR(bsp_spi_init((BSP_LCD_H_RES * BSP_LCD_V_RES) * sizeof(uint16_t)), TAG, "");
if (!cfg->mount) {
cfg->mount = &mount_config;
}
if (!cfg->host) {
bsp_sdcard_get_sdspi_host(SDMMC_HOST_SLOT_0, &sdhost);
cfg->host = &sdhost;
}
if (!cfg->slot.sdspi) {
bsp_sdcard_sdspi_get_slot(BSP_SDSPI_HOST, &sdslot);
cfg->slot.sdspi = &sdslot;
}
#if !CONFIG_FATFS_LONG_FILENAMES
ESP_LOGW(TAG, "Warning: Long filenames on SD card are disabled in menuconfig!");
#endif
return esp_vfs_fat_sdspi_mount(BSP_SD_MOUNT_POINT, cfg->host, cfg->slot.sdspi, cfg->mount, &bsp_sdcard);
}
esp_err_t bsp_sdcard_mount(void)
{
bsp_sdcard_cfg_t cfg = {0};
return bsp_sdcard_sdspi_mount(&cfg);
}
esp_err_t bsp_sdcard_unmount(void)
{
esp_err_t ret = ESP_OK;
ret |= esp_vfs_fat_sdcard_unmount(BSP_SD_MOUNT_POINT, bsp_sdcard);
bsp_sdcard = NULL;
//TODO: Check if LCD initialized (when LCD deinit will be covered by BSP)
if (spi_initialized) {
ret |= spi_bus_free(BSP_SDSPI_HOST);
spi_initialized = false;
}
return ret;
}
esp_err_t bsp_speaker_init(void)
{
// Configure speaker GPIO as output mode
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << BSP_SPEAKER_IO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
gpio_set_level(BSP_SPEAKER_IO, 0); // Turn off speaker
return ESP_OK;
}
#define LCD_CMD_BITS 8
#define LCD_PARAM_BITS 8
#define LCD_LEDC_CH CONFIG_BSP_DISPLAY_BRIGHTNESS_LEDC_CH
esp_err_t bsp_display_brightness_init(void)
{
// Setup LEDC peripheral for PWM backlight control
const ledc_channel_config_t LCD_backlight_channel = {
.gpio_num = BSP_LCD_BACKLIGHT,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LCD_LEDC_CH,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = 1,
.duty = 0,
.hpoint = 0
};
const ledc_timer_config_t LCD_backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = 1,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
BSP_ERROR_CHECK_RETURN_ERR(ledc_timer_config(&LCD_backlight_timer));
BSP_ERROR_CHECK_RETURN_ERR(ledc_channel_config(&LCD_backlight_channel));
return ESP_OK;
}
esp_err_t bsp_display_brightness_set(int brightness_percent)
{
if (brightness_percent > 100) {
brightness_percent = 100;
}
if (brightness_percent < 0) {
brightness_percent = 0;
}
ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);
uint32_t duty_cycle = (1023 * brightness_percent) / 100; // LEDC resolution set to 10bits, thus: 100% = 1023
BSP_ERROR_CHECK_RETURN_ERR(ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle));
BSP_ERROR_CHECK_RETURN_ERR(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH));
return ESP_OK;
}
esp_err_t bsp_display_backlight_off(void)
{
return bsp_display_brightness_set(0);
}
esp_err_t bsp_display_backlight_on(void)
{
return bsp_display_brightness_set(100);
}
esp_err_t bsp_display_new(const bsp_display_config_t *config, esp_lcd_panel_handle_t *ret_panel,
esp_lcd_panel_io_handle_t *ret_io)
{
esp_err_t ret = ESP_OK;
assert(config != NULL && config->max_transfer_sz > 0);
BSP_ERROR_CHECK_RETURN_ERR(bsp_feature_enable(BSP_FEATURE_LCD, true));
/* Initialize SPI */
ESP_RETURN_ON_ERROR(bsp_spi_init(config->max_transfer_sz), TAG, "");
ESP_LOGI(TAG, "Install panel IO");
const esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = BSP_LCD_DC,
.cs_gpio_num = BSP_LCD_CS,
.pclk_hz = BSP_LCD_PIXEL_CLOCK_HZ,
.lcd_cmd_bits = LCD_CMD_BITS,
.lcd_param_bits = LCD_PARAM_BITS,
.spi_mode = 0,
.trans_queue_depth = 10,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)BSP_LCD_SPI_NUM, &io_config, ret_io), err, TAG,
"New panel IO failed");
ESP_LOGI(TAG, "Install LCD driver");
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = BSP_LCD_RST, // Shared with Touch reset
.color_space = BSP_LCD_COLOR_SPACE,
.bits_per_pixel = BSP_LCD_BITS_PER_PIXEL,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_ili9341(*ret_io, &panel_config, ret_panel), err, TAG, "New panel failed");
esp_lcd_panel_reset(*ret_panel);
esp_lcd_panel_init(*ret_panel);
#if CONFIG_BSP_M5STACK_CORE_LCD_INVERT_COLOR
esp_lcd_panel_invert_color(*ret_panel, true);
#else
esp_lcd_panel_invert_color(*ret_panel, false);
#endif
return ret;
err:
if (*ret_panel) {
esp_lcd_panel_del(*ret_panel);
}
if (*ret_io) {
esp_lcd_panel_io_del(*ret_io);
}
spi_bus_free(BSP_LCD_SPI_NUM);
return ret;
}
#if (BSP_CONFIG_NO_GRAPHIC_LIB == 0)
static lv_display_t *bsp_display_lcd_init(const bsp_display_cfg_t *cfg)
{
assert(cfg != NULL);
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
const bsp_display_config_t bsp_disp_cfg = {
.max_transfer_sz = BSP_LCD_DRAW_BUFF_SIZE * sizeof(uint16_t),
};
BSP_ERROR_CHECK_RETURN_NULL(bsp_display_new(&bsp_disp_cfg, &panel_handle, &io_handle));
esp_lcd_panel_disp_on_off(panel_handle, true);
/* Add LCD screen */
ESP_LOGD(TAG, "Add LCD screen");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = cfg->buffer_size,
.double_buffer = cfg->double_buffer,
.hres = BSP_LCD_H_RES,
.vres = BSP_LCD_V_RES,
.monochrome = false,
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
.rotation =
{
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.flags = {
.buff_dma = cfg->flags.buff_dma,
.buff_spiram = cfg->flags.buff_spiram,
#if LVGL_VERSION_MAJOR >= 9
.swap_bytes = (BSP_LCD_BIGENDIAN ? true : false),
#endif
}
};
return lvgl_port_add_disp(&disp_cfg);
}
static const button_gpio_config_t bsp_button_config[BSP_BUTTON_NUM] = {
{
.gpio_num = BSP_BUTTON_LEFT,
.active_level = 0,
},
{
.gpio_num = BSP_BUTTON_MIDDLE,
.active_level = 0,
},
{
.gpio_num = BSP_BUTTON_RIGHT,
.active_level = 0,
},
};
static lv_indev_t *bsp_display_indev_init(lv_display_t *disp)
{
const button_config_t btn_cfg = {0};
button_handle_t prev_btn = NULL;
button_handle_t next_btn = NULL;
button_handle_t enter_btn = NULL;
BSP_ERROR_CHECK_RETURN_NULL(iot_button_new_gpio_device(&btn_cfg, &bsp_button_config[BSP_BUTTON_PREV], &prev_btn));
BSP_ERROR_CHECK_RETURN_NULL(iot_button_new_gpio_device(&btn_cfg, &bsp_button_config[BSP_BUTTON_NEXT], &next_btn));
BSP_ERROR_CHECK_RETURN_NULL(iot_button_new_gpio_device(&btn_cfg, &bsp_button_config[BSP_BUTTON_ENTER], &enter_btn));
const lvgl_port_nav_btns_cfg_t btns = {
.disp = disp,
.button_prev = prev_btn,
.button_next = next_btn,
.button_enter = enter_btn
};
return lvgl_port_add_navigation_buttons(&btns);
}
lv_display_t *bsp_display_start(void)
{
bsp_display_cfg_t cfg = {.lvgl_port_cfg = ESP_LVGL_PORT_INIT_CONFIG(),
.buffer_size = BSP_LCD_DRAW_BUFF_SIZE,
.double_buffer = BSP_LCD_DRAW_BUFF_DOUBLE,
.flags = {
.buff_dma = true,
.buff_spiram = false,
}
};
cfg.lvgl_port_cfg.task_affinity = 1; /* For camera */
return bsp_display_start_with_config(&cfg);
}
lv_display_t *bsp_display_start_with_config(const bsp_display_cfg_t *cfg)
{
assert(cfg != NULL);
BSP_ERROR_CHECK_RETURN_NULL(lvgl_port_init(&cfg->lvgl_port_cfg));
BSP_ERROR_CHECK_RETURN_NULL(bsp_display_brightness_init());
BSP_NULL_CHECK(disp = bsp_display_lcd_init(cfg), NULL);
// Initialize keypad input device
BSP_NULL_CHECK(disp_indev = bsp_display_indev_init(disp), NULL);
// Turn on backlight
bsp_display_backlight_on();
return disp;
}
lv_indev_t *bsp_display_get_input_dev(void)
{
return disp_indev;
}
void bsp_display_rotate(lv_display_t *disp, lv_display_rotation_t rotation)
{
lv_disp_set_rotation(disp, rotation);
}
bool bsp_display_lock(uint32_t timeout_ms)
{
return lvgl_port_lock(timeout_ms);
}
void bsp_display_unlock(void)
{
lvgl_port_unlock();
}
esp_err_t bsp_iot_button_create(button_handle_t btn_array[], int *btn_cnt, int btn_array_size)
{
esp_err_t ret = ESP_OK;
if ((btn_array_size < BSP_BUTTON_NUM) || (btn_array == NULL)) {
return ESP_ERR_INVALID_ARG;
}
if (btn_cnt) {
*btn_cnt = 0;
}
const button_config_t btn_cfg = {0};
for (int i = 0; i < BSP_BUTTON_NUM; i++) {
ret |= iot_button_new_gpio_device(&btn_cfg, &bsp_button_config[i], &btn_array[i]);
if (btn_array[i] == NULL) {
ret = ESP_FAIL;
break;
}
if (btn_cnt) {
(*btn_cnt)++;
}
}
return ret;
}
#endif // (BSP_CONFIG_NO_GRAPHIC_LIB == 0)