Advertisement
honey_the_codewitch

htcw_gfx 2.0 example

Oct 19th, 2024 (edited)
245
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 18.57 KB | None | 0 0
  1. //////////////////////////////////
  2. // EXAMPLE
  3. // Uses htcw_gfx with an m5 stack
  4. // core2 to demonstrate the vector
  5. // canvas, and DMA enabled draws
  6. //////////////////////////////////
  7.  
  8. // If defined the clock face is only drawn once on startup
  9. // and then stored in a bitmap. It is copied over on
  10. // every iteration, instead of being redrawn every time
  11. #define BUFFER_FACE
  12.  
  13. #if __has_include(<Arduino.h>)
  14. #include <Arduino.h>
  15. #else
  16. #include "freertos/FreeRTOS.h"
  17. #include "freertos/task.h"
  18. #include "freertos/semphr.h"
  19. #endif
  20. #include <time.h>
  21. #include <memory.h>
  22. #include <driver/gpio.h>
  23. #include <driver/spi_master.h>
  24. // ili9342 panel driver codewitch-honey-crisis/htcw_esp_lcd_panel_ili9342
  25. #include <esp_lcd_panel_ili9342.h>
  26. #include <esp_lcd_panel_io.h>
  27. #include <esp_lcd_panel_ops.h>
  28. #include <esp_lcd_panel_vendor.h>
  29. // cross platform i2c init codewitch-honey-crisis/htcw_esp_i2c
  30. #include <esp_i2c.hpp>
  31. // core2 power management codewitch-honey-crisis/htcw_m5core2_power
  32. #include <m5core2_power.hpp>
  33. // touch screen codewitch-honey-crisis/htcw_ft6336
  34. #include <ft6336.hpp>
  35.  
  36. // graphics library codewitch-honey-crisis/htcw_gfx
  37. #include <gfx.hpp>
  38.  
  39. // import the htcw_gfx graphics library namespace
  40. using namespace gfx;
  41.  
  42. // import the appropriate namespace for our other libraries
  43. #ifdef ARDUINO
  44. namespace arduino {}
  45. using namespace arduino;
  46. #else
  47. namespace esp_idf {}
  48. using namespace esp_idf;
  49. #endif
  50.  
  51. // clock settings (not const, so we can alter them later)
  52. static uint16_t face_border_width = 2;
  53. static vector_pixel face_border_color = color<vector_pixel>::black;
  54. static vector_pixel face_color = color<vector_pixel>::white;
  55. static vector_pixel tick_border_color = color<vector_pixel>::gray;
  56. static vector_pixel tick_color = color<vector_pixel>::gray;
  57. static uint16_t tick_border_width = 2;
  58. static vector_pixel minute_color = color<vector_pixel>::black;
  59. static vector_pixel minute_border_color = color<vector_pixel>::black;
  60. static uint16_t minute_border_width = 2;
  61. static vector_pixel hour_color = color<vector_pixel>::black;
  62. static vector_pixel hour_border_color = color<vector_pixel>::black;
  63. static uint16_t hour_border_width = 2;
  64. static vector_pixel second_color = color<vector_pixel>::red;
  65. static vector_pixel second_border_color = color<vector_pixel>::red;
  66. static uint16_t second_border_width = 2;
  67.  
  68. // declare the power management driver
  69. m5core2_power power(esp_i2c<1,21,22>::instance);
  70. // declare the touch driver
  71. ft6336<320,280> touch(esp_i2c<1,21,22>::instance);
  72.  
  73. // declare a bitmap type for our frame buffer type (RGB565)
  74. using fb_t = bitmap<rgb_pixel<16>>;
  75. // get a color pseudo-enum for our bitmap type
  76. using color_t = color<typename fb_t::pixel_type>;
  77.  
  78. // lcd data
  79. // This works out to be 32KB - the max DMA transfer size
  80. static const size16 lcd_transfer_buffer_size(128,128);
  81. // we use two transfer buffers to send data to the display
  82. // in order to maximize throughput using DMA (ESP32)
  83. static uint8_t* lcd_transfer_buffer1=nullptr;
  84. static uint8_t* lcd_transfer_buffer2=nullptr;
  85. // 0 = no flushes in progress, otherwise flushing
  86. static volatile int lcd_flushing = 0;
  87. static esp_lcd_panel_handle_t lcd_handle = nullptr;
  88.  
  89. // indicates the LCD DMA transfer is complete
  90. static bool lcd_flush_ready(esp_lcd_panel_io_handle_t panel_io,
  91.                             esp_lcd_panel_io_event_data_t *edata,
  92.                             void *user_ctx)
  93. {
  94.     lcd_flushing = 0;
  95.     return true;
  96. }
  97. // waits for there to be a free buffer
  98. static void lcd_wait_dma()
  99. {
  100.     while (lcd_flushing>1);
  101. }
  102. // indicates we're about to start drawing
  103. static void lcd_begin_draw()
  104. {
  105.     lcd_wait_dma();
  106.     ++lcd_flushing;
  107. }
  108. // flush a bitmap to the display
  109. static void lcd_flush_bitmap(int x1, int y1, int x2, int y2, const void *bitmap)
  110. {
  111.     // adjust end coordinates for a quirk of Espressif's API (add 1 to each)
  112.     esp_lcd_panel_draw_bitmap(lcd_handle, x1, y1, x2 + 1, y2 + 1, (void *)bitmap);
  113. }
  114. // initialize the screen using the esp panel API
  115. // htcw_gfx no longer has intrinsic display driver support
  116. // for performance and flash size reasons
  117. // here we use the ESP LCD Panel API for it
  118. static void lcd_panel_init()
  119. {
  120.     // get the size in bytes of the transfer buffer(s)
  121.     const size_t tb_size = fb_t::sizeof_buffer(lcd_transfer_buffer_size);
  122.     // configure the SPI bus
  123.     spi_bus_config_t buscfg;
  124.     memset(&buscfg, 0, sizeof(buscfg));
  125.     buscfg.sclk_io_num = 18;
  126.     buscfg.mosi_io_num = 23;
  127.     buscfg.miso_io_num = -1;
  128.     buscfg.quadwp_io_num = -1;
  129.     buscfg.quadhd_io_num = -1;
  130.     // declare enough space for the transfer buffers + 8 bytes SPI DMA overhead
  131.     buscfg.max_transfer_sz = tb_size + 8;
  132.  
  133.     // Initialize the SPI bus on VSPI (SPI3)
  134.     spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
  135.  
  136.     esp_lcd_panel_io_handle_t io_handle = NULL;
  137.     esp_lcd_panel_io_spi_config_t io_config;
  138.     memset(&io_config, 0, sizeof(io_config));
  139.     io_config.dc_gpio_num = 15;
  140.     io_config.cs_gpio_num = 5;
  141.     io_config.pclk_hz = 40 * 1000 * 1000;
  142.     io_config.lcd_cmd_bits = 8;
  143.     io_config.lcd_param_bits = 8;
  144.     io_config.spi_mode = 0;
  145.     io_config.trans_queue_depth = 10;
  146.     io_config.on_color_trans_done = lcd_flush_ready;
  147.     // Attach the LCD to the SPI bus
  148.     esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &io_handle);
  149.  
  150.     lcd_handle = NULL;
  151.     esp_lcd_panel_dev_config_t panel_config;
  152.     memset(&panel_config, 0, sizeof(panel_config));
  153.     panel_config.reset_gpio_num = -1;
  154. #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
  155.     panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
  156. #else
  157.     panel_config.color_space = ESP_LCD_COLOR_SPACE_BGR;
  158. #endif
  159.     panel_config.bits_per_pixel = 16;
  160.  
  161.     // Initialize the LCD configuration
  162.     if (ESP_OK != esp_lcd_new_panel_ili9342(io_handle, &panel_config, &lcd_handle))
  163.     {
  164.         printf("Error initializing LCD panel.\n");
  165.         while(1) vTaskDelay(5);
  166.     }
  167.  
  168.     // Reset the display
  169.     esp_lcd_panel_reset(lcd_handle);
  170.  
  171.     // Initialize LCD panel
  172.     esp_lcd_panel_init(lcd_handle);
  173.     //  Swap x and y axis (Different LCD screens may need different options)
  174.     esp_lcd_panel_swap_xy(lcd_handle, false);
  175.     esp_lcd_panel_set_gap(lcd_handle, 0, 0);
  176.     esp_lcd_panel_mirror(lcd_handle, false, false);
  177.     esp_lcd_panel_invert_color(lcd_handle, true);
  178.     // Turn on the screen
  179. #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
  180.     esp_lcd_panel_disp_on_off(lcd_handle, true);
  181. #else
  182.     esp_lcd_panel_disp_off(lcd_handle, true);
  183. #endif
  184.  
  185.     // initialize the transfer buffers
  186.     lcd_transfer_buffer1 = (uint8_t*)malloc(tb_size);
  187.     if(lcd_transfer_buffer1==nullptr) {
  188.         puts("Out of memory initializing transfer buffer 1");
  189.         while(1) vTaskDelay(5);
  190.     }
  191.  
  192.     lcd_transfer_buffer2 = (uint8_t*)malloc(tb_size);
  193.     if(lcd_transfer_buffer2==nullptr) {
  194.         puts("Out of memory initializing transfer buffer 2");
  195.         while(1) vTaskDelay(5);
  196.     }
  197. }
  198. // monitor memory usage. kind of crude
  199. // tracks memory usage by comparing the minimum free heap size
  200. // to the maximum free heap size every second before being reset
  201. // in loop()
  202. static size_t max_mem = 0;
  203. static size_t min_mem = (unsigned)-1;
  204. static SemaphoreHandle_t mem_sync = nullptr;
  205. static void mem_task(void*) {
  206.     while(true) {
  207.         size_t mem = esp_get_free_heap_size();
  208.         xSemaphoreTake(mem_sync,portMAX_DELAY);
  209.         if(mem>max_mem) {
  210.             max_mem = mem;
  211.         }
  212.         if(mem<min_mem) {
  213.             min_mem=mem;
  214.         }
  215.         xSemaphoreGive(mem_sync);
  216.         vTaskDelay(1);
  217.     }
  218. }
  219. // compute thetas for a rotation
  220. static void update_transform(float rotation, float& ctheta, float& stheta) {
  221.     float rads = gfx::math::deg2rad(rotation); // rotation * (3.1415926536f / 180.0f);
  222.     ctheta = cosf(rads);
  223.     stheta = sinf(rads);
  224. }
  225. // transform a point given some thetas, a center and an offset
  226. static pointf transform_point(float ctheta, float stheta, pointf center, pointf offset, float x, float y) {
  227.     float rx = (ctheta * (x - (float)center.x) - stheta * (y - (float)center.y) + (float)center.x) + offset.x;
  228.     float ry = (stheta * (x - (float)center.x) + ctheta * (y - (float)center.y) + (float)center.y) + offset.y;
  229.     return {(float)rx, (float)ry};
  230. }
  231. // declare the canvas for our clock.
  232. // the dimensions can be set any time before
  233. // initialize() is called. Once initialized,
  234. // the dimensions cannot be changed
  235. static canvas clock_canvas({128,128});
  236. // understanding the canvas is easy if you know SVG, since
  237. // all the commands have a rough SVG corollary.
  238.  
  239. void draw_clock_face() {
  240.     constexpr static const float rot_step = 360.0f / 12.0f;
  241.     pointf offset(0, 0);
  242.     pointf center(0, 0);
  243.  
  244.     float rotation(0);
  245.     float ctheta, stheta;
  246.     ssize16 size = (ssize16)clock_canvas.dimensions();
  247.     rectf b = gfx::sizef(size.width, size.height).bounds();
  248.     b.inflate_inplace(-face_border_width - 1, -face_border_width - 1);
  249.     float w = b.width();
  250.     float h = b.height();
  251.     if(w>h) w= h;
  252.     rectf sr(0, w / 30, w / 30, w / 5);
  253.     sr.center_horizontal_inplace(b);
  254.     center = gfx::pointf(w * 0.5f + face_border_width + 1, w * 0.5f + face_border_width + 1);
  255.     clock_canvas.fill_color(face_color);
  256.     clock_canvas.stroke_color(face_border_color);
  257.     clock_canvas.stroke_width(face_border_width);
  258.    
  259.     clock_canvas.circle(center, center.x - 1);
  260.     clock_canvas.render();
  261.    
  262.     bool toggle = false;
  263.     clock_canvas.stroke_color(tick_border_color);
  264.     clock_canvas.fill_color(tick_color);
  265.     clock_canvas.stroke_width(tick_border_width);
  266.    
  267.     for (float rot = 0; rot < 360.0f; rot += rot_step) {
  268.         rotation = rot;
  269.         update_transform(rotation, ctheta, stheta);
  270.         toggle = !toggle;
  271.         if (toggle) {
  272.             clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y1));
  273.             clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y1));
  274.             clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
  275.             clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
  276.             clock_canvas.close_path();
  277.         } else {
  278.             clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y1));
  279.             clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y1));
  280.             clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2 - sr.height() * 0.5f));
  281.             clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2 - sr.height() * 0.5f));
  282.             clock_canvas.close_path();
  283.         }
  284.         clock_canvas.render();
  285.     }    
  286. }
  287. // our current "time" used to position the clock hands
  288. static time_t current_time = 0;
  289. // the bitmaps used for our transfer buffers
  290. fb_t tbmp;
  291. fb_t tbmp2;
  292. #ifdef BUFFER_FACE
  293. // the bitmap used to hold the clock face
  294. fb_t fbmp;
  295. #endif
  296. // the rectangle where the clock lives
  297. srect16 clock_rect = (ssize16(128,128).bounds()).center(ssize16(320,240).bounds());
  298. #ifdef ARDUINO
  299. // entry point (arduino)
  300. void setup()
  301. {
  302.     Serial.begin(115200);
  303. #else
  304. // arduino compat shims:
  305. static uint32_t millis() {
  306.     return pdTICKS_TO_MS(xTaskGetTickCount());
  307. }
  308. void loop();
  309. // entry point (esp-idf)
  310. extern "C" void app_main()
  311. {
  312. #endif
  313.     // initialize the AXP192 in the core 2
  314.     power.initialize();
  315.     // initialize the LCD
  316.     lcd_panel_init();
  317.     // initialize the touch panel
  318.     touch.initialize();
  319.     // create a semaphore to sync access to our memory data
  320.     mem_sync = xSemaphoreCreateMutex();
  321.     // create a task on the other core to monitor memory
  322.     TaskHandle_t mem_task_handle = nullptr;
  323.     xTaskCreatePinnedToCore(mem_task,"mem_task",1024,nullptr,10,&mem_task_handle,1-xPortGetCoreID());
  324.    
  325.     // fill the screen:
  326.     // we transfer the bitmap 6 times
  327.     // to fill 320x240.
  328.     tbmp=fb_t({320,40},lcd_transfer_buffer1);
  329.     tbmp.fill(tbmp.bounds(),color_t::white);
  330.     for(int y = 0;y<240;y+=40) {
  331.         lcd_begin_draw();
  332.         lcd_flush_bitmap(0,y,319,y+39,tbmp.begin());
  333.     }
  334.  
  335.     // initialize the transfer bitmaps
  336.     tbmp=fb_t({128,128},lcd_transfer_buffer1);
  337.     tbmp2=fb_t({128,128},lcd_transfer_buffer2);
  338.  
  339.     // we want the second hand color to be semitransparent
  340.     second_color.opacity(.5); // half opacity
  341.     // initialize the canvas
  342.     clock_canvas.initialize();
  343.     // get the style
  344.     canvas_style clock_style = clock_canvas.style();
  345.     // set some of the defaults to what we want
  346.     // you can set these individually off of the canvas
  347.     clock_style.fill_paint_type = paint_type::solid;
  348.     clock_style.stroke_paint_type = paint_type::solid;
  349.     clock_style.stroke_width = 1;
  350.     clock_canvas.style(clock_style);
  351.  
  352. #ifdef BUFFER_FACE
  353.     // if indicated, we only draw the clock face once
  354.     // to a buffer. We then copy that buffer over
  355.     // each iteration instead of redrawing the face.
  356.     // trades memory for speed.
  357.     fbmp = create_bitmap_from(tbmp2,{128,128});
  358.     // attach/bind the initialized canvas to the face bitmap
  359.     draw::canvas(fbmp,spoint16::zero(),clock_canvas);
  360.     fbmp.fill(fbmp.bounds(),color_t::white);
  361.     draw_clock_face();
  362. #else
  363.     // clear the transfer bitmaps
  364.     tbmp.fill(tbmp.bounds(),color_t::white);
  365.     tbmp2.fill(tbmp2.bounds(),color_t::white);
  366. #endif
  367.     // ESP-IDF compat shim
  368. #ifndef ARDUINO
  369.    
  370.     while (1)
  371.     {
  372.         static int count = 0;
  373.         loop();
  374.         // tickle the watchdog periodically
  375.         if (count++ == 4)
  376.         {
  377.             count = 0;
  378.             vTaskDelay(5);
  379.         }
  380.     }
  381. #endif
  382. }
  383. void loop()
  384. {
  385.     // DMA technique: The idea is to draw to one bitmap while flushing the other one.
  386.     // That's why we have two transfer buffers. Here we choose which one
  387.     // based on which one we used last time (there are only two)
  388.     static int dma_toggle = 0; // which buffer to use
  389.     fb_t& xbmp = dma_toggle?tbmp:tbmp2; // a reference to the right bitmap
  390.     // tell the lcd API we're about to draw (waits if necessary)
  391.     lcd_begin_draw();
  392.     // bind the canvas to the current transfer bitmap
  393.     draw::canvas(xbmp,spoint16::zero(),clock_canvas);
  394. #ifdef BUFFER_FACE
  395.     draw::bitmap(xbmp,xbmp.bounds(),fbmp,fbmp.bounds());
  396. #else
  397.     draw_clock_face();
  398. #endif
  399.     pointf offset(0, 0);
  400.     pointf center(0, 0);
  401.     time_t time = current_time;
  402.     float rotation(0);
  403.     float ctheta, stheta;
  404.     ssize16 size = (ssize16)clock_canvas.dimensions();
  405.     rectf b = gfx::sizef(size.width, size.height).bounds();
  406.     b.inflate_inplace(-face_border_width - 1, -face_border_width - 1);
  407.     float w = b.width();
  408.     float h = b.height();
  409.     if(w>h) w= h;
  410.     // i lay things out relative to one another. so i keep that sr rect
  411.     // around to sort of track my current "position/bounds" within the drawing.
  412.     // i then "move it" from one place to the next to position the next portion
  413.     rectf sr(0, w / 30, w / 30, w / 5);
  414.     sr.center_horizontal_inplace(b);
  415.     center = gfx::pointf(w * 0.5f + face_border_width + 1, w * 0.5f + face_border_width + 1);
  416.     // took some experimentation to get the numbers right
  417.     sr = gfx::rectf(0, w / 40, w / 16, w / 2);
  418.     sr.center_horizontal_inplace(b);
  419.     // create a path for the minute hand:
  420.     rotation = (fmodf(time / 60.0f, 60) / 60.0f) * 360.0f;
  421.     update_transform(rotation, ctheta, stheta);
  422.     clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y1));
  423.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
  424.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y2 + (w / 20)));
  425.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
  426.     clock_canvas.close_path();
  427.     clock_canvas.fill_color(minute_color);
  428.     clock_canvas.stroke_color(minute_border_color);
  429.     clock_canvas.stroke_width(minute_border_width);
  430.     clock_canvas.render(); // render the path
  431.     // create a path for the hour hand
  432.     sr.y1 += w / 8;
  433.     rotation = (fmodf(time / (3600.0f), 12.0f) / (12.0f)) * 360.0f;
  434.     update_transform(rotation, ctheta, stheta);
  435.     clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y1));
  436.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
  437.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y2 + (w / 20)));
  438.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
  439.     clock_canvas.close_path();
  440.     clock_canvas.fill_color(hour_color);
  441.     clock_canvas.stroke_color(hour_border_color);
  442.     clock_canvas.stroke_width(hour_border_width);
  443.     clock_canvas.render(); // render the path
  444.     // create a path for the second hand
  445.     sr.y1 -= w / 8;
  446.     rotation = ((time % 60) / 60.0f) * 360.0f;
  447.     update_transform(rotation, ctheta, stheta);
  448.     clock_canvas.move_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y1));
  449.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x2, sr.y2));
  450.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1 + sr.width() * 0.5f, sr.y2 + (w / 20)));
  451.     clock_canvas.line_to(transform_point(ctheta, stheta, center, offset, sr.x1, sr.y2));
  452.     clock_canvas.close_path();
  453.     clock_canvas.fill_color(second_color);
  454.     clock_canvas.stroke_color(second_border_color);
  455.     clock_canvas.stroke_width(second_border_width);
  456.     clock_canvas.render();
  457.     // increment the face "time"
  458.     ++current_time;
  459.    
  460.     // send what we just drew to the display
  461.     lcd_flush_bitmap(clock_rect.x1,clock_rect.y1,clock_rect.x2,clock_rect.y2,xbmp.begin());
  462.     // update to use the next buffer
  463.     if(++dma_toggle) {
  464.         dma_toggle=0;
  465.     }
  466.     // for calculating the frames per second.
  467.     static int frames = 0;
  468.     ++frames;
  469.     static uint64_t update_ts = 0;
  470.     // once every second:
  471.     if(millis()>update_ts+1000) {
  472.         update_ts=millis();
  473.         // print the frames per second and average frame time
  474.         printf("%d FPS - frame time: %0.2fms\n",frames,1000.f/((float)frames));
  475.         frames = 0;
  476.         // get the memory stats (thread safe)
  477.         xSemaphoreTake(mem_sync,portMAX_DELAY);
  478.         size_t mmax = max_mem;
  479.         size_t mmin = min_mem;
  480.         // reset the memory stats
  481.         max_mem = 0;
  482.         min_mem = (unsigned)-1;
  483.         xSemaphoreGive(mem_sync);
  484.         printf("Current mem usage: %0.2fKB\n",((float)mmax-mmin)/1024.f);
  485.     }
  486.    
  487. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement