commit e680528d7312704b289c4dc4ad24fa2777d75767 Author: imi415 Date: Sat Jul 23 23:04:21 2022 +0800 Initial commit. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3772daf --- /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(hello_world) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d4e24f --- /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 := hello_world + +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/example_test.py b/example_test.py new file mode 100644 index 0000000..9901d5c --- /dev/null +++ b/example_test.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +from __future__ import division, print_function, unicode_literals + +import ttfw_idf + + +@ttfw_idf.idf_example_test(env_tag='Example_GENERIC', target=['esp32', 'esp32s2', 'esp32c3'], ci_target=['esp32']) +def test_examples_hello_world(env, extra_data): + app_name = 'hello_world' + dut = env.get_dut(app_name, 'examples/get-started/hello_world') + dut.start_app() + res = dut.expect(ttfw_idf.MINIMUM_FREE_HEAP_SIZE_RE) + if not res: + raise ValueError('Maximum heap size info not found') + ttfw_idf.print_heap_size(app_name, dut.app.config_name, dut.TARGET, res[0]) + + +if __name__ == '__main__': + test_examples_hello_world() diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..975f4ae --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( +SRCS + "app_main.c" + "app_lcd_impl.c" + "lib/st7789_lcd.c" + +INCLUDE_DIRS + "lib" +) diff --git a/main/app_lcd_impl.c b/main/app_lcd_impl.c new file mode 100644 index 0000000..c11f974 --- /dev/null +++ b/main/app_lcd_impl.c @@ -0,0 +1,172 @@ +/* IDF */ +#include "esp_log.h" +#include "esp_system.h" + +/* FreeRTOS */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +/* IDF drivers */ +#include "driver/gpio.h" +#include "driver/spi_master.h" + +/* Private */ +#include "st7789_lcd.h" + +#define LCD_GPIO_RST 4 +#define LCD_GPIO_DC 2 +#define LCD_GPIO_MOSI 23 +#define LCD_GPIO_SCK 18 +#define LCD_GPIO_BL 5 + +#define LCD_SPI_HOST SPI3_HOST +#define LCD_SPI_MAX_SIZE 4096 +#define LCD_SPI_MHZ 10 + +#define APP_LOG_TAG "LCD_IMPL" + +static void lcd_impl_spi_pre_cb(spi_transaction_t *txn) { + int dc = (int)txn->user; + + gpio_set_level(LCD_GPIO_DC, dc); +} + +spi_device_handle_t lcd_impl_init(void) { + gpio_set_direction(LCD_GPIO_BL, GPIO_MODE_OUTPUT); + gpio_set_direction(LCD_GPIO_DC, GPIO_MODE_OUTPUT); + gpio_set_direction(LCD_GPIO_RST, GPIO_MODE_OUTPUT); + + gpio_set_level(LCD_GPIO_BL, 0); + gpio_set_level(LCD_GPIO_RST, 1); + gpio_set_level(LCD_GPIO_DC, 0); + + gpio_set_pull_mode(LCD_GPIO_MOSI, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(LCD_GPIO_SCK, GPIO_PULLUP_ONLY); + + esp_err_t err; + spi_device_handle_t spi_handle; + spi_bus_config_t bus_cfg = { + .mosi_io_num = LCD_GPIO_MOSI, + .sclk_io_num = LCD_GPIO_SCK, + .miso_io_num = -1, + .quadhd_io_num = -1, + .quadwp_io_num = -1, + .max_transfer_sz = LCD_SPI_MAX_SIZE, + }; + + spi_device_interface_config_t if_cfg = { + .clock_speed_hz = LCD_SPI_MHZ * 1000 * 1000, + .mode = 3, + .spics_io_num = -1, + .queue_size = 1, + .pre_cb = lcd_impl_spi_pre_cb, + }; + + err = spi_bus_initialize(LCD_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO); + if (err != ESP_OK) { + ESP_LOGE(APP_LOG_TAG, "SPI bus initialization failed"); + return NULL; + } + + err = spi_bus_add_device(LCD_SPI_HOST, &if_cfg, &spi_handle); + if (err != ESP_OK) { + ESP_LOGE(APP_LOG_TAG, "SPI add device failed"); + return NULL; + } + + return spi_handle; +} + +st7789_lcd_ret_t lcd_impl_reset(void *pdev) { + /* Hardware reset is necessary if CS not used !! */ + + ESP_LOGI(APP_LOG_TAG, "LCD reset!"); + + gpio_set_level(LCD_GPIO_RST, 0U); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(LCD_GPIO_RST, 1U); + vTaskDelay(pdMS_TO_TICKS(100)); + + return ST7789_SUCCESS; +} + +st7789_lcd_ret_t lcd_impl_delay(void *pdev, uint32_t msec) { + vTaskDelay(pdMS_TO_TICKS(msec)); + + return ST7789_SUCCESS; +} + +st7789_lcd_ret_t lcd_impl_write_command(void *pdev, uint8_t *command, uint32_t len) { + ESP_LOGD(APP_LOG_TAG, "Write CMD: %d bytes, 0x%02x...", len, command[0]); + + esp_err_t ret; + + spi_device_handle_t handle = pdev; + + spi_transaction_t txn = { + .length = 0x08, + .tx_buffer = command, + .user = (void *)0U, + }; + + ret = spi_device_polling_transmit(handle, &txn); + if (ret != ESP_OK) { + ESP_LOGE(APP_LOG_TAG, "CMD transmit failed phase 1"); + return ST7789_FAIL; + } + + /* This command has parameters */ + if (len > 1) { + txn.length = (len - 1) * 8; + txn.tx_buffer = &command[1]; + txn.user = (void *)1U; + + gpio_set_level(LCD_GPIO_DC, 1U); + ret = spi_device_polling_transmit(handle, &txn); + if (ret != ESP_OK) { + ESP_LOGE(APP_LOG_TAG, "CMD transmit failed phase 2"); + return ST7789_FAIL; + } + } + + return ST7789_SUCCESS; +} + +st7789_lcd_ret_t lcd_impl_write_data(void *pdev, uint8_t *data, uint32_t len) { + ESP_LOGD(APP_LOG_TAG, "Write DATA: %d bytes", len); + + esp_err_t ret; + spi_device_handle_t handle = pdev; + + spi_transaction_t txn; + uint32_t d_ptr = 0U; + + txn.user = (void *)1U; + + while (len > LCD_SPI_MAX_SIZE) { + txn.tx_buffer = &data[d_ptr]; + txn.length = LCD_SPI_MAX_SIZE * 8; + + ret = spi_device_polling_transmit(handle, &txn); + if (ret != ESP_OK) { + ESP_LOGE(APP_LOG_TAG, "DATA transmit failed phase 1"); + return ST7789_FAIL; + } + + len -= LCD_SPI_MAX_SIZE; + d_ptr += LCD_SPI_MAX_SIZE; + } + + if (len > 0) { + txn.tx_buffer = &data[d_ptr]; + txn.length = len * 8; + + ret = spi_device_polling_transmit(handle, &txn); + if (ret != ESP_OK) { + ESP_LOGE(APP_LOG_TAG, "DATA transmit failed phase 2"); + return ST7789_FAIL; + } + } + + return ST7789_SUCCESS; +} \ No newline at end of file diff --git a/main/app_main.c b/main/app_main.c new file mode 100644 index 0000000..3cdd674 --- /dev/null +++ b/main/app_main.c @@ -0,0 +1,70 @@ +/* Hello World Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include + +/* IDF headers */ +#include "esp_log.h" +#include "esp_spi_flash.h" +#include "esp_system.h" + +/* Drivers */ +#include "driver/spi_master.h" + +/* FreeRTOS */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +/* Config */ +#include "sdkconfig.h" + +/* LCD */ +#include "st7789_lcd.h" + +#define APP_LOG_TAG "MAIN" + +spi_device_handle_t lcd_impl_init(void); +st7789_lcd_ret_t lcd_impl_reset(void *pdev); +st7789_lcd_ret_t lcd_impl_delay(void *pdev, uint32_t msec); +st7789_lcd_ret_t lcd_impl_write_command(void *pdev, uint8_t *command, uint32_t len); +st7789_lcd_ret_t lcd_impl_write_data(void *pdev, uint8_t *data, uint32_t len); + +void app_main(void) { + ESP_LOGI(APP_LOG_TAG, "MAIN"); + + st7789_lcd_t lcd = { + .cb = + { + .write_command_cb = lcd_impl_write_command, + .write_data_cb = lcd_impl_write_data, + .reset_cb = lcd_impl_reset, + .delay_cb = lcd_impl_delay, + }, + .config = + { + .dir = ST7789_LCD_DIR_0, + .inverted = false, + .bgr_mode = false, + .pix_fmt = ST7789_LCD_PIXFMT_RGB565, + }, + .user_data = lcd_impl_init(), + }; + + if (lcd.user_data == NULL) { + ESP_LOGE(APP_LOG_TAG, "IMPL initialization failed"); + vTaskSuspend(NULL); + } + + if (st7789_lcd_init(&lcd) != ST7789_SUCCESS) { + ESP_LOGE(APP_LOG_TAG, "LCD init failed"); + } + + for (;;) { + vTaskSuspend(NULL); + } +} 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/lib/st7789_lcd.c b/main/lib/st7789_lcd.c new file mode 100644 index 0000000..a37bf8e --- /dev/null +++ b/main/lib/st7789_lcd.c @@ -0,0 +1,195 @@ +#include "st7789_lcd.h" + +#include + +#define LCD_ASSERT(x) \ + if (!(x)) \ + for (;;) { /* ABORT. */ \ + } +#define LCD_ERROR_CHECK(x) \ + if (x != ST7789_SUCCESS) return ST7789_FAIL + +static uint8_t st7789_lcd_init_sequence[] = { + 0x05, 0xB2, 0x0C, 0x0C, 0x00, 0x33, 0x33, // Porch Setting + 0x01, 0xB7, 0x35, // Gate Control + 0x01, 0xBB, 0x19, // VCOM Setting // Factory 0x19 -> 0.725V + 0x01, 0xC0, 0x2C, // LCM Control + 0x01, 0xC2, 0x01, // VDV and VRH Command Enable + 0x01, 0xC3, 0x12, // VRH Set // Factory 12H + 0x01, 0xC4, 0x20, // VDV Set + 0x01, 0xC6, 0x0F, // Frame Rate Control in Normal Mode + 0x02, 0xD0, 0xA4, 0xA1, // Power Control 1 + 0x0E, 0xE0, 0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, + 0x23, // Positive Voltage Gamma Control + 0x0E, 0xE1, 0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, + 0x23, // Negative Voltage Gamma Control + 0x00, 0x21, // Inversion On +}; + +static st7789_lcd_ret_t st7789_lcd_execute_sequence(st7789_lcd_cb_t *cb, void *user_data, uint8_t *seq, + uint32_t seq_len) { + LCD_ASSERT(cb->write_command_cb != NULL); + + uint32_t i = 0; + while (i < seq_len) { + LCD_ERROR_CHECK(cb->write_command_cb(user_data, &seq[i + 1], seq[i] + 1)); + i += seq[i] + 2; + } + + return ST7789_SUCCESS; +} + +static st7789_lcd_ret_t st7789_lcd_reset(st7789_lcd_t *lcd) { + return lcd->cb.reset_cb(lcd->user_data); +} + +static st7789_lcd_ret_t st7789_lcd_sleep(st7789_lcd_t *lcd, bool sleep) { + uint8_t tx_buf[1] = {0x11U}; + if (sleep) { + tx_buf[0] = 0x10U; + } + + return lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x01); +} + +static st7789_lcd_ret_t st7789_lcd_config(st7789_lcd_t *lcd) { + /* PIXFMT */ + uint8_t tx_buf[2] = {0x3AU, lcd->config.pix_fmt}; + + LCD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x02)); + + /* INVERSION */ + if (lcd->config.inverted) { + tx_buf[0] = 0x20U; + } else { + tx_buf[0] = 0x21U; + } + + LCD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x01)); + + tx_buf[0] = 0x36; + switch (lcd->config.dir) { + case ST7789_LCD_DIR_270: + tx_buf[1] = 0xA8; + break; + case ST7789_LCD_DIR_180: + tx_buf[1] = 0xC8; + break; + case ST7789_LCD_DIR_90: + tx_buf[1] = 0x68; + break; + case ST7789_LCD_DIR_0: + default: + tx_buf[1] = 0x08; + break; + } + + if (lcd->config.bgr_mode) { + tx_buf[1] &= ~(1 << 3U); /* RGB <-> BGR */ + } + + LCD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x02)); + + return ST7789_SUCCESS; +} + +static st7789_lcd_ret_t st7789_lcd_window(st7789_lcd_t *lcd, st7789_lcd_coord_t *coord) { + uint16_t real_x_start, real_x_end, real_y_start, real_y_end; + + uint16_t x_offset, y_offset; + switch (lcd->config.dir) { + case ST7789_LCD_DIR_0: + case ST7789_LCD_DIR_90: + x_offset = 0; + y_offset = 0; + break; + case ST7789_LCD_DIR_180: + x_offset = 0; + y_offset = 80; + break; + case ST7789_LCD_DIR_270: + x_offset = 80; + y_offset = 0; + break; + default: + x_offset = 0; + y_offset = 0; + } + + real_x_start = coord->x_start + x_offset; + real_x_end = coord->x_end + x_offset; + real_y_start = coord->y_start + y_offset; + real_y_end = coord->y_end + y_offset; + + uint8_t tx_buf[5] = { + 0x2A, + ((uint8_t)(real_x_start >> 0x08U) & 0xFFU), + (real_x_start & 0xFFU), + ((uint8_t)(real_x_end >> 0x08U) & 0xFFU), + (real_x_end & 0xFFU), + }; + + LCD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x05)); + + tx_buf[0] = 0x2BU; + tx_buf[1] = ((uint8_t)(real_y_start >> 0x08U) & 0xFFU); + tx_buf[2] = (real_y_start & 0xFFU); + tx_buf[3] = ((uint8_t)(real_y_end >> 0x08U) & 0xFFU); + tx_buf[4] = (real_y_end & 0xFFU); + + LCD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x05)); + + return ST7789_SUCCESS; +} + +st7789_lcd_ret_t st7789_lcd_init(st7789_lcd_t *lcd) { + LCD_ERROR_CHECK(st7789_lcd_reset(lcd)); + LCD_ERROR_CHECK(st7789_lcd_sleep(lcd, false)); + + LCD_ERROR_CHECK(lcd->cb.delay_cb(lcd->user_data, 120)); + + LCD_ERROR_CHECK(st7789_lcd_execute_sequence(&lcd->cb, lcd->user_data, st7789_lcd_init_sequence, + sizeof(st7789_lcd_init_sequence))); + LCD_ERROR_CHECK(st7789_lcd_config(lcd)); + LCD_ERROR_CHECK(st7789_lcd_power(lcd, true)); + + return ST7789_SUCCESS; +} + +st7789_lcd_ret_t st7789_lcd_power(st7789_lcd_t *lcd, bool on) { + uint8_t tx_buf[1] = {0x28U}; + if (on) { + tx_buf[0] = 0x29U; + } + + LCD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 1)); + + return ST7789_SUCCESS; +} + +st7789_lcd_ret_t st7789_lcd_upload(st7789_lcd_t *lcd, st7789_lcd_coord_t *coord, uint8_t *data) { + LCD_ERROR_CHECK(st7789_lcd_window(lcd, coord)); + + uint32_t data_size = (coord->y_end - coord->y_start + 1) * (coord->x_end - coord->x_start + 1); + + switch (lcd->config.pix_fmt) { + case ST7789_LCD_PIXFMT_RGB444: + data_size = data_size * 3 / 2; + break; + case ST7789_LCD_PIXFMT_RGB565: + data_size = data_size * 2; + break; + case ST7789_LCD_PIXFMT_RGB666: + case ST7789_LCD_PIXFMT_RGB888: + default: + data_size = data_size * 3; + break; + } + + uint8_t tx_buf[1] = {0x2C}; + + LCD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x01)); + LCD_ERROR_CHECK(lcd->cb.write_data_cb(lcd->user_data, data, data_size)); + + return ST7789_SUCCESS; +} \ No newline at end of file diff --git a/main/lib/st7789_lcd.h b/main/lib/st7789_lcd.h new file mode 100644 index 0000000..f84eb95 --- /dev/null +++ b/main/lib/st7789_lcd.h @@ -0,0 +1,58 @@ +#ifndef ST7789_LCD_H +#define ST7789_LCD_H + +#include +#include + +typedef enum { + ST7789_SUCCESS, + ST7789_FAIL, +} st7789_lcd_ret_t; + +typedef enum { + ST7789_LCD_DIR_0, + ST7789_LCD_DIR_90, + ST7789_LCD_DIR_180, + ST7789_LCD_DIR_270, +} st7789_lcd_dir_t; + +typedef enum { + ST7789_LCD_PIXFMT_RGB444 = 3U, + ST7789_LCD_PIXFMT_RGB565 = 5U, + ST7789_LCD_PIXFMT_RGB666 = 6U, + ST7789_LCD_PIXFMT_RGB888 = 7U, +} st7789_lcd_pixfmt_t; + +typedef struct { + st7789_lcd_ret_t (*write_command_cb)(void *handle, uint8_t *command, uint32_t len); + st7789_lcd_ret_t (*write_data_cb)(void *handle, uint8_t *data, uint32_t len); + st7789_lcd_ret_t (*reset_cb)(void *handle); + st7789_lcd_ret_t (*poll_busy_cb)(void *handle); + st7789_lcd_ret_t (*delay_cb)(void *handle, uint32_t msec); +} st7789_lcd_cb_t; + +typedef struct { + uint32_t x_start; + uint32_t x_end; + uint32_t y_start; + uint32_t y_end; +} st7789_lcd_coord_t; + +typedef struct { + st7789_lcd_dir_t dir; + st7789_lcd_pixfmt_t pix_fmt; + bool inverted; + bool bgr_mode; +} st7789_lcd_config_t; + +typedef struct { + st7789_lcd_cb_t cb; + st7789_lcd_config_t config; + void *user_data; +} st7789_lcd_t; + +st7789_lcd_ret_t st7789_lcd_init(st7789_lcd_t *lcd); +st7789_lcd_ret_t st7789_lcd_power(st7789_lcd_t *lcd, bool on); +st7789_lcd_ret_t st7789_lcd_upload(st7789_lcd_t *lcd, st7789_lcd_coord_t *coord, uint8_t *data); + +#endif \ No newline at end of file diff --git a/sdkconfig.ci b/sdkconfig.ci new file mode 100644 index 0000000..e69de29