commit 7f183e292853de7bbec876a5530beb7dc770d07c Author: imi415 Date: Wed Dec 29 14:27:33 2021 +0800 Initial commit. diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3003f59 --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +BasedOnStyle: Google +IndentWidth: 4 +AlignConsecutiveMacros: AcrossEmptyLines +AlignConsecutiveDeclarations: true +AlignConsecutiveAssignments: AcrossEmptyLinesAndComments +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: false + AfterStruct: false + SplitEmptyFunction: false +ColumnLimit: 120 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1455a9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build +/sdkconfig +/sdkconfig.old diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5f11dbc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "components/lvgl"] + path = components/lvgl + url = https://github.com/lvgl/lvgl.git +[submodule "main/lib/epd-spi"] + path = main/lib/epd-spi + url = https://github.com/imi415/epd-spi.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c30a410 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(esp32_s2_cal) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0f7bbe8 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := esp32_s2_cal + +include $(IDF_PATH)/make/project.mk diff --git a/README.md b/README.md new file mode 100644 index 0000000..7afca17 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Hello World Example + +Starts a FreeRTOS task to print "Hello World". + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## How to use example + +Follow detailed instructions provided specifically for this example. + +Select the instructions depending on Espressif chip installed on your development board: + +- [ESP32 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html) +- [ESP32-S2 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html) + + +## Example folder contents + +The project **hello_world** contains one source file in C language [hello_world_main.c](main/hello_world_main.c). The file is located in folder [main](main). + +ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both). + +Below is short explanation of remaining files in the project folder. + +``` +├── CMakeLists.txt +├── example_test.py Python script used for automated example testing +├── main +│   ├── CMakeLists.txt +│   ├── component.mk Component make file +│   └── hello_world_main.c +├── Makefile Makefile used by legacy GNU Make +└── README.md This is the file you are currently reading +``` + +For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide. + +## Troubleshooting + +* Program upload failure + + * Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs. + * The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again. + +## Technical support and feedback + +Please use the following feedback channels: + +* For technical queries, go to the [esp32.com](https://esp32.com/) forum +* For a feature request or bug report, create a [GitHub issue](https://github.com/espressif/esp-idf/issues) + +We will get back to you as soon as possible. diff --git a/components/lvgl b/components/lvgl new file mode 160000 index 0000000..d38eb1e --- /dev/null +++ b/components/lvgl @@ -0,0 +1 @@ +Subproject commit d38eb1e689fa5a64c25e677275172d9c8a4ab2f0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..0e0e99a --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,12 @@ +idf_component_register(SRCS + "main.c" + "helper/helper_wifi.c" + "impl/impl_epd.c" + "impl/impl_lvgl.c" + "lib/epd-spi/src/epd_common.c" + "lib/epd-spi/src/epd_panel_gdew042t2.c" + +INCLUDE_DIRS + "lib/epd-spi/include" + "impl" +) diff --git a/main/component.mk b/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/main/helper/helper_wifi.c b/main/helper/helper_wifi.c new file mode 100644 index 0000000..6f89f27 --- /dev/null +++ b/main/helper/helper_wifi.c @@ -0,0 +1,17 @@ +#include "esp_log.h" +#include "esp_spi_flash.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +esp_err_t helper_wifi_init(void) { + // Check stored credentials from NVS, + // if no credentials stored or button pressed, force initiate easy connect + + return ESP_OK; +} + +void helper_wifi_task(void *pvParameters) { + // +} \ No newline at end of file diff --git a/main/impl/impl_epd.c b/main/impl/impl_epd.c new file mode 100644 index 0000000..1ee6a87 --- /dev/null +++ b/main/impl/impl_epd.c @@ -0,0 +1,179 @@ + +#include "impl_epd.h" + +#define EPD_GPIO_MOSI GPIO_NUM_15 +#define EPD_GPIO_SCLK GPIO_NUM_14 +#define EPD_GPIO_BUSY GPIO_NUM_11 +#define EPD_GPIO_CS GPIO_NUM_13 +#define EPD_GPIO_RES GPIO_NUM_10 +#define EPD_GPIO_DC GPIO_NUM_12 + +#define EPD_SPI_HOST SPI2_HOST +#define EPD_SPI_SPEED (16 * 1000 * 1000) /* 16MHz */ +#define EPD_SPI_MAX_XFER_SIZE (1024) + +/* Pre-transfer callback for SPI driver. */ +static void impl_epd_spi_pre_transfer_cb(spi_transaction_t *txn) { + uint32_t dc_value = (uint32_t)txn->user; + gpio_set_level(EPD_GPIO_DC, dc_value); +} + +static void IRAM_ATTR impl_epd_busy_irq_handler(void *arg) { + impl_epd_handle_t *epd = arg; + BaseType_t xHigherPriorityTaskWoken; + + xSemaphoreGiveFromISR(epd->busy_semaphore, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +} + +esp_err_t impl_epd_init(void *handle) { + esp_err_t ret; + spi_bus_config_t bus_config = { + .mosi_io_num = EPD_GPIO_MOSI, + .sclk_io_num = EPD_GPIO_SCLK, + .miso_io_num = -1, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = EPD_SPI_MAX_XFER_SIZE, + }; + spi_device_interface_config_t device_config = { + .clock_speed_hz = EPD_SPI_SPEED, + .mode = 0, + .spics_io_num = EPD_GPIO_CS, + .queue_size = 7, + .pre_cb = impl_epd_spi_pre_transfer_cb, + }; + + impl_epd_handle_t *epd = handle; + + /* Initialize SPI bus */ + ret = spi_bus_initialize(EPD_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + ESP_ERROR_CHECK(ret); + + ret = spi_bus_add_device(EPD_SPI_HOST, &device_config, &epd->spi); + ESP_ERROR_CHECK(ret); + + /* Initialize RTOS components */ + epd->busy_semaphore = xSemaphoreCreateBinary(); + if (epd->busy_semaphore == NULL) { + return ESP_FAIL; + } + + /* Initialize auxillary IOs */ + gpio_set_direction(EPD_GPIO_BUSY, GPIO_MODE_INPUT); + gpio_set_direction(EPD_GPIO_DC, GPIO_MODE_OUTPUT); + gpio_set_direction(EPD_GPIO_RES, GPIO_MODE_OUTPUT); + + // Set up EPD busy interrupt + gpio_pullup_en(EPD_GPIO_BUSY); + gpio_set_intr_type(EPD_GPIO_BUSY, GPIO_INTR_POSEDGE); + gpio_isr_handler_add(EPD_GPIO_BUSY, impl_epd_busy_irq_handler, epd); + + /* Release RESET pin */ + gpio_set_level(EPD_GPIO_RES, 1); + + return ret; +} + +epd_ret_t impl_epd_write_command(void *handle, uint8_t *command, uint32_t len) { + esp_err_t ret = EPD_OK; + spi_transaction_t txn; + + impl_epd_handle_t *epd = handle; + + memset(&txn, 0x0, sizeof(txn)); + + txn.length = 8; + txn.tx_buffer = command; + txn.user = (void *)0; + + ret = spi_device_polling_transmit(epd->spi, &txn); + if (ret != ESP_OK) ret = EPD_FAIL; + + if (len > 1) { + txn.length = 8 * (len - 1); + txn.tx_buffer = &command[1]; + txn.user = (void *)1; + + ret = spi_device_polling_transmit(epd->spi, &txn); + if (ret != ESP_OK) ret = EPD_FAIL; + } + + return ret; +} + +epd_ret_t impl_epd_write_data(void *handle, uint8_t *data, uint32_t len) { + esp_err_t ret = EPD_OK; + spi_transaction_t txn; + + impl_epd_handle_t *epd = handle; + + uint8_t *dma_buffer = heap_caps_malloc(EPD_SPI_MAX_XFER_SIZE, MALLOC_CAP_DMA); + if (dma_buffer == NULL) return EPD_FAIL; + + memset(&txn, 0x0, sizeof(txn)); + + uint8_t has_partial = (len % EPD_SPI_MAX_XFER_SIZE ? 1 : 0); + uint32_t txn_count = len / EPD_SPI_MAX_XFER_SIZE + has_partial; + + txn.user = (void *)1; + + for (uint32_t i = 0; i < txn_count; i++) { + uint16_t txn_bytes = EPD_SPI_MAX_XFER_SIZE; + if ((i == txn_count - 1) && has_partial) { + txn_bytes = len % EPD_SPI_MAX_XFER_SIZE; + } + + memcpy(dma_buffer, &data[i * EPD_SPI_MAX_XFER_SIZE], txn_bytes); + + txn.length = 8 * txn_bytes; + txn.rxlength = 0; + txn.tx_buffer = dma_buffer; + + ret = spi_device_polling_transmit(epd->spi, &txn); + if (ret != ESP_OK) ret = EPD_FAIL; + } + + free(dma_buffer); + + return ret; +} + +epd_ret_t impl_epd_reset(void *handle) { + for (uint8_t i = 0; i < 4; i++) { + gpio_set_level(EPD_GPIO_RES, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(EPD_GPIO_RES, 1); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + vTaskDelay(pdMS_TO_TICKS(50)); + + return EPD_OK; +} + +epd_ret_t impl_epd_poll_busy(void *handle) { + impl_epd_handle_t *epd = handle; + + if (gpio_get_level(EPD_GPIO_BUSY) == 1) { + return EPD_OK; + } + + gpio_intr_enable(EPD_GPIO_BUSY); + + if (xSemaphoreTake(epd->busy_semaphore, pdMS_TO_TICKS(10000)) != pdTRUE) { + gpio_intr_disable(EPD_GPIO_BUSY); + + return EPD_FAIL; + } + + gpio_intr_disable(EPD_GPIO_BUSY); + + return EPD_OK; +} + +epd_ret_t impl_epd_delay(void *handle, uint32_t msec) { + vTaskDelay(pdMS_TO_TICKS(msec)); + + return EPD_OK; +} \ No newline at end of file diff --git a/main/impl/impl_epd.h b/main/impl/impl_epd.h new file mode 100644 index 0000000..ba7cb76 --- /dev/null +++ b/main/impl/impl_epd.h @@ -0,0 +1,25 @@ +#ifndef IMPL_EPD_H +#define IMPL_EPD_H + +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +// +#include "epd_panel_gdew042t2.h" + +typedef struct { + spi_device_handle_t spi; + SemaphoreHandle_t busy_semaphore; +} impl_epd_handle_t; + +esp_err_t impl_epd_init(void *handle); +epd_ret_t impl_epd_write_command(void *handle, uint8_t *command, uint32_t len); +epd_ret_t impl_epd_write_data(void *handle, uint8_t *data, uint32_t len); +epd_ret_t impl_epd_reset(void *handle); +epd_ret_t impl_epd_poll_busy(void *handle); +epd_ret_t impl_epd_delay(void *handle, uint32_t msec); + +#endif \ No newline at end of file diff --git a/main/impl/impl_lvgl.c b/main/impl/impl_lvgl.c new file mode 100644 index 0000000..3a83d8a --- /dev/null +++ b/main/impl/impl_lvgl.c @@ -0,0 +1,186 @@ +#include "impl_lvgl.h" + +#include "esp_log.h" +#include "impl_epd.h" + +#define EPD_DISPLAY_PIXEL_COUNT 400 * 300 +#define EPD_DISPLAY_FRAME_SIZE (EPD_DISPLAY_PIXEL_COUNT * 2 / 8) + +#define EPD_DISPLAY_GS 0 +#define EPD_DISPLAY_MAX_PARTIAL 6 + +static impl_epd_handle_t s_epd_impl; + +static epd_gdew042t2_t s_gd_epd = { + .cb = + { + .reset_cb = impl_epd_reset, + .write_command_cb = impl_epd_write_command, + .write_data_cb = impl_epd_write_data, + .poll_busy_cb = impl_epd_poll_busy, + .delay_cb = impl_epd_delay, + }, +#if EPD_DISPLAY_GS + .mode = EPD_GDEW042T2_MODE_GS, +#else + .mode = EPD_GDEW042T2_MODE_BW, +#endif + .user_data = &s_epd_impl, +}; + +#if !EPD_DISPLAY_GS +static uint8_t s_epd_partial_counter = EPD_DISPLAY_MAX_PARTIAL - 1; +#endif + +static lv_disp_draw_buf_t s_disp_buf; +static lv_color_t s_disp_store[EPD_DISPLAY_FRAME_SIZE]; +static lv_disp_drv_t s_disp_drv; + +static TaskHandle_t s_lv_tick_handle; +static TaskHandle_t s_lv_task_handle; +static SemaphoreHandle_t s_lv_semphr_handle; + +static void impl_lvgl_epd_set_px_cb(lv_disp_drv_t *disp_drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, + lv_color_t color, lv_opa_t opa) { + uint32_t byte_index = (buf_w * y + x) / 8; + uint8_t bit_index = 7 - (x % 8); + + uint8_t brightness = lv_color_brightness(color); + +#if EPD_DISPLAY_GS + if (brightness < 64) { + buf[byte_index] &= ~(1 << bit_index); + buf[15000 + byte_index] &= ~(1 << bit_index); + } else if (brightness < 128) { + buf[byte_index] &= ~(1 << bit_index); + buf[15000 + byte_index] |= (1 << bit_index); + } else if (brightness < 192) { + buf[byte_index] |= (1 << bit_index); + buf[15000 + byte_index] &= ~(1 << bit_index); + } else { + buf[byte_index] |= (1 << bit_index); + buf[15000 + byte_index] |= (1 << bit_index); + } +#else + uint8_t current_frame = 0; + if ((s_epd_partial_counter % 2) == 1) { + current_frame = 1; + } + + if (brightness > 128) { + buf[current_frame * 15000 + byte_index] |= (1 << bit_index); + } else { + buf[current_frame * 15000 + byte_index] &= ~(1 << bit_index); + } +#endif +} + +static void impl_lvgl_epd_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { + epd_coord_t coord = { + .x_start = 0, + .x_end = 399, + .y_start = 0, + .y_end = 299, + }; + +#if EPD_DISPLAY_GS + epd_gdew042t2_upload(&s_gd_epd, &coord, (uint8_t *)color_p, (uint8_t *)&color_p[15000]); +#else + if (s_epd_partial_counter == 0) { + s_epd_partial_counter = EPD_DISPLAY_MAX_PARTIAL - 1; + + s_gd_epd.mode = EPD_GDEW042T2_MODE_BW; + + // Flush to full + epd_gdew042t2_upload(&s_gd_epd, &coord, (uint8_t *)color_p, (uint8_t *)color_p); + } else { + s_gd_epd.mode = EPD_GDEW042T2_MODE_BW_PART; + + // Even: buffer 0, odd: buffer 1 + if ((s_epd_partial_counter % 2) == 1) { + epd_gdew042t2_upload(&s_gd_epd, &coord, (uint8_t *)color_p, (uint8_t *)&color_p[15000]); + } else { + epd_gdew042t2_upload(&s_gd_epd, &coord, (uint8_t *)&color_p[15000], (uint8_t *)color_p); + } + s_epd_partial_counter--; + } +#endif + + lv_disp_flush_ready(disp_drv); +} + +static void impl_lvgl_epd_rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) { + area->x1 = 0; + area->x2 = 399; +} + +static void impl_lvgl_log_cb(const char *buf) { ESP_LOGI("LVGL", "%s", buf); } + +void impl_lvgl_timer_task(void *pvParameters) { + for (;;) { + impl_lvgl_lock(); + lv_timer_handler(); + impl_lvgl_unlock(); + vTaskDelay(pdMS_TO_TICKS(100)); + } +} + +void impl_lvgl_tick_task(void *pvParameters) { + for (;;) { + impl_lvgl_lock(); + lv_tick_inc(50); + impl_lvgl_unlock(); + vTaskDelay(pdMS_TO_TICKS(50)); + } +} + +esp_err_t impl_lvgl_lock(void) { + if (xSemaphoreTake(s_lv_semphr_handle, portMAX_DELAY) != pdTRUE) { + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t impl_lvgl_unlock(void) { + xSemaphoreGive(s_lv_semphr_handle); + + return ESP_OK; +} + +esp_err_t impl_lvgl_init(void) { + ESP_ERROR_CHECK(impl_epd_init(&s_epd_impl)); + + lv_init(); + lv_disp_draw_buf_init(&s_disp_buf, s_disp_store, NULL, EPD_DISPLAY_PIXEL_COUNT); + lv_disp_drv_init(&s_disp_drv); + + s_disp_drv.set_px_cb = impl_lvgl_epd_set_px_cb; + s_disp_drv.flush_cb = impl_lvgl_epd_flush_cb; + s_disp_drv.rounder_cb = impl_lvgl_epd_rounder_cb; + s_disp_drv.hor_res = 400; + s_disp_drv.ver_res = 300; + s_disp_drv.draw_buf = &s_disp_buf; + s_disp_drv.full_refresh = 1; + + printf("Buffer start: %p\r\n", s_disp_store); + + lv_disp_t *disp = lv_disp_drv_register(&s_disp_drv); + if (disp == NULL) { + return ESP_FAIL; + } + + s_lv_semphr_handle = xSemaphoreCreateBinary(); + if (s_lv_semphr_handle == NULL) { + return ESP_FAIL; + } + + xSemaphoreGive(s_lv_semphr_handle); + + xTaskCreate(impl_lvgl_tick_task, "LV_TICK", 1024, NULL, 5, &s_lv_tick_handle); + xTaskCreate(impl_lvgl_timer_task, "LV_TMR", 4096, NULL, 4, &s_lv_task_handle); + + // TODO: Launch LVGL tasks below. + + return ESP_OK; +} \ No newline at end of file diff --git a/main/impl/impl_lvgl.h b/main/impl/impl_lvgl.h new file mode 100644 index 0000000..d12293c --- /dev/null +++ b/main/impl/impl_lvgl.h @@ -0,0 +1,12 @@ +#ifndef IMPL_LVGL_H +#define IMPL_LVGL_H + +#include "esp_system.h" + +#include "lvgl.h" + +esp_err_t impl_lvgl_lock(void); +esp_err_t impl_lvgl_unlock(void); +esp_err_t impl_lvgl_init(void); + +#endif \ No newline at end of file diff --git a/main/lib/epd-spi b/main/lib/epd-spi new file mode 160000 index 0000000..e08d953 --- /dev/null +++ b/main/lib/epd-spi @@ -0,0 +1 @@ +Subproject commit e08d953fcb0f8483f72c7b232494d421c22f2447 diff --git a/main/main.c b/main/main.c new file mode 100644 index 0000000..adf039c --- /dev/null +++ b/main/main.c @@ -0,0 +1,55 @@ +#include + +#include "driver/gpio.h" +#include "esp_spi_flash.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "sdkconfig.h" +// +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +// +#include "impl_lvgl.h" + +static esp_err_t app_init_nvs(void) { + 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(); + } + + return ret; +} + +static esp_err_t app_init_gpio(void) { return gpio_install_isr_service(0); } + +esp_err_t impl_lvgl_init(void); + +void app_main(void) { + ESP_ERROR_CHECK(app_init_nvs()); + ESP_ERROR_CHECK(app_init_gpio()); + + ESP_ERROR_CHECK(impl_lvgl_init()); + + /*Create a style for the shadow*/ + impl_lvgl_lock(); + lv_obj_t *test_label = lv_label_create(lv_scr_act()); + lv_label_set_recolor(test_label, true); + lv_label_set_text(test_label, "#000000 Test Label with# #808080 ReColor #"); + lv_obj_set_width(test_label, 400); + lv_obj_set_style_text_align(test_label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_align(test_label, LV_ALIGN_CENTER, 0, -25); + impl_lvgl_unlock(); + + char text_buf[32]; + + /* Dead loop */ + for (;;) { + vTaskDelay(pdMS_TO_TICKS(2000)); + snprintf(text_buf, 32, "C=%d", xTaskGetTickCount()); + impl_lvgl_lock(); + lv_label_set_text(test_label, text_buf); + lv_obj_set_style_text_align(test_label, LV_TEXT_ALIGN_CENTER, 0); + impl_lvgl_unlock(); + } +}