Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //////////////////////////////////
- // EXAMPLE
- // Uses htcw_gfx with an m5 stack
- // core2 to demonstrate the vector
- // canvas, and DMA enabled draws
- //////////////////////////////////
- // If defined the clock face is only drawn once on startup
- // and then stored in a bitmap. It is copied over on
- // every iteration, instead of being redrawn every time
- #define BUFFER_FACE
- #if __has_include(<Arduino.h>)
- #include <Arduino.h>
- #else
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "freertos/semphr.h"
- #endif
- #include <time.h>
- #include <memory.h>
- #include <driver/gpio.h>
- #include <driver/spi_master.h>
- // ili9342 panel driver codewitch-honey-crisis/htcw_esp_lcd_panel_ili9342
- #include <esp_lcd_panel_ili9342.h>
- #include <esp_lcd_panel_io.h>
- #include <esp_lcd_panel_ops.h>
- #include <esp_lcd_panel_vendor.h>
- // cross platform i2c init codewitch-honey-crisis/htcw_esp_i2c
- #include <esp_i2c.hpp>
- // core2 power management codewitch-honey-crisis/htcw_m5core2_power
- #include <m5core2_power.hpp>
- // touch screen codewitch-honey-crisis/htcw_ft6336
- #include <ft6336.hpp>
- // graphics library codewitch-honey-crisis/htcw_gfx
- #include <gfx.hpp>
- // import the htcw_gfx graphics library namespace
- using namespace gfx;
- // import the appropriate namespace for our other libraries
- #ifdef ARDUINO
- namespace arduino {}
- using namespace arduino;
- #else
- namespace esp_idf {}
- using namespace esp_idf;
- #endif
- // clock settings (not const, so we can alter them later)
- static uint16_t face_border_width = 2;
- static vector_pixel face_border_color = color<vector_pixel>::black;
- static vector_pixel face_color = color<vector_pixel>::white;
- static vector_pixel tick_border_color = color<vector_pixel>::gray;
- static vector_pixel tick_color = color<vector_pixel>::gray;
- static uint16_t tick_border_width = 2;
- static vector_pixel minute_color = color<vector_pixel>::black;
- static vector_pixel minute_border_color = color<vector_pixel>::black;
- static uint16_t minute_border_width = 2;
- static vector_pixel hour_color = color<vector_pixel>::black;
- static vector_pixel hour_border_color = color<vector_pixel>::black;
- static uint16_t hour_border_width = 2;
- static vector_pixel second_color = color<vector_pixel>::red;
- static vector_pixel second_border_color = color<vector_pixel>::red;
- static uint16_t second_border_width = 2;
- // declare the power management driver
- m5core2_power power(esp_i2c<1,21,22>::instance);
- // declare the touch driver
- ft6336<320,280> touch(esp_i2c<1,21,22>::instance);
- // declare a bitmap type for our frame buffer type (RGB565)
- using fb_t = bitmap<rgb_pixel<16>>;
- // get a color pseudo-enum for our bitmap type
- using color_t = color<typename fb_t::pixel_type>;
- // lcd data
- // This works out to be 32KB - the max DMA transfer size
- static const size16 lcd_transfer_buffer_size(128,128);
- // we use two transfer buffers to send data to the display
- // in order to maximize throughput using DMA (ESP32)
- static uint8_t* lcd_transfer_buffer1=nullptr;
- static uint8_t* lcd_transfer_buffer2=nullptr;
- // 0 = no flushes in progress, otherwise flushing
- static volatile int lcd_flushing = 0;
- static esp_lcd_panel_handle_t lcd_handle = nullptr;
- // indicates the LCD DMA transfer is complete
- static bool lcd_flush_ready(esp_lcd_panel_io_handle_t panel_io,
- esp_lcd_panel_io_event_data_t *edata,
- void *user_ctx)
- {
- lcd_flushing = 0;
- return true;
- }
- // waits for there to be a free buffer
- static void lcd_wait_dma()
- {
- while (lcd_flushing>1);
- }
- // indicates we're about to start drawing
- static void lcd_begin_draw()
- {
- lcd_wait_dma();
- ++lcd_flushing;
- }
- // flush a bitmap to the display
- static void lcd_flush_bitmap(int x1, int y1, int x2, int y2, const void *bitmap)
- {
- // adjust end coordinates for a quirk of Espressif's API (add 1 to each)
- esp_lcd_panel_draw_bitmap(lcd_handle, x1, y1, x2 + 1, y2 + 1, (void *)bitmap);
- }
- // initialize the screen using the esp panel API
- // htcw_gfx no longer has intrinsic display driver support
- // for performance and flash size reasons
- // here we use the ESP LCD Panel API for it
- static void lcd_panel_init()
- {
- // get the size in bytes of the transfer buffer(s)
- const size_t tb_size = fb_t::sizeof_buffer(lcd_transfer_buffer_size);
- // configure the SPI bus
- spi_bus_config_t buscfg;
- memset(&buscfg, 0, sizeof(buscfg));
- buscfg.sclk_io_num = 18;
- buscfg.mosi_io_num = 23;
- buscfg.miso_io_num = -1;
- buscfg.quadwp_io_num = -1;
- buscfg.quadhd_io_num = -1;
- // declare enough space for the transfer buffers + 8 bytes SPI DMA overhead
- buscfg.max_transfer_sz = tb_size + 8;
- // Initialize the SPI bus on VSPI (SPI3)
- spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
- esp_lcd_panel_io_handle_t io_handle = NULL;
- esp_lcd_panel_io_spi_config_t io_config;
- memset(&io_config, 0, sizeof(io_config));
- io_config.dc_gpio_num = 15;
- io_config.cs_gpio_num = 5;
- io_config.pclk_hz = 40 * 1000 * 1000;
- io_config.lcd_cmd_bits = 8;
- io_config.lcd_param_bits = 8;
- io_config.spi_mode = 0;
- io_config.trans_queue_depth = 10;
- io_config.on_color_trans_done = lcd_flush_ready;
- // Attach the LCD to the SPI bus
- esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &io_handle);
- lcd_handle = NULL;
- esp_lcd_panel_dev_config_t panel_config;
- memset(&panel_config, 0, sizeof(panel_config));
- panel_config.reset_gpio_num = -1;
- #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
- panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
- #else
- panel_config.color_space = ESP_LCD_COLOR_SPACE_BGR;
- #endif
- panel_config.bits_per_pixel = 16;
- // Initialize the LCD configuration
- if (ESP_OK != esp_lcd_new_panel_ili9342(io_handle, &panel_config, &lcd_handle))
- {
- printf("Error initializing LCD panel.\n");
- while(1) vTaskDelay(5);
- }
- // Reset the display
- esp_lcd_panel_reset(lcd_handle);
- // Initialize LCD panel
- esp_lcd_panel_init(lcd_handle);
- // Swap x and y axis (Different LCD screens may need different options)
- esp_lcd_panel_swap_xy(lcd_handle, false);
- esp_lcd_panel_set_gap(lcd_handle, 0, 0);
- esp_lcd_panel_mirror(lcd_handle, false, false);
- esp_lcd_panel_invert_color(lcd_handle, true);
- // Turn on the screen
- #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
- esp_lcd_panel_disp_on_off(lcd_handle, true);
- #else
- esp_lcd_panel_disp_off(lcd_handle, true);
- #endif
- // initialize the transfer buffers
- lcd_transfer_buffer1 = (uint8_t*)malloc(tb_size);
- if(lcd_transfer_buffer1==nullptr) {
- puts("Out of memory initializing transfer buffer 1");
- while(1) vTaskDelay(5);
- }
- lcd_transfer_buffer2 = (uint8_t*)malloc(tb_size);
- if(lcd_transfer_buffer2==nullptr) {
- puts("Out of memory initializing transfer buffer 2");
- while(1) vTaskDelay(5);
- }
- }
- // monitor memory usage. kind of crude
- // tracks memory usage by comparing the minimum free heap size
- // to the maximum free heap size every second before being reset
- // in loop()
- static size_t max_mem = 0;
- static size_t min_mem = (unsigned)-1;
- static SemaphoreHandle_t mem_sync = nullptr;
- static void mem_task(void*) {
- while(true) {
- size_t mem = esp_get_free_heap_size();
- xSemaphoreTake(mem_sync,portMAX_DELAY);
- if(mem>max_mem) {
- max_mem = mem;
- }
- if(mem<min_mem) {
- min_mem=mem;
- }
- xSemaphoreGive(mem_sync);
- vTaskDelay(1);
- }
- }
- // compute thetas for a rotation
- static void update_transform(float rotation, float& ctheta, float& stheta) {
- float rads = gfx::math::deg2rad(rotation); // rotation * (3.1415926536f / 180.0f);
- ctheta = cosf(rads);
- stheta = sinf(rads);
- }
- // transform a point given some thetas, a center and an offset
- static pointf transform_point(float ctheta, float stheta, pointf center, pointf offset, float x, float y) {
- float rx = (ctheta * (x - (float)center.x) - stheta * (y - (float)center.y) + (float)center.x) + offset.x;
- float ry = (stheta * (x - (float)center.x) + ctheta * (y - (float)center.y) + (float)center.y) + offset.y;
- return {(float)rx, (float)ry};
- }
- // declare the canvas for our clock.
- // the dimensions can be set any time before
- // initialize() is called. Once initialized,
- // the dimensions cannot be changed
- static canvas clock_canvas({128,128});
- // understanding the canvas is easy if you know SVG, since
- // all the commands have a rough SVG corollary.
- void draw_clock_face() {
- constexpr static const float rot_step = 360.0f / 12.0f;
- pointf offset(0, 0);
- pointf center(0, 0);
- float rotation(0);
- float ctheta, stheta;
- ssize16 size = (ssize16)clock_canvas.dimensions();
- rectf b = gfx::sizef(size.width, size.height).bounds();
- b.inflate_inplace(-face_border_width - 1, -face_border_width - 1);
- float w = b.width();
- float h = b.height();
- if(w>h) w= h;
- rectf sr(0, w / 30, w / 30, w / 5);
- sr.center_horizontal_inplace(b);
- center = gfx::pointf(w * 0.5f + face_border_width + 1, w * 0.5f + face_border_width + 1);
- clock_canvas.fill_color(face_color);
- clock_canvas.stroke_color(face_border_color);
- clock_canvas.stroke_width(face_border_width);
- clock_canvas.circle(center, center.x - 1);
- clock_canvas.render();
- bool toggle = false;
- clock_canvas.stroke_color(tick_border_color);
- clock_canvas.fill_color(tick_color);
- clock_canvas.stroke_width(tick_border_width);
- for (float rot = 0; rot < 360.0f; rot += rot_step) {
- rotation = rot;
- update_transform(rotation, ctheta, stheta);
- toggle = !toggle;
- if (toggle) {
- clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y1));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y1));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
- clock_canvas.close_path();
- } else {
- clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y1));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y1));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2 - sr.height() * 0.5f));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2 - sr.height() * 0.5f));
- clock_canvas.close_path();
- }
- clock_canvas.render();
- }
- }
- // our current "time" used to position the clock hands
- static time_t current_time = 0;
- // the bitmaps used for our transfer buffers
- fb_t tbmp;
- fb_t tbmp2;
- #ifdef BUFFER_FACE
- // the bitmap used to hold the clock face
- fb_t fbmp;
- #endif
- // the rectangle where the clock lives
- srect16 clock_rect = (ssize16(128,128).bounds()).center(ssize16(320,240).bounds());
- #ifdef ARDUINO
- // entry point (arduino)
- void setup()
- {
- Serial.begin(115200);
- #else
- // arduino compat shims:
- static uint32_t millis() {
- return pdTICKS_TO_MS(xTaskGetTickCount());
- }
- void loop();
- // entry point (esp-idf)
- extern "C" void app_main()
- {
- #endif
- // initialize the AXP192 in the core 2
- power.initialize();
- // initialize the LCD
- lcd_panel_init();
- // initialize the touch panel
- touch.initialize();
- // create a semaphore to sync access to our memory data
- mem_sync = xSemaphoreCreateMutex();
- // create a task on the other core to monitor memory
- TaskHandle_t mem_task_handle = nullptr;
- xTaskCreatePinnedToCore(mem_task,"mem_task",1024,nullptr,10,&mem_task_handle,1-xPortGetCoreID());
- // fill the screen:
- // we transfer the bitmap 6 times
- // to fill 320x240.
- tbmp=fb_t({320,40},lcd_transfer_buffer1);
- tbmp.fill(tbmp.bounds(),color_t::white);
- for(int y = 0;y<240;y+=40) {
- lcd_begin_draw();
- lcd_flush_bitmap(0,y,319,y+39,tbmp.begin());
- }
- // initialize the transfer bitmaps
- tbmp=fb_t({128,128},lcd_transfer_buffer1);
- tbmp2=fb_t({128,128},lcd_transfer_buffer2);
- // we want the second hand color to be semitransparent
- second_color.opacity(.5); // half opacity
- // initialize the canvas
- clock_canvas.initialize();
- // get the style
- canvas_style clock_style = clock_canvas.style();
- // set some of the defaults to what we want
- // you can set these individually off of the canvas
- clock_style.fill_paint_type = paint_type::solid;
- clock_style.stroke_paint_type = paint_type::solid;
- clock_style.stroke_width = 1;
- clock_canvas.style(clock_style);
- #ifdef BUFFER_FACE
- // if indicated, we only draw the clock face once
- // to a buffer. We then copy that buffer over
- // each iteration instead of redrawing the face.
- // trades memory for speed.
- fbmp = create_bitmap_from(tbmp2,{128,128});
- // attach/bind the initialized canvas to the face bitmap
- draw::canvas(fbmp,spoint16::zero(),clock_canvas);
- fbmp.fill(fbmp.bounds(),color_t::white);
- draw_clock_face();
- #else
- // clear the transfer bitmaps
- tbmp.fill(tbmp.bounds(),color_t::white);
- tbmp2.fill(tbmp2.bounds(),color_t::white);
- #endif
- // ESP-IDF compat shim
- #ifndef ARDUINO
- while (1)
- {
- static int count = 0;
- loop();
- // tickle the watchdog periodically
- if (count++ == 4)
- {
- count = 0;
- vTaskDelay(5);
- }
- }
- #endif
- }
- void loop()
- {
- // DMA technique: The idea is to draw to one bitmap while flushing the other one.
- // That's why we have two transfer buffers. Here we choose which one
- // based on which one we used last time (there are only two)
- static int dma_toggle = 0; // which buffer to use
- fb_t& xbmp = dma_toggle?tbmp:tbmp2; // a reference to the right bitmap
- // tell the lcd API we're about to draw (waits if necessary)
- lcd_begin_draw();
- // bind the canvas to the current transfer bitmap
- draw::canvas(xbmp,spoint16::zero(),clock_canvas);
- #ifdef BUFFER_FACE
- draw::bitmap(xbmp,xbmp.bounds(),fbmp,fbmp.bounds());
- #else
- draw_clock_face();
- #endif
- pointf offset(0, 0);
- pointf center(0, 0);
- time_t time = current_time;
- float rotation(0);
- float ctheta, stheta;
- ssize16 size = (ssize16)clock_canvas.dimensions();
- rectf b = gfx::sizef(size.width, size.height).bounds();
- b.inflate_inplace(-face_border_width - 1, -face_border_width - 1);
- float w = b.width();
- float h = b.height();
- if(w>h) w= h;
- // i lay things out relative to one another. so i keep that sr rect
- // around to sort of track my current "position/bounds" within the drawing.
- // i then "move it" from one place to the next to position the next portion
- rectf sr(0, w / 30, w / 30, w / 5);
- sr.center_horizontal_inplace(b);
- center = gfx::pointf(w * 0.5f + face_border_width + 1, w * 0.5f + face_border_width + 1);
- // took some experimentation to get the numbers right
- sr = gfx::rectf(0, w / 40, w / 16, w / 2);
- sr.center_horizontal_inplace(b);
- // create a path for the minute hand:
- rotation = (fmodf(time / 60.0f, 60) / 60.0f) * 360.0f;
- update_transform(rotation, ctheta, stheta);
- clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y1));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y2 + (w / 20)));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
- clock_canvas.close_path();
- clock_canvas.fill_color(minute_color);
- clock_canvas.stroke_color(minute_border_color);
- clock_canvas.stroke_width(minute_border_width);
- clock_canvas.render(); // render the path
- // create a path for the hour hand
- sr.y1 += w / 8;
- rotation = (fmodf(time / (3600.0f), 12.0f) / (12.0f)) * 360.0f;
- update_transform(rotation, ctheta, stheta);
- clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y1));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y2 + (w / 20)));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
- clock_canvas.close_path();
- clock_canvas.fill_color(hour_color);
- clock_canvas.stroke_color(hour_border_color);
- clock_canvas.stroke_width(hour_border_width);
- clock_canvas.render(); // render the path
- // create a path for the second hand
- sr.y1 -= w / 8;
- rotation = ((time % 60) / 60.0f) * 360.0f;
- update_transform(rotation, ctheta, stheta);
- clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y1));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y2 + (w / 20)));
- clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
- clock_canvas.close_path();
- clock_canvas.fill_color(second_color);
- clock_canvas.stroke_color(second_border_color);
- clock_canvas.stroke_width(second_border_width);
- clock_canvas.render();
- // increment the face "time"
- ++current_time;
- // send what we just drew to the display
- lcd_flush_bitmap(clock_rect.x1,clock_rect.y1,clock_rect.x2,clock_rect.y2,xbmp.begin());
- // update to use the next buffer
- if(++dma_toggle) {
- dma_toggle=0;
- }
- // for calculating the frames per second.
- static int frames = 0;
- ++frames;
- static uint64_t update_ts = 0;
- // once every second:
- if(millis()>update_ts+1000) {
- update_ts=millis();
- // print the frames per second and average frame time
- printf("%d FPS - frame time: %0.2fms\n",frames,1000.f/((float)frames));
- frames = 0;
- // get the memory stats (thread safe)
- xSemaphoreTake(mem_sync,portMAX_DELAY);
- size_t mmax = max_mem;
- size_t mmin = min_mem;
- // reset the memory stats
- max_mem = 0;
- min_mem = (unsigned)-1;
- xSemaphoreGive(mem_sync);
- printf("Current mem usage: %0.2fKB\n",((float)mmax-mmin)/1024.f);
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement