From b7e9df46b92cb503e8ed262f46399e1131759eba Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sun, 10 Mar 2024 20:20:14 +0800 Subject: [PATCH 01/16] Enable the creation of the compile_commands.json file --- example/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 10495d7..f100d9b 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -7,6 +7,7 @@ include(pico_sdk_import.cmake) project(ssd1306-example) set(CMAKE_C_STANDARD 11) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # initialize the Raspberry Pi Pico SDK pico_sdk_init() From 77ce193f82e298766846391bbc2a417e5a0ddc3c Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sun, 10 Mar 2024 20:28:57 +0800 Subject: [PATCH 02/16] Add missing stdint header --- font.h | 1 + 1 file changed, 1 insertion(+) diff --git a/font.h b/font.h index 8c9ff4a..28f986c 100644 --- a/font.h +++ b/font.h @@ -1,5 +1,6 @@ #ifndef _inc_font #define _inc_font +#include /* * Format From 792fec6ec9a34ff81280418b673c432b7563b0e4 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sun, 10 Mar 2024 20:32:56 +0800 Subject: [PATCH 03/16] Define a struct for fonts Instead of encoding font properties in the bitmap buffer, Encode them in a struct, thus removing magic numbers in the code --- basic_font.h | 13 +++++++++++++ font_struct.h | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 basic_font.h create mode 100644 font_struct.h diff --git a/basic_font.h b/basic_font.h new file mode 100644 index 0000000..e284a27 --- /dev/null +++ b/basic_font.h @@ -0,0 +1,13 @@ +#ifndef _basic_font_h +#define _basic_font_h +#include "font.h" +#include "font_struct.h" + +const font basic_font = { + .bytes_per_char = 5, + .char_width = 5, + .first_char_in_font = 32, + .char_height = 8, + .bitmap_buffer = (char *)&font_8x5[5], +}; +#endif diff --git a/font_struct.h b/font_struct.h new file mode 100644 index 0000000..a778200 --- /dev/null +++ b/font_struct.h @@ -0,0 +1,14 @@ +#ifndef _font_struct_h +#define _font_struct_h +#include +/** + * Struct that holds a font and all relevant information + */ +typedef struct { + uint16_t bytes_per_char; // the size of the char sprite in bytes (including padding) + uint16_t char_width; // width of the caracter in pixels (how many columns the sprite spans) + char first_char_in_font; + uint16_t char_height; // height of the character in pixels (how many rows the sprite spans) + const char *bitmap_buffer; +} font; +#endif From 7adbabf1dab44343d20ba3770cd06de645d5a4f8 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sun, 10 Mar 2024 20:37:21 +0800 Subject: [PATCH 04/16] Remove functions with built in font When using the built in font, it creates a conflict in the linker as the symbol font5x8 is defined twice (once in the executable and once in the 'library'. As this is a fairly trivial wrapper, the user can recreate it with their 'default' font for the project. --- ssd1306.c | 8 -------- ssd1306.h | 34 +++++++++++++--------------------- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/ssd1306.c b/ssd1306.c index 1ce7d9d..3496ab8 100644 --- a/ssd1306.c +++ b/ssd1306.c @@ -219,14 +219,6 @@ void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_ } } -void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c) { - ssd1306_draw_char_with_font(p, x, y, scale, font_8x5, c); -} - -void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const char *s) { - ssd1306_draw_string_with_font(p, x, y, scale, font_8x5, s); -} - static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) { switch(size) { case 1: diff --git a/ssd1306.h b/ssd1306.h index 138cef3..095a233 100644 --- a/ssd1306.h +++ b/ssd1306.h @@ -30,6 +30,7 @@ SOFTWARE. #ifndef _inc_ssd1306 #define _inc_ssd1306 +#include #include #include @@ -237,17 +238,6 @@ void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size); */ void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c); -/** - @brief draw char with builtin font - - @param[in] p : instance of display - @param[in] x : x starting position of char - @param[in] y : y starting position of char - @param[in] scale : scale font to n times of original size (default should be 1) - @param[in] c : character to draw -*/ -void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c); - /** @brief draw string with given font @@ -261,14 +251,16 @@ void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, cha void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, const char *s ); /** - @brief draw string with builtin font - - @param[in] p : instance of display - @param[in] x : x starting position of text - @param[in] y : y starting position of text - @param[in] scale : scale font to n times of original size (default should be 1) - @param[in] s : text to draw -*/ -void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const char *s); - + @brief Blit a sprite into the display buffer + + @param[in] disp : instance of display with display buffer + @param[in] sprite : the buffer containing the sprite (padded if necessary) + @param[in] sprite_height : the height of the sprite in pixels + @param[in] sprite_width : the width of the sprite in pixels (number of columns) + @param[in] start_col : the column on the display containing the top left corner of the sprite + @param[in] start_row : the row containing the top left corner of the sprite + */ +void ssd1306_blit(ssd1306_t *disp, const char* sprite, + uint32_t sprite_height, uint32_t sprite_width, + uint32_t start_col, uint32_t start_row); #endif From 09cd9ae29ca9b0868f212dfac5f988340824dcaf Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Mon, 11 Mar 2024 01:58:14 +0800 Subject: [PATCH 05/16] Add struct for BMSPA_font --- example/BMSPA_font.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/example/BMSPA_font.h b/example/BMSPA_font.h index e180690..e56306a 100644 --- a/example/BMSPA_font.h +++ b/example/BMSPA_font.h @@ -1,3 +1,8 @@ +#ifndef _bmspa_font_h +#define _bmspa_font_h +#include +#include "font_struct.h" + const uint8_t BMSPA_font[] = { 8, 8, 0, 32, 126, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // @@ -97,3 +102,12 @@ const uint8_t BMSPA_font[] = { 0x02,0x01,0x01,0x02,0x02,0x01,0x00,0x00, // ~ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; + +const font bmspa_font = { + .bitmap_buffer = (const char *)&BMSPA_font[5] , + .first_char_in_font = 32, + .bytes_per_char = 8, + .char_width = 8, + .char_height = 8, +}; +#endif From 53f2ad51f6ec29adfff8cc38643a61f707fbb2b9 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sat, 16 Mar 2024 18:18:49 +0800 Subject: [PATCH 06/16] Added struct to represent the font into the font files --- example/acme_5_outlines_font.h | 14 ++++++++++++++ example/bubblesstandard_font.h | 14 ++++++++++++++ example/crackers_font.h | 14 ++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/example/acme_5_outlines_font.h b/example/acme_5_outlines_font.h index 7d2798e..74ab427 100644 --- a/example/acme_5_outlines_font.h +++ b/example/acme_5_outlines_font.h @@ -1,3 +1,8 @@ +#ifndef _acme_font_h +#define _acme_font_h +#include +#include "font_struct.h" + const uint8_t acme_font[] = { 8, 6, 1, 32, 126, 0x00,0x00,0x00,0x00,0x00,0x00, // @@ -97,3 +102,12 @@ const uint8_t acme_font[] = { 0x0f,0x09,0x0d,0x09,0x0b,0x09, // ~ 0x00,0x00,0x00,0x00,0x00,0x00 }; + +const font acme = { + .bitmap_buffer = (const char *)&acme_font[5] , + .first_char_in_font = 32, + .bytes_per_char = 6, + .char_width = 6, + .char_height = 8, +}; +#endif diff --git a/example/bubblesstandard_font.h b/example/bubblesstandard_font.h index 1162fb3..d465740 100644 --- a/example/bubblesstandard_font.h +++ b/example/bubblesstandard_font.h @@ -1,3 +1,8 @@ +#ifndef _bubblesstandard_font_h +#define _bmspa_font_h +#include +#include "font_struct.h" + const uint8_t bubblesstandard_font[] = { 8, 7, 0, 32, 126, 0x00,0x00,0x00,0x00,0x00,0x00,0x00, // @@ -97,3 +102,12 @@ const uint8_t bubblesstandard_font[] = { 0x10,0x08,0x08,0x10,0x10,0x08,0x00, // ~ 0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; + +const font bubblesstandard = { + .bitmap_buffer = (const char *)&bubblesstandard_font[5] , + .first_char_in_font = 32, + .bytes_per_char = 7, + .char_width = 7, + .char_height = 8, +}; +#endif diff --git a/example/crackers_font.h b/example/crackers_font.h index e3b5960..8da2ece 100644 --- a/example/crackers_font.h +++ b/example/crackers_font.h @@ -1,3 +1,8 @@ +#ifndef _crackers_font_h +#define _crackers_font_h +#include +#include "font_struct.h" + const unsigned char crackers_font[] = { 8, 6, 2, 32, 126, 0x00,0x00,0x00,0x00,0x00,0x00, // @@ -97,3 +102,12 @@ const unsigned char crackers_font[] = { 0x04,0x06,0x06,0x02,0x04,0x06, // ~ 0x00,0x00,0x00,0x00,0x00,0x00 }; + +const font crackers = { + .bitmap_buffer = (const char *)&crackers_font[5] , + .first_char_in_font = 32, + .bytes_per_char = 6, + .char_width = 6, + .char_height = 8, +}; +#endif From 4b1fa21157f7995a30eaff6629d8a0f70bf7ca79 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sat, 16 Mar 2024 18:19:43 +0800 Subject: [PATCH 07/16] Commented the struct further --- font_struct.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/font_struct.h b/font_struct.h index a778200..c1d7c59 100644 --- a/font_struct.h +++ b/font_struct.h @@ -7,7 +7,7 @@ typedef struct { uint16_t bytes_per_char; // the size of the char sprite in bytes (including padding) uint16_t char_width; // width of the caracter in pixels (how many columns the sprite spans) - char first_char_in_font; + char first_char_in_font; // the first char that is included in the font uint16_t char_height; // height of the character in pixels (how many rows the sprite spans) const char *bitmap_buffer; } font; From dfacd8f3c2570d00b39cffd9407a345dff22a1ae Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sat, 16 Mar 2024 18:21:13 +0800 Subject: [PATCH 08/16] Add cmake output to gitignore list --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c6127b3..d04645c 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ modules.order Module.symvers Mkfile.old dkms.conf +build/ +.cache/ From 01238b3ed9ee67b3bf113e6b6e8429a607133ea6 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sat, 16 Mar 2024 19:29:17 +0800 Subject: [PATCH 09/16] Use DMA engine to transfer display buffer Instead of having the CPU Idle while the Data is being transfered to the Display, Use the DMA engine to transfer display buffer into the I2C peripheral. allowing the CPU time to be used otherwise. The use of the DMA needed to change the way the peripheral is initialized. Most of the data initialization is done via a Macro that sets up all the necessary data in the display struct. To initialize the display, the ssd1306_init function still needs to be run. The define also changes the implementation of the 'ssd1306_show' function to use the DMA instead of 'fancy_write' Also add commands to the CMakeLists.txt file of the example to allow for changing the compile Define on the target. --- example/CMakeLists.txt | 12 +++++ ssd1306.c | 100 ++++++++++++++++++++++++++++++++++++++++- ssd1306.h | 54 +++++++++++++++++++--- 3 files changed, 159 insertions(+), 7 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index f100d9b..c23d133 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,6 +8,7 @@ project(ssd1306-example) set(CMAKE_C_STANDARD 11) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +option(USE_DMA_FOR_DISPLAY "Use the DMA engine to move the display buffer to the display" OFF) # initialize the Raspberry Pi Pico SDK pico_sdk_init() @@ -23,6 +24,17 @@ target_include_directories(ssd1306-example ${CMAKE_CURRENT_LIST_DIR}/../ ) +if(USE_DMA_FOR_DISPLAY) + message(STATUS "Using DMA") + target_compile_definitions(ssd1306-example + PUBLIC SSD1306_USE_DMA + ) + target_link_libraries(ssd1306-example + hardware_dma + ) +endif(USE_DMA_FOR_DISPLAY) + + target_link_libraries(ssd1306-example pico_stdlib hardware_i2c) pico_enable_stdio_usb(ssd1306-example 1) diff --git a/ssd1306.c b/ssd1306.c index 3496ab8..467795d 100644 --- a/ssd1306.c +++ b/ssd1306.c @@ -31,7 +31,10 @@ SOFTWARE. #include #include "ssd1306.h" -#include "font.h" +#ifdef SSD1306_USE_DMA +#include "hardware/dma.h" +#endif +#include "font_struct.h" inline static void swap(int32_t *a, int32_t *b) { int32_t *t=a; @@ -39,6 +42,7 @@ inline static void swap(int32_t *a, int32_t *b) { *b=*t; } +#ifndef SSD1306_USE_DMA inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, char *name) { switch(i2c_write_blocking(i2c, addr, src, len, false)) { case PICO_ERROR_GENERIC: @@ -52,12 +56,55 @@ inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src break; } } +#endif +#ifdef SSD1306_USE_DMA +inline static void ssd1306_write(ssd1306_t *p, uint8_t val) { + uint8_t d[2]= {0x00, val}; + i2c_write_blocking(p->i2c_i, p->address, d, 2, true); +} +#else inline static void ssd1306_write(ssd1306_t *p, uint8_t val) { uint8_t d[2]= {0x00, val}; fancy_write(p->i2c_i, p->address, d, 2, "ssd1306_write"); } +#endif +#ifdef SSD1306_USE_DMA +bool ssd1306_init(ssd1306_t *p) { + uint8_t startup_commands[]= { + SET_DISP, + SET_DISP_CLK_DIV, + 0x80, + SET_MUX_RATIO, + p->height - 1, + SET_DISP_OFFSET, + 0x00, + SET_DISP_START_LINE, + SET_CHARGE_PUMP, \ + p->external_vcc ? 0x10 : 0x14, + SET_SEG_REMAP | 0x01, + SET_COM_OUT_DIR | 0x08, + SET_COM_PIN_CFG, + p->width > 2 * p->height ? 0x02 : 0x12, + SET_CONTRAST, + 0xff, + SET_PRECHARGE, + p->external_vcc ? 0x22 : 0xF1, + SET_VCOM_DESEL, + 0x30, + SET_ENTIRE_ON, + SET_NORM_INV, + SET_DISP | 0x01, + SET_MEM_ADDR, + 0x00, + }; + dma_channel_claim(p->dma_channel); + for(size_t i=0; iwidth=width; p->height=height; @@ -114,10 +161,13 @@ bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address return true; } +#endif +#ifndef SSD1306_USE_DMA inline void ssd1306_deinit(ssd1306_t *p) { free(p->buffer-1); } +#endif inline void ssd1306_poweroff(ssd1306_t *p) { ssd1306_write(p, SET_DISP|0x00); @@ -282,6 +332,53 @@ inline void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0); } +#ifdef SSD1306_USE_DMA +void copy_to_dma_tx(ssd1306_t *disp) { + disp->dma_tx_buffer[0] = 1u << I2C_IC_DATA_CMD_RESTART_LSB | 0x0040; + for (int i = 0; i < disp->bufsize-1; i++) { + disp->dma_tx_buffer[i+1] = disp->buffer[i]; + } + disp->dma_tx_buffer[disp->bufsize] = (1u << I2C_IC_DATA_CMD_STOP_LSB) | disp->buffer[disp->bufsize-1]; +} +#endif + +#ifdef SSD1306_USE_DMA +void ssd1306_show(ssd1306_t *p) { + // if there is already a transfer running, wait until it has completed + dma_channel_wait_for_finish_blocking(p->dma_channel); + copy_to_dma_tx(p); + // now set the address of the display that we want to write to + p->i2c_i->hw->enable = 0; + p->i2c_i->hw->tar = p->address; + p->i2c_i->hw->enable = 1; + uint8_t payload[]= {SET_COL_ADDR, 0, p->width-1, SET_PAGE_ADDR, 0, p->pages-1}; + if(p->width==64) { + payload[1]+=32; + payload[2]+=32; + } + + for(size_t i=0; idma_channel); + channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_16); + channel_config_set_read_increment(&dma_config, true); + channel_config_set_write_increment(&dma_config, false); + // configure the i2c channel to cooperate with the dma engine + channel_config_set_dreq(&dma_config, i2c_get_dreq(p->i2c_i, true)); + dma_channel_configure( + p->dma_channel, // Channel to be configured + &dma_config, // The configuration we just created + &i2c_get_hw(p->i2c_i)->data_cmd, // The initial write address + p->dma_tx_buffer, // The initial read address + p->bufsize+1, // Number of transfers; in this case each is 2 byte. + true // Start immediately. + ); +} +#else void ssd1306_show(ssd1306_t *p) { uint8_t payload[]= {SET_COL_ADDR, 0, p->width-1, SET_PAGE_ADDR, 0, p->pages-1}; if(p->width==64) { @@ -296,3 +393,4 @@ void ssd1306_show(ssd1306_t *p) { fancy_write(p->i2c_i, p->address, p->buffer-1, p->bufsize+1, "ssd1306_show"); } +#endif diff --git a/ssd1306.h b/ssd1306.h index 095a233..ac6ee6e 100644 --- a/ssd1306.h +++ b/ssd1306.h @@ -57,20 +57,61 @@ typedef enum { SET_CHARGE_PUMP = 0x8D } ssd1306_command_t; +#ifdef SSD1306_USE_DMA +#include "hardware/dma.h" +/* construct and initialize the display struct used to generate the display output + * at compile time. This allows omitting the code to define the variables at runtime + * as all the details are known at compile time + */ +#define CREATE_DISPLAY(width_, height_, I2C, address_, dma_channel_, external_vcc_, id) \ + uint8_t display_buffer_ ## id[width_*height_];\ + uint16_t dma_tx_bufferbuffer_ ## id[width_*height_+1];\ + ssd1306_t display_ ## id = {\ + .dma_tx_buffer = dma_tx_bufferbuffer_ ## id,\ + .buffer = display_buffer_ ## id,\ + .bufsize = width_ * height_ / 8,\ + .width = width_,\ + .height = height_,\ + .pages = height_ / 8,\ + .address = address_,\ + .dma_channel = dma_channel_,\ + .external_vcc = external_vcc_,\ + .i2c_i = I2C,\ + } +#endif + /** * @brief holds the configuration */ +#ifdef SSD1306_USE_DMA +typedef struct { + volatile uint16_t *dma_tx_buffer; + volatile uint8_t *buffer; /**< display buffer */ + const size_t bufsize; /**< buffer size */ + const uint8_t width; /**< width of display */ + const uint8_t height; /**< height of display */ + const uint8_t pages; /**< stores pages of display (calculated on initialization*/ + const uint8_t address; /**< i2c address of display*/ + const uint dma_channel; + const uint8_t external_vcc; /**< whether display uses external vcc */ + i2c_inst_t *i2c_i; /**< i2c connection instance */ +} ssd1306_t; +#else typedef struct { + size_t bufsize; /**< buffer size */ + uint8_t *buffer; /**< display buffer */ uint8_t width; /**< width of display */ - uint8_t height; /**< height of display */ + uint8_t height; /**< height of display */ uint8_t pages; /**< stores pages of display (calculated on initialization*/ - uint8_t address; /**< i2c address of display*/ - i2c_inst_t *i2c_i; /**< i2c connection instance */ - bool external_vcc; /**< whether display uses external vcc */ - uint8_t *buffer; /**< display buffer */ - size_t bufsize; /**< buffer size */ + uint8_t address; /**< i2c address of display*/ + i2c_inst_t *i2c_i; /**< i2c connection instance */ + bool external_vcc; /**< whether display uses external vcc */ } ssd1306_t; +#endif +#ifdef SSD1306_USE_DMA +bool ssd1306_init(ssd1306_t *p); +#else /** * @brief initialize display * @@ -85,6 +126,7 @@ typedef struct { * @retval false if initialization failed */ bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance); +#endif /** * @brief deinitialize display From b3403098579675f2c68b411cc433b762a1efec04 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sat, 16 Mar 2024 20:20:44 +0800 Subject: [PATCH 10/16] Fix include define bug --- example/BMSPA_font.h | 2 +- example/bubblesstandard_font.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/BMSPA_font.h b/example/BMSPA_font.h index e56306a..aa1a2b9 100644 --- a/example/BMSPA_font.h +++ b/example/BMSPA_font.h @@ -103,7 +103,7 @@ const uint8_t BMSPA_font[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; -const font bmspa_font = { +const font bmspa = { .bitmap_buffer = (const char *)&BMSPA_font[5] , .first_char_in_font = 32, .bytes_per_char = 8, diff --git a/example/bubblesstandard_font.h b/example/bubblesstandard_font.h index d465740..e069796 100644 --- a/example/bubblesstandard_font.h +++ b/example/bubblesstandard_font.h @@ -1,5 +1,5 @@ #ifndef _bubblesstandard_font_h -#define _bmspa_font_h +#define _bubblesstandard_font_h #include #include "font_struct.h" From d154cc5c07e36abb3c84c8041be916f279ffe950 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sat, 16 Mar 2024 20:21:14 +0800 Subject: [PATCH 11/16] Modify Example so that it also runs DMA mode The example is modified so that during build time DMA mode may be selected or standard mode. Due to a small added delay due to copying data into the DMA tx buffer the sleep time needed to be increased to eliminate tearing. --- example/example.c | 61 ++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/example/example.c b/example/example.c index 4fcbc89..284adc4 100644 --- a/example/example.c +++ b/example/example.c @@ -10,11 +10,16 @@ #include "bubblesstandard_font.h" #include "crackers_font.h" #include "BMSPA_font.h" +#include "font.h" const uint8_t num_chars_per_disp[]={7,7,7,5}; -const uint8_t *fonts[4]= {acme_font, bubblesstandard_font, crackers_font, BMSPA_font}; +const uint8_t *fonts[] = {acme_font, bubblesstandard_font, crackers_font, BMSPA_font}; + +#define SLEEPTIME 30 +#ifdef SSD1306_USE_DMA +CREATE_DISPLAY(128, 64, i2c1, 0x3C, 0, 0, 01) ; +#endif -#define SLEEPTIME 25 void setup_gpios(void); void animation(void); @@ -40,14 +45,22 @@ void setup_gpios(void) { gpio_pull_up(3); } +void ssd1306_draw_string(ssd1306_t *disp, uint32_t x, uint32_t y, uint32_t scale, const char *s) { + ssd1306_draw_string_with_font(disp, x, y, scale, font_8x5, s); +} + void animation(void) { const char *words[]= {"SSD1306", "DISPLAY", "DRIVER"}; - ssd1306_t disp; - disp.external_vcc=false; - ssd1306_init(&disp, 128, 64, 0x3C, i2c1); - ssd1306_clear(&disp); +#ifdef SSD1306_USE_DMA + ssd1306_init(&display_01); +#else + ssd1306_t display_01; + display_01.external_vcc=false; + ssd1306_init(&display_01, 128, 64, 0x3C, i2c1); +#endif + ssd1306_clear(&display_01); printf("ANIMATION!\n"); @@ -55,33 +68,33 @@ void animation(void) { for(;;) { for(int y=0; y<31; ++y) { - ssd1306_draw_line(&disp, 0, y, 127, y); - ssd1306_show(&disp); + ssd1306_draw_line(&display_01, 0, y, 127, y); + ssd1306_show(&display_01); sleep_ms(SLEEPTIME); - ssd1306_clear(&disp); + ssd1306_clear(&display_01); } for(int y=0, i=1; y>=0; y+=i) { - ssd1306_draw_line(&disp, 0, 31-y, 127, 31+y); - ssd1306_draw_line(&disp, 0, 31+y, 127, 31-y); - ssd1306_show(&disp); + ssd1306_draw_line(&display_01, 0, 31-y, 127, 31+y); + ssd1306_draw_line(&display_01, 0, 31+y, 127, 31-y); + ssd1306_show(&display_01); sleep_ms(SLEEPTIME); - ssd1306_clear(&disp); + ssd1306_clear(&display_01); if(y==32) i=-1; } for(int i=0; i Date: Sat, 16 Mar 2024 20:33:08 +0800 Subject: [PATCH 12/16] Add a function that performs blitting Blitting moves a sprite into the display buffer while in this case using bit wise operations to accomplish this with fewer CPU cycles than using the draw pixel functions --- ssd1306.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ ssd1306.h | 14 ++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/ssd1306.c b/ssd1306.c index 467795d..4ec2155 100644 --- a/ssd1306.c +++ b/ssd1306.c @@ -224,6 +224,50 @@ void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t } } +// assumes the bytes are organized in horizontal bars (called pages) 8 pixels high (as one byte per column of the bar and then the subsequent +// byte in the memmory fills occupies the next column but all the same rows as the previous byte) +// The sprite is assumed to be in the same paged layout with a minimum of 8 bits height (padded if needed). +void ssd1306_blit(ssd1306_t *disp, const char *sprite, uint32_t sprite_height, uint32_t sprite_width, uint32_t start_col, uint32_t start_row) { + uint8_t top_page = start_row >> 3; + uint8_t top_row_in_page = start_row % 8; + uint8_t sprite_dangling_bits = sprite_height % 8; + uint8_t full_sprite_pages = (sprite_height >> 3); + // handle the full rows in the sprite + for (int i = 0; i < full_sprite_pages; i ++) { + uint32_t displ_buffer_addr = (top_page + i) * disp->width + start_col; + for (int j = 0; j < sprite_width; j++) { + uint32_t sprite_addr = i * sprite_width + j; + if (top_row_in_page > 0) { + disp->buffer[displ_buffer_addr+j] &= ~(0xff << top_row_in_page); + disp->buffer[displ_buffer_addr+j] |= sprite[sprite_addr] << top_row_in_page; + disp->buffer[displ_buffer_addr+j+disp->width] &= ~(0xff >> (8 - top_row_in_page)); + disp->buffer[displ_buffer_addr+j+disp->width] |= sprite[sprite_addr] >> ( 8 - top_row_in_page); + } else { + disp->buffer[displ_buffer_addr+j] = sprite[sprite_addr]; + } + } + } + // handle a possibly dangling row + if (sprite_dangling_bits > 0) { + uint32_t displ_buffer_start_addr = (top_page + full_sprite_pages) * disp->width + start_col; + uint32_t sprite_start_addr = full_sprite_pages * sprite_width; + if (sprite_dangling_bits > (8 - top_row_in_page)) { + for (int i = 0; ibuffer[displ_buffer_start_addr+i] &= ~(0xff >> (8-top_row_in_page)); + disp->buffer[displ_buffer_start_addr+i] |= sprite[sprite_start_addr+i] & (0xff >> (8-top_row_in_page)); + disp->buffer[displ_buffer_start_addr+i+disp->width] &= ~(0xff >> (sprite_dangling_bits - 8 + top_row_in_page)); + disp->buffer[displ_buffer_start_addr+i+disp->width] |= sprite[sprite_start_addr+i+sprite_width] >> (sprite_dangling_bits - 8 + top_row_in_page); + } + } else { + for (int i = 0; ibuffer[displ_buffer_start_addr+i] &= ~(0xff >> (8-sprite_dangling_bits)); + disp->buffer[displ_buffer_start_addr+i] |= sprite[sprite_start_addr+i] & (0xff >> (8-sprite_dangling_bits)); + } + } + } + +} + void ssd1306_clear_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { for(uint32_t i=0; i Date: Mon, 24 Jun 2024 14:08:46 +0800 Subject: [PATCH 13/16] Add: Implementing widget system for the ssd1306 --- ssd1306.c | 235 +++++++++++++++++++++------------------------- ssd1306.h | 133 +++++--------------------- ssd1306_widgets.c | 37 ++++++++ ssd1306_widgets.h | 28 ++++++ text_buffer.c | 59 ++++++++++++ text_buffer.h | 22 +++++ 6 files changed, 275 insertions(+), 239 deletions(-) create mode 100644 ssd1306_widgets.c create mode 100644 ssd1306_widgets.h create mode 100644 text_buffer.c create mode 100644 text_buffer.h diff --git a/ssd1306.c b/ssd1306.c index 4ec2155..34a4029 100644 --- a/ssd1306.c +++ b/ssd1306.c @@ -44,7 +44,7 @@ inline static void swap(int32_t *a, int32_t *b) { #ifndef SSD1306_USE_DMA inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, char *name) { - switch(i2c_write_blocking(i2c, addr, src, len, false)) { + switch(i2c_write_blocking(i2c, addr, src, len, true)) { case PICO_ERROR_GENERIC: printf("[%s] addr not acknowledged!\n", name); break; @@ -190,40 +190,6 @@ inline void ssd1306_clear(ssd1306_t *p) { memset(p->buffer, 0, p->bufsize); } -void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { - if(x>=p->width || y>=p->height) return; - - p->buffer[x+p->width*(y>>3)]&=~(0x1<<(y&0x07)); -} - -void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { - if(x>=p->width || y>=p->height) return; - - p->buffer[x+p->width*(y>>3)]|=0x1<<(y&0x07); // y>>3==y/8 && y&0x7==y%8 -} - -void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2) { - if(x1>x2) { - swap(&x1, &x2); - swap(&y1, &y2); - } - - if(x1==x2) { - if(y1>y2) - swap(&y1, &y2); - for(int32_t i=y1; i<=y2; ++i) - ssd1306_draw_pixel(p, x1, i); - return; - } - - float m=(float) (y2-y1) / (float) (x2-x1); - - for(int32_t i=x1; i<=x2; ++i) { - float y=m*(float) (i-x1)+(float) y1; - ssd1306_draw_pixel(p, i, (uint32_t) y); - } -} - // assumes the bytes are organized in horizontal bars (called pages) 8 pixels high (as one byte per column of the bar and then the subsequent // byte in the memmory fills occupies the next column but all the same rows as the previous byte) // The sprite is assumed to be in the same paged layout with a minimum of 8 bits height (padded if needed). @@ -268,114 +234,105 @@ void ssd1306_blit(ssd1306_t *disp, const char *sprite, uint32_t sprite_height, u } -void ssd1306_clear_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - for(uint32_t i=0; ifont[4]) - return; - uint32_t parts_per_line=(font[0]>>3)+((font[0]&7)>0); - for(uint8_t w=0; w>=1) { - if(line & 1) - ssd1306_draw_square(p, x+w*scale, y+((lp<<3)+j)*scale, scale, scale); +// Blit a sprite onto the screen the packing of the sprite is considered tp be +// loose, such that the memmory occupied by a display column is considered padded to the next byte +// assuming one bit is a single pixel so a 5 pixel tall sprite would use one byte per +// column and a 13 bit tall sprite would use 2 bytes per column. +// The content of the padded bits are irrelevant +// it also assumes that the display buffer include pad bits at the end of every column (if needed) +// It is also assumed that the sprite fits entirely onto the screen. This will fail if the sprite +// partly exits the screen area +// +// sprite -> Pointer to the buffer containing the sprite in column major order +// sprite_height -> the number of rows covered by a sprite +// sprite_width -> the number of columns covered by a sprite +// start_col -> column containing the top left pixel of the sprite +// start_row -> row containing the top left pixel of the column +void blit_row_mayor_display_buffer(ssd1306_t *disp, const uint8_t* sprite, uint32_t sprite_height, uint32_t sprite_width, uint32_t start_col, uint32_t start_row) { + uint32_t dbuf_bytes_per_line = (disp->width >> 3) + (disp->width % 8 > 0 ? 1 : 0); // length of a column in the display in bytes with padding + uint8_t dbuf_start_bit = start_col % 8; // the first bit in the byte that the sprite would modify + uint8_t sprite_line_pad_byte = sprite_width % 8 > 0 ? 1 : 0; // determin if the sprite has a byte that includes padding + uint8_t sprite_bytes_per_line = (sprite_width >> 3) + sprite_line_pad_byte; // length of a sprite column in byte including padding + // now we blit the columns of the sprite into the appropriate bytes of the display buffer + for (uint32_t line=0;line> 3); // determin the first byte of the display buffer we are blitting into + uint32_t sprite_line_start_addr = line * sprite_bytes_per_line; // deterime the start byte in the sprite memory + // now for every non padded byte in a sprite column we copy it to the display buffer + for (uint32_t line_byte=0; line_byte<(sprite_width>>3);line_byte++) { + if (dbuf_start_bit > 0) { + // if we do not have a byte alligned write into the display buffer memory we need some extra bit shifting + disp->buffer[dbuf_line_start_addr+line_byte] &= ~(0xff << dbuf_start_bit); + disp->buffer[dbuf_line_start_addr+line_byte] |= sprite[sprite_line_start_addr+line_byte] << dbuf_start_bit; + disp->buffer[dbuf_line_start_addr+line_byte+1] &= ~(0xff >> (8 - dbuf_start_bit)); + disp->buffer[dbuf_line_start_addr+line_byte+1] |= sprite[sprite_line_start_addr+line_byte] >> (8 - dbuf_start_bit); + } else { + // if we are aligned we are fine + disp->buffer[dbuf_line_start_addr+line_byte] = sprite[sprite_line_start_addr+line_byte]; } - - ++pp; + } + if (sprite_line_pad_byte) { + // the last byte is padded, so now we need to deal with a padded and a non padded byte + uint8_t display_buffer_byte = disp->buffer[dbuf_line_start_addr+sprite_bytes_per_line-1]; + uint8_t sprite_buffer_byte = sprite[sprite_line_start_addr + sprite_bytes_per_line - 1]; + uint8_t sprite_bits = sprite_width % 8; // how many bits still need to be written into the display buffer + uint8_t dbuf_byte_unmodified_bits = 8 - dbuf_start_bit; // how many bits are still 'untouched' in the current buffer byte + // so we now need to figure out if the bytes at the end of our sprite line + // fit into the bits that are left in the display buffer byte + if (sprite_bits > dbuf_byte_unmodified_bits) { + //if we need to write more bits into the display buffer that fit into the current byte + //first take care of the bits that need to go into the current display buffer byte + display_buffer_byte &= ~(0xff << dbuf_start_bit); + display_buffer_byte |= sprite_buffer_byte << dbuf_start_bit; + disp->buffer[dbuf_line_start_addr+sprite_bytes_per_line-1] = display_buffer_byte; + + // update the variables that we can now treat the easy case + display_buffer_byte = disp->buffer[dbuf_line_start_addr+sprite_bytes_per_line]; + sprite_bits -= dbuf_byte_unmodified_bits; + sprite_buffer_byte = sprite_buffer_byte >> dbuf_byte_unmodified_bits; + dbuf_byte_unmodified_bits = 8; + } + display_buffer_byte &= ~((0xff >> (8 - sprite_bits)) << dbuf_start_bit); // mask the bits that will be written to by the sprite + display_buffer_byte |= (sprite_buffer_byte & (0xff >> (8 - sprite_bits))) << dbuf_start_bit; // write the sprite bits into the dbuffer + disp->buffer[dbuf_line_start_addr+sprite_bytes_per_line-1] = display_buffer_byte; // write back the display buffer } } } -void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, const char *s) { - for(int32_t x_n=x; *s; x_n+=(font[1]+font[2])*scale) { - ssd1306_draw_char_with_font(p, x_n, y, scale, font, *(s++)); - } -} +void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { + if(x>=p->width || y>=p->height) return; -static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) { - switch(size) { - case 1: - return data[offset]; - case 2: - return data[offset]|(data[offset+1]<<8); - case 4: - return data[offset]|(data[offset+1]<<8)|(data[offset+2]<<16)|(data[offset+3]<<24); - default: - __builtin_unreachable(); - } - __builtin_unreachable(); + p->buffer[x+p->width*(y>>3)]&=~(0x1<<(y&0x07)); } -void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset) { - if(size<54) // data smaller than header - return; +void ssd1306_set_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { + if(x>=p->width || y>=p->height) return; - const uint32_t bfOffBits=ssd1306_bmp_get_val(data, 10, 4); - const uint32_t biSize=ssd1306_bmp_get_val(data, 14, 4); - const uint32_t biWidth=ssd1306_bmp_get_val(data, 18, 4); - const int32_t biHeight=(int32_t) ssd1306_bmp_get_val(data, 22, 4); - const uint16_t biBitCount=(uint16_t) ssd1306_bmp_get_val(data, 28, 2); - const uint32_t biCompression=ssd1306_bmp_get_val(data, 30, 4); + p->buffer[x+p->width*(y>>3)]|=0x1<<(y&0x07); // y>>3==y/8 && y&0x7==y%8 +} - if(biBitCount!=1) // image not monochrome - return; +void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2) { + if(x1>x2) { + swap(&x1, &x2); + swap(&y1, &y2); + } - if(biCompression!=0) // image compressed + if(x1==x2) { + if(y1>y2) + swap(&y1, &y2); + for(int32_t i=y1; i<=y2; ++i) + ssd1306_draw_pixel(p, x1, i); return; - - const int table_start=14+biSize; - uint8_t color_val=0; - - for(uint8_t i=0; i<2; ++i) { - if(!((data[table_start+i*4]<<16)|(data[table_start+i*4+1]<<8)|data[table_start+i*4+2])) { - color_val=i; - break; - } } - uint32_t bytes_per_line=(biWidth/8)+(biWidth&7?1:0); - if(bytes_per_line&3) - bytes_per_line=(bytes_per_line^(bytes_per_line&3))+4; - - const uint8_t *img_data=data+bfOffBits; - - int32_t step=biHeight>0?-1:1; - int32_t border=biHeight>0?-1:-biHeight; + float m=(float) (y2-y1) / (float) (x2-x1); - for(uint32_t y=biHeight>0?biHeight-1:0; y!=(uint32_t)border; y+=step) { - for(uint32_t x=0; x>3]>>(7-(x&7)))&1)==color_val) - ssd1306_draw_pixel(p, x_offset+x, y_offset+y); - } - img_data+=bytes_per_line; + for(int32_t i=x1; i<=x2; ++i) { + float y=m*(float) (i-x1)+(float) y1; + ssd1306_draw_pixel(p, i, (uint32_t) y); } } -inline void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size) { - ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0); -} - #ifdef SSD1306_USE_DMA void copy_to_dma_tx(ssd1306_t *disp) { disp->dma_tx_buffer[0] = 1u << I2C_IC_DATA_CMD_RESTART_LSB | 0x0040; @@ -384,9 +341,33 @@ void copy_to_dma_tx(ssd1306_t *disp) { } disp->dma_tx_buffer[disp->bufsize] = (1u << I2C_IC_DATA_CMD_STOP_LSB) | disp->buffer[disp->bufsize-1]; } -#endif -#ifdef SSD1306_USE_DMA +void print_content_of_dma_tx(ssd1306_t *disp) { + char print_buf[10]; + printf("\r\nPrinting DMA content\r\n\n"); + for (int i = 0; i < disp->height/8; i++) { + for(int j = 0; j < disp->width; j++) { + sprintf(print_buf, "%04x", disp->dma_tx_buffer[i*disp->width + j + 1]); + uart_puts(uart0, print_buf); + } + } +} + +// we need to load all the data from out actual display buffer into +// into the buffer that will be read out by the DMA. Then we need to set +// the appropriate control bits for the top 8 bits of every 16 bit word +void print_frame_buffer_content(ssd1306_t *disp, void (*sprint_fn)(char *, size_t) { + unsigned int pages = disp->height / 8; + char text_out[10]; + for (int i = 0; i < pages; i++) { + printf("\r\n"); + for (int j = 0; j < disp->width; j ++) { + sprintf(text_out, "%02x", disp->buffer[i*pages + j]); + sprint_fn(text_out, strlen(text_out) + 1); + } + } +} + void ssd1306_show(ssd1306_t *p) { // if there is already a transfer running, wait until it has completed dma_channel_wait_for_finish_blocking(p->dma_channel); @@ -404,6 +385,7 @@ void ssd1306_show(ssd1306_t *p) { for(size_t i=0; ibuffer-1)=0x40; - fancy_write(p->i2c_i, p->address, p->buffer-1, p->bufsize+1, "ssd1306_show"); } #endif diff --git a/ssd1306.h b/ssd1306.h index 9f683c5..9abc8a3 100644 --- a/ssd1306.h +++ b/ssd1306.h @@ -63,12 +63,12 @@ typedef enum { * at compile time. This allows omitting the code to define the variables at runtime * as all the details are known at compile time */ -#define CREATE_DISPLAY(width_, height_, I2C, address_, dma_channel_, external_vcc_, id) \ - uint8_t display_buffer_ ## id[width_*height_];\ - uint16_t dma_tx_bufferbuffer_ ## id[width_*height_+1];\ +#define CREATE_DISPLAY(width_, height_, I2C, address_, dma_channel_, external_vcc_, varname) \ + uint8_t display_buffer_ ## varname[width_*height_/8];\ + uint16_t dma_tx_bufferbuffer_ ## varname[(width_*height_/8)+1];\ ssd1306_t display_ ## id = {\ - .dma_tx_buffer = dma_tx_bufferbuffer_ ## id,\ - .buffer = display_buffer_ ## id,\ + .dma_tx_buffer = dma_tx_bufferbuffer_ ## varname,\ + .buffer = display_buffer_ ## varname,\ .bufsize = width_ * height_ / 8,\ .width = width_,\ .height = height_,\ @@ -128,6 +128,7 @@ bool ssd1306_init(ssd1306_t *p); bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance); #endif + /** * @brief deinitialize display * @@ -151,6 +152,19 @@ void ssd1306_poweroff(ssd1306_t *p); */ void ssd1306_poweron(ssd1306_t *p); +/** + @brief Blit a sprite into the display buffer + + @param[in] disp : instance of display with display buffer + @param[in] sprite : the buffer containing the sprite (padded if necessary) + @param[in] sprite_height : the height of the sprite in pixels + @param[in] sprite_width : the width of the sprite in pixels (number of columns) + @param[in] start_col : the column on the display containing the top left corner of the sprite + @param[in] start_row : the row containing the top left corner of the sprite + */ +void ssd1306_blit(ssd1306_t *disp, const char* sprite, + uint32_t sprite_height, uint32_t sprite_width, + uint32_t start_col, uint32_t start_row); /** @brief set contrast of display @@ -202,10 +216,10 @@ void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y); @param[in] x : x position @param[in] y : y position */ -void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y); +void ssd1306_set_pixel(ssd1306_t *p, uint32_t x, uint32_t y); /** - @brief draw line on buffer + @brief draw pixel on buffer @param[in] p : instance of display @param[in] x1 : x position of starting point @@ -214,109 +228,4 @@ void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y); @param[in] y2 : y position of end point */ void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2); - -/** - @brief clear square at given position with given size - - @param[in] p : instance of display - @param[in] x : x position of starting point - @param[in] y : y position of starting point - @param[in] width : width of square - @param[in] height : height of square -*/ -void ssd1306_clear_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height); - -/** - @brief draw filled square at given position with given size - - @param[in] p : instance of display - @param[in] x : x position of starting point - @param[in] y : y position of starting point - @param[in] width : width of square - @param[in] height : height of square -*/ -void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height); - -/** - @brief Blit a sprite into the display buffer - - @param[in] disp : instance of display with display buffer - @param[in] sprite : the buffer containing the sprite (padded if necessary) - @param[in] sprite_height : the height of the sprite in pixels - @param[in] sprite_width : the width of the sprite in pixels (number of columns) - @param[in] start_col : the column on the display containing the top left corner of the sprite - @param[in] start_row : the row containing the top left corner of the sprite - */ -void ssd1306_blit(ssd1306_t *disp, const char* sprite, - uint32_t sprite_height, uint32_t sprite_width, - uint32_t start_col, uint32_t start_row); - -/** - @brief draw empty square at given position with given size - - @param[in] p : instance of display - @param[in] x : x position of starting point - @param[in] y : y position of starting point - @param[in] width : width of square - @param[in] height : height of square -*/ -void ssd1306_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height); - -/** - @brief draw monochrome bitmap with offset - - @param[in] p : instance of display - @param[in] data : image data (whole file) - @param[in] size : size of image data in bytes - @param[in] x_offset : offset of horizontal coordinate - @param[in] y_offset : offset of vertical coordinate -*/ -void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset); - -/** - @brief draw monochrome bitmap - - @param[in] p : instance of display - @param[in] data : image data (whole file) - @param[in] size : size of image data in bytes -*/ -void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size); - -/** - @brief draw char with given font - - @param[in] p : instance of display - @param[in] x : x starting position of char - @param[in] y : y starting position of char - @param[in] scale : scale font to n times of original size (default should be 1) - @param[in] font : pointer to font - @param[in] c : character to draw -*/ -void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c); - -/** - @brief draw string with given font - - @param[in] p : instance of display - @param[in] x : x starting position of text - @param[in] y : y starting position of text - @param[in] scale : scale font to n times of original size (default should be 1) - @param[in] font : pointer to font - @param[in] s : text to draw -*/ -void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, const char *s ); - -/** - @brief Blit a sprite into the display buffer - - @param[in] disp : instance of display with display buffer - @param[in] sprite : the buffer containing the sprite (padded if necessary) - @param[in] sprite_height : the height of the sprite in pixels - @param[in] sprite_width : the width of the sprite in pixels (number of columns) - @param[in] start_col : the column on the display containing the top left corner of the sprite - @param[in] start_row : the row containing the top left corner of the sprite - */ -void ssd1306_blit(ssd1306_t *disp, const char* sprite, - uint32_t sprite_height, uint32_t sprite_width, - uint32_t start_col, uint32_t start_row); #endif diff --git a/ssd1306_widgets.c b/ssd1306_widgets.c new file mode 100644 index 0000000..7067c9c --- /dev/null +++ b/ssd1306_widgets.c @@ -0,0 +1,37 @@ +#include "ssd1306_widgets.h" + +static void render_char_to_screen(ssd1306_t *disp, const font *f, char c, uint16_t pos_x, uint16_t pos_y) { + const char *sprite_start = &(f->bitmap_buffer[(c - f->first_char_in_font) * f->bytes_per_char]); + ssd1306_blit(disp, sprite_start, f->char_height, f->char_width, pos_x, pos_y); +} + +// this assumes that the text box is at least the size of a single character +void render_textbox(ssd1306_t *disp, ssd1306_textbox *tbw) { + uint16_t lines_in_textbox = tbw->height/tbw->fnt->char_height ; + uint16_t columns_in_textbox = tbw->width/tbw->fnt->char_width ; + uint16_t tbox_col = 0; + uint16_t tbox_line = 0; + // this guarantees that there + size_t txt_idx = get_start_of_nth_line_from_end(tbw, lines_in_textbox); + while (txt_idx < tbw->tb.filled) { + char c = tbw->tb.buffer[(tbw->tb->start + txt_idx) % TEXT_BUF_LEN]; + txt_idx ++; + if ( c == '\n' || c == 13) { + tbox_col=0; + if (tbox_line < lines_in_textbox) { + tbox_line++; + } + } else { + if (tbox_col < columns_in_textbox) { + render_char_to_screen(disp, tbw->fnt, c, tbw->pos_x + (tbox_col * tbw->fnt->char_width), tbw->pos_y + (tbox_line * tbw->fnt->char_height)); + tbox_col ++; + } + } + } +} + +// have the text widget process a character +void process_char(ssd1306_textbox *tbw, char c) { + text_buffer_process_char(&tbw->tb, c); + tbw->updated = true; +} diff --git a/ssd1306_widgets.h b/ssd1306_widgets.h new file mode 100644 index 0000000..fc635da --- /dev/null +++ b/ssd1306_widgets.h @@ -0,0 +1,28 @@ +#include "ssd1306.h" +#include "text_buffer.h" +#include "font_struct.h" + +#define CREATE_TEXT_BOX_WIDGET(width_, height_, pos_x_, pos_y_, font_struct_name_, text_box_name_) \ + text_buffer text_box_name_ ##_tbuffer;\ + ssd1306_textbox text_box_name_ = {\ + .pos_x = pos_x_,\ + .pos_y = pos_y_,\ + .width = width_,\ + .height = height_,\ + .fnt = &font_struct_name_,\ + .text_buffer = &text_box_name_ ## _tbuffer\ + } + +typedef struct { + uint16_t pos_x; + uint16_t pos_y; + uint16_t width; + uint16_t height; + const font *fnt; + text_buffer *tb; + bool updated; +} ssd1306_textbox; + +void render_textbox(ssd1306_t *disp, ssd1306_textbox *text_widget); + +void process_char(ssd1306_textbox *text_widget, char c); diff --git a/text_buffer.c b/text_buffer.c new file mode 100644 index 0000000..0e3c68e --- /dev/null +++ b/text_buffer.c @@ -0,0 +1,59 @@ +#include "text_buffer.h" + +void clear(text_buffer* tbuf) { + tbuf->filled = 0; + tbuf->start = 0; +}; + +// get the start of the line that index idx is located in +// this function assumes that idx is not larger than TEXT_BUF_LEN +size_t get_beginning_of_line(text_buffer *tb, size_t idx) { + size_t i = idx; + for (; i > 0; i--) { + if (tb->buffer[(tb->start+i) % TEXT_BUF_LEN] == '\n') { + return i-1; + } + } + return i; +} + +void append_char(text_buffer *tb, char c) { + size_t write_idx = (tb->start + tb->filled) % TEXT_BUF_LEN; + tb->buffer[write_idx] = c; + if (tb->filled == TEXT_BUF_LEN) { + tb->start ++; + } else { + tb->filled ++; + } +} + +void text_buffer_process_char(text_buffer *tb, char c) { + if (c == 127 && tb->filled > 0) { // backspace + tb->filled --; + return; + } + if ((c >= 32 && c <= 126) || c == '\r') { + append_char(tb, c); + return; + } + if ( c == 9 ) { + append_char(tb, ' '); + append_char(tb, ' '); + append_char(tb, ' '); + append_char(tb, ' '); + } +} + +// find the index of the char that is the start char for the nth line back (if it exists) +// if there are less than n lines, then it returns the start of the text buffer. +// In either way it will be the top left character in a text box +size_t get_start_of_nth_line_from_end(text_buffer *tb, uint16_t lines_to_display) { + uint16_t lines = 0; + size_t current_buf_idx = tb->filled; + while (lines 0) { + current_buf_idx = get_beginning_of_line(tb, current_buf_idx); + lines_to_display ++; + } + return current_buf_idx; +} + diff --git a/text_buffer.h b/text_buffer.h new file mode 100644 index 0000000..327e0c5 --- /dev/null +++ b/text_buffer.h @@ -0,0 +1,22 @@ +#include +#include +#define TEXT_BUF_LEN 512 +enum tb_state { + WRAP, + TRUNC +}; + +typedef struct { + char buffer[TEXT_BUF_LEN]; + size_t start; + size_t filled; +} text_buffer; + +// get the start index for the last n lines in the buffer +size_t get_start_of_nth_line_from_end(text_buffer *tb, uint16_t lines_to_display); + +// clear the text buffer +void clear(text_buffer* tbuf); + +// processes the char in such a way that only printable characters end up in the text buffer +void text_buffer_process_char(text_buffer *tb, char c); From ded63d222bf251813678cd71178adcb000c3c05f Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Wed, 26 Jun 2024 04:35:24 +0800 Subject: [PATCH 14/16] Modified Changed stuff around for the build system Changed things such that the build system can straight up import the src directory as a subdirectory. Changed the example CMake to do this moved the fonts into their own directory and included that in the build system. Added the start of a widget system to the ssd1306 display (developed for other projects). --- example/CMakeLists.txt | 24 +++-- example/example.c | 42 ++++---- {example => fonts}/BMSPA_font.h | 0 {example => fonts}/acme_5_outlines_font.h | 0 basic_font.h => fonts/basic_font.h | 0 {example => fonts}/bubblesstandard_font.h | 0 {example => fonts}/crackers_font.h | 0 font.h => fonts/font.h | 0 font_struct.h => fonts/font_struct.h | 0 src/CMakeLists.txt | 37 +++++++ ssd1306.c => src/ssd1306.c | 119 +++++++++++++++++++-- ssd1306.h => src/ssd1306.h | 22 ++-- ssd1306_widgets.c => src/ssd1306_widgets.c | 16 ++- ssd1306_widgets.h => src/ssd1306_widgets.h | 18 ++-- text_buffer.c => src/text_buffer.c | 4 +- text_buffer.h => src/text_buffer.h | 2 +- 16 files changed, 220 insertions(+), 64 deletions(-) rename {example => fonts}/BMSPA_font.h (100%) rename {example => fonts}/acme_5_outlines_font.h (100%) rename basic_font.h => fonts/basic_font.h (100%) rename {example => fonts}/bubblesstandard_font.h (100%) rename {example => fonts}/crackers_font.h (100%) rename font.h => fonts/font.h (100%) rename font_struct.h => fonts/font_struct.h (100%) create mode 100644 src/CMakeLists.txt rename ssd1306.c => src/ssd1306.c (81%) rename ssd1306.h => src/ssd1306.h (91%) rename ssd1306_widgets.c => src/ssd1306_widgets.c (73%) rename ssd1306_widgets.h => src/ssd1306_widgets.h (60%) rename text_buffer.c => src/text_buffer.c (94%) rename text_buffer.h => src/text_buffer.h (92%) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index c23d133..ade9e30 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.13) # note: this must happen before project() include(pico_sdk_import.cmake) -project(ssd1306-example) +project(ssd1306-examples) set(CMAKE_C_STANDARD 11) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -14,32 +14,36 @@ option(USE_DMA_FOR_DISPLAY "Use the DMA engine to move the display buffer to the pico_sdk_init() # rest of your project - +add_subdirectory(../src lib) add_executable(ssd1306-example - example.c ../ssd1306.c + example.c ) target_include_directories(ssd1306-example PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/../ + ${CMAKE_CURRENT_LIST_DIR}/../fonts ) if(USE_DMA_FOR_DISPLAY) message(STATUS "Using DMA") target_compile_definitions(ssd1306-example - PUBLIC SSD1306_USE_DMA + PUBLIC + SSD1306_USE_DMA ) target_link_libraries(ssd1306-example hardware_dma ) endif(USE_DMA_FOR_DISPLAY) +target_link_libraries(ssd1306-example + pico_stdlib + hardware_i2c + ssd1306 + ssd1306_widgets + ) -target_link_libraries(ssd1306-example pico_stdlib hardware_i2c) - -pico_enable_stdio_usb(ssd1306-example 1) -pico_enable_stdio_uart(ssd1306-example 0) - +pico_enable_stdio_usb(ssd1306-example 0) +pico_enable_stdio_uart(ssd1306-example 1) # create map/bin/hex/uf2 file in addition to ELF. pico_add_extra_outputs(ssd1306-example) diff --git a/example/example.c b/example/example.c index 284adc4..966a519 100644 --- a/example/example.c +++ b/example/example.c @@ -17,7 +17,7 @@ const uint8_t *fonts[] = {acme_font, bubblesstandard_font, crackers_font, BMSPA_ #define SLEEPTIME 30 #ifdef SSD1306_USE_DMA -CREATE_DISPLAY(128, 64, i2c1, 0x3C, 0, 0, 01) ; +CREATE_DISPLAY(128, 64, i2c1, 0x3C, 0, 0, main_display) ; #endif @@ -54,13 +54,13 @@ void animation(void) { const char *words[]= {"SSD1306", "DISPLAY", "DRIVER"}; #ifdef SSD1306_USE_DMA - ssd1306_init(&display_01); + ssd1306_init(&main_display); #else ssd1306_t display_01; display_01.external_vcc=false; ssd1306_init(&display_01, 128, 64, 0x3C, i2c1); #endif - ssd1306_clear(&display_01); + ssd1306_clear(&main_display); printf("ANIMATION!\n"); @@ -68,33 +68,33 @@ void animation(void) { for(;;) { for(int y=0; y<31; ++y) { - ssd1306_draw_line(&display_01, 0, y, 127, y); - ssd1306_show(&display_01); + ssd1306_draw_line(&main_display, 0, y, 127, y); + ssd1306_show(&main_display); sleep_ms(SLEEPTIME); - ssd1306_clear(&display_01); + ssd1306_clear(&main_display); } for(int y=0, i=1; y>=0; y+=i) { - ssd1306_draw_line(&display_01, 0, 31-y, 127, 31+y); - ssd1306_draw_line(&display_01, 0, 31+y, 127, 31-y); - ssd1306_show(&display_01); + ssd1306_draw_line(&main_display, 0, 31-y, 127, 31+y); + ssd1306_draw_line(&main_display, 0, 31+y, 127, 31-y); + ssd1306_show(&main_display); sleep_ms(SLEEPTIME); - ssd1306_clear(&display_01); + ssd1306_clear(&main_display); if(y==32) i=-1; } for(int i=0; i #include #include -#include #include #include +#include #include "ssd1306.h" #ifdef SSD1306_USE_DMA @@ -305,7 +304,7 @@ void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { p->buffer[x+p->width*(y>>3)]&=~(0x1<<(y&0x07)); } -void ssd1306_set_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { +void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { if(x>=p->width || y>=p->height) return; p->buffer[x+p->width*(y>>3)]|=0x1<<(y&0x07); // y>>3==y/8 && y&0x7==y%8 @@ -333,6 +332,109 @@ void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t } } +void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + for(uint32_t i=0; ifont[4]) + return; + + uint32_t parts_per_line=(font[0]>>3)+((font[0]&7)>0); + for(uint8_t w=0; w>=1) { + if(line & 1) + ssd1306_draw_square(p, x+w*scale, y+((lp<<3)+j)*scale, scale, scale); + } + + ++pp; + } + } +} + +void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, const char *s) { + for(int32_t x_n=x; *s; x_n+=(font[1]+font[2])*scale) { + ssd1306_draw_char_with_font(p, x_n, y, scale, font, *(s++)); + } +} + +static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) { + switch(size) { + case 1: + return data[offset]; + case 2: + return data[offset]|(data[offset+1]<<8); + case 4: + return data[offset]|(data[offset+1]<<8)|(data[offset+2]<<16)|(data[offset+3]<<24); + default: + __builtin_unreachable(); + } + __builtin_unreachable(); +} + +void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, uint32_t y_offset) { + if(size<54) // data smaller than header + return; + + const uint32_t bfOffBits=ssd1306_bmp_get_val(data, 10, 4); + const uint32_t biSize=ssd1306_bmp_get_val(data, 14, 4); + const int32_t biWidth=(int32_t) ssd1306_bmp_get_val(data, 18, 4); + const int32_t biHeight=(int32_t) ssd1306_bmp_get_val(data, 22, 4); + const uint16_t biBitCount=(uint16_t) ssd1306_bmp_get_val(data, 28, 2); + const uint32_t biCompression=ssd1306_bmp_get_val(data, 30, 4); + + if(biBitCount!=1) // image not monochrome + return; + + if(biCompression!=0) // image compressed + return; + + const int table_start=14+biSize; + uint8_t color_val; + + for(uint8_t i=0; i<2; ++i) { + if(!((data[table_start+i*4]<<16)|(data[table_start+i*4+1]<<8)|data[table_start+i*4+2])) { + color_val=i; + break; + } + } + + uint32_t bytes_per_line=(biWidth/8)+(biWidth&7?1:0); + if(bytes_per_line&3) + bytes_per_line=(bytes_per_line^(bytes_per_line&3))+4; + + const uint8_t *img_data=data+bfOffBits; + + int step=biHeight>0?-1:1; + int border=biHeight>0?-1:biHeight; + + for(uint32_t y=biHeight>0?biHeight-1:0; y!=border; y+=step) { + for(uint32_t x=0; x>3]>>(7-(x&7)))&1)==color_val) + ssd1306_draw_pixel(p, x_offset+x, y_offset+y); + } + img_data+=bytes_per_line; + } +} + +void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size) { + ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0); +} + #ifdef SSD1306_USE_DMA void copy_to_dma_tx(ssd1306_t *disp) { disp->dma_tx_buffer[0] = 1u << I2C_IC_DATA_CMD_RESTART_LSB | 0x0040; @@ -342,13 +444,12 @@ void copy_to_dma_tx(ssd1306_t *disp) { disp->dma_tx_buffer[disp->bufsize] = (1u << I2C_IC_DATA_CMD_STOP_LSB) | disp->buffer[disp->bufsize-1]; } -void print_content_of_dma_tx(ssd1306_t *disp) { +void print_content_of_dma_tx(ssd1306_t *disp, void (*sprint_fn)(char *)) { char print_buf[10]; - printf("\r\nPrinting DMA content\r\n\n"); for (int i = 0; i < disp->height/8; i++) { for(int j = 0; j < disp->width; j++) { sprintf(print_buf, "%04x", disp->dma_tx_buffer[i*disp->width + j + 1]); - uart_puts(uart0, print_buf); + sprint_fn(print_buf); } } } @@ -356,14 +457,14 @@ void print_content_of_dma_tx(ssd1306_t *disp) { // we need to load all the data from out actual display buffer into // into the buffer that will be read out by the DMA. Then we need to set // the appropriate control bits for the top 8 bits of every 16 bit word -void print_frame_buffer_content(ssd1306_t *disp, void (*sprint_fn)(char *, size_t) { +void print_out_buffer_content(ssd1306_t *disp, void (*sprint_fn)(char *)) { unsigned int pages = disp->height / 8; char text_out[10]; for (int i = 0; i < pages; i++) { - printf("\r\n"); + sprint_fn("\r\n"); for (int j = 0; j < disp->width; j ++) { sprintf(text_out, "%02x", disp->buffer[i*pages + j]); - sprint_fn(text_out, strlen(text_out) + 1); + sprint_fn(text_out); } } } diff --git a/ssd1306.h b/src/ssd1306.h similarity index 91% rename from ssd1306.h rename to src/ssd1306.h index 9abc8a3..95af429 100644 --- a/ssd1306.h +++ b/src/ssd1306.h @@ -31,8 +31,8 @@ SOFTWARE. #ifndef _inc_ssd1306 #define _inc_ssd1306 #include -#include -#include +#include "hardware/i2c.h" +#include "hardware/i2c.h" /** * @brief defines commands used in ssd1306 @@ -63,12 +63,12 @@ typedef enum { * at compile time. This allows omitting the code to define the variables at runtime * as all the details are known at compile time */ -#define CREATE_DISPLAY(width_, height_, I2C, address_, dma_channel_, external_vcc_, varname) \ - uint8_t display_buffer_ ## varname[width_*height_/8];\ - uint16_t dma_tx_bufferbuffer_ ## varname[(width_*height_/8)+1];\ - ssd1306_t display_ ## id = {\ - .dma_tx_buffer = dma_tx_bufferbuffer_ ## varname,\ - .buffer = display_buffer_ ## varname,\ +#define CREATE_DISPLAY(width_, height_, I2C, address_, dma_channel_, external_vcc_, varname_) \ + uint8_t display_buffer_ ## varname_[width_*height_/8];\ + uint16_t dma_tx_bufferbuffer_ ## varname_[(width_*height_/8)+1];\ + ssd1306_t varname_ = {\ + .dma_tx_buffer = dma_tx_bufferbuffer_ ## varname_,\ + .buffer = display_buffer_ ## varname_,\ .bufsize = width_ * height_ / 8,\ .width = width_,\ .height = height_,\ @@ -76,7 +76,7 @@ typedef enum { .address = address_,\ .dma_channel = dma_channel_,\ .external_vcc = external_vcc_,\ - .i2c_i = I2C,\ + .i2c_i = I2C\ } #endif @@ -229,3 +229,7 @@ void ssd1306_set_pixel(ssd1306_t *p, uint32_t x, uint32_t y); */ void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2); #endif + +void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size); + +void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, const char *s); diff --git a/ssd1306_widgets.c b/src/ssd1306_widgets.c similarity index 73% rename from ssd1306_widgets.c rename to src/ssd1306_widgets.c index 7067c9c..b39bf03 100644 --- a/ssd1306_widgets.c +++ b/src/ssd1306_widgets.c @@ -12,9 +12,9 @@ void render_textbox(ssd1306_t *disp, ssd1306_textbox *tbw) { uint16_t tbox_col = 0; uint16_t tbox_line = 0; // this guarantees that there - size_t txt_idx = get_start_of_nth_line_from_end(tbw, lines_in_textbox); + size_t txt_idx = get_start_of_nth_line_from_end(&tbw->tb, lines_in_textbox); while (txt_idx < tbw->tb.filled) { - char c = tbw->tb.buffer[(tbw->tb->start + txt_idx) % TEXT_BUF_LEN]; + char c = tbw->tb.buffer[(tbw->tb.start + txt_idx) % TEXT_BUF_LEN]; txt_idx ++; if ( c == '\n' || c == 13) { tbox_col=0; @@ -28,10 +28,16 @@ void render_textbox(ssd1306_t *disp, ssd1306_textbox *tbw) { } } } + tbw->updated = false; } // have the text widget process a character -void process_char(ssd1306_textbox *tbw, char c) { - text_buffer_process_char(&tbw->tb, c); - tbw->updated = true; +void process_char(ssd1306_textbox *text_widget, char c) { + text_buffer_process_char(&text_widget->tb, c); + text_widget->updated = true; +} + +void clear_textbox(ssd1306_textbox *text_widget) { + text_buffer_clear(&text_widget->tb); + text_widget->updated = true; } diff --git a/ssd1306_widgets.h b/src/ssd1306_widgets.h similarity index 60% rename from ssd1306_widgets.h rename to src/ssd1306_widgets.h index fc635da..59b850b 100644 --- a/ssd1306_widgets.h +++ b/src/ssd1306_widgets.h @@ -2,16 +2,15 @@ #include "text_buffer.h" #include "font_struct.h" -#define CREATE_TEXT_BOX_WIDGET(width_, height_, pos_x_, pos_y_, font_struct_name_, text_box_name_) \ - text_buffer text_box_name_ ##_tbuffer;\ - ssd1306_textbox text_box_name_ = {\ +// Create an instance of a Text Box +#define CREATE_TEXT_BOX_WIDGET(width_, height_, pos_x_, pos_y_, font_struct_name_, varname_) \ + ssd1306_textbox varname_ = {\ .pos_x = pos_x_,\ .pos_y = pos_y_,\ .width = width_,\ .height = height_,\ - .fnt = &font_struct_name_,\ - .text_buffer = &text_box_name_ ## _tbuffer\ - } + .fnt = &font_struct_name_\ + };\ typedef struct { uint16_t pos_x; @@ -19,10 +18,15 @@ typedef struct { uint16_t width; uint16_t height; const font *fnt; - text_buffer *tb; + text_buffer tb; bool updated; } ssd1306_textbox; +// Render a text box to a screen void render_textbox(ssd1306_t *disp, ssd1306_textbox *text_widget); +// translate a character in a text box void process_char(ssd1306_textbox *text_widget, char c); + +// clear the content of the text box +void clear_textbox(ssd1306_textbox *text_widget); diff --git a/text_buffer.c b/src/text_buffer.c similarity index 94% rename from text_buffer.c rename to src/text_buffer.c index 0e3c68e..c5d4506 100644 --- a/text_buffer.c +++ b/src/text_buffer.c @@ -1,6 +1,6 @@ #include "text_buffer.h" -void clear(text_buffer* tbuf) { +void text_buffer_clear(text_buffer* tbuf) { tbuf->filled = 0; tbuf->start = 0; }; @@ -28,7 +28,7 @@ void append_char(text_buffer *tb, char c) { } void text_buffer_process_char(text_buffer *tb, char c) { - if (c == 127 && tb->filled > 0) { // backspace + if (c == '\b' && tb->filled > 0) { // backspace tb->filled --; return; } diff --git a/text_buffer.h b/src/text_buffer.h similarity index 92% rename from text_buffer.h rename to src/text_buffer.h index 327e0c5..6c2fba1 100644 --- a/text_buffer.h +++ b/src/text_buffer.h @@ -16,7 +16,7 @@ typedef struct { size_t get_start_of_nth_line_from_end(text_buffer *tb, uint16_t lines_to_display); // clear the text buffer -void clear(text_buffer* tbuf); +void text_buffer_clear(text_buffer* tbuf); // processes the char in such a way that only printable characters end up in the text buffer void text_buffer_process_char(text_buffer *tb, char c); From 222e58151da53c96fdfba6c67b595c30c1977883 Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sun, 30 Jun 2024 02:37:27 +0800 Subject: [PATCH 15/16] Change: Prepare folder for multiple examples Create a new subfolder and update the example folder to include the subfolders containing our examples --- example/CMakeLists.txt | 51 +----------------- example/basic_example/CMakeLists.txt | 49 +++++++++++++++++ example/{ => basic_example}/example.c | 0 example/{ => basic_example}/image.h | 0 .../{ => basic_example}/pico_sdk_import.cmake | 0 example/{ => basic_example}/test.bmp | Bin 6 files changed, 51 insertions(+), 49 deletions(-) create mode 100644 example/basic_example/CMakeLists.txt rename example/{ => basic_example}/example.c (100%) rename example/{ => basic_example}/image.h (100%) rename example/{ => basic_example}/pico_sdk_import.cmake (100%) rename example/{ => basic_example}/test.bmp (100%) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ade9e30..04ed6d7 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,49 +1,2 @@ -cmake_minimum_required(VERSION 3.13) - -# initialize the SDK based on PICO_SDK_PATH -# note: this must happen before project() -include(pico_sdk_import.cmake) - -project(ssd1306-examples) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -option(USE_DMA_FOR_DISPLAY "Use the DMA engine to move the display buffer to the display" OFF) - -# initialize the Raspberry Pi Pico SDK -pico_sdk_init() - -# rest of your project -add_subdirectory(../src lib) -add_executable(ssd1306-example - example.c -) - -target_include_directories(ssd1306-example - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/../fonts -) - -if(USE_DMA_FOR_DISPLAY) - message(STATUS "Using DMA") - target_compile_definitions(ssd1306-example - PUBLIC - SSD1306_USE_DMA - ) - target_link_libraries(ssd1306-example - hardware_dma - ) -endif(USE_DMA_FOR_DISPLAY) - -target_link_libraries(ssd1306-example - pico_stdlib - hardware_i2c - ssd1306 - ssd1306_widgets - ) - -pico_enable_stdio_usb(ssd1306-example 0) -pico_enable_stdio_uart(ssd1306-example 1) -# create map/bin/hex/uf2 file in addition to ELF. -pico_add_extra_outputs(ssd1306-example) - +add_subdirectory(basic_example) +add_subdirectory(usb_cdc_example) diff --git a/example/basic_example/CMakeLists.txt b/example/basic_example/CMakeLists.txt new file mode 100644 index 0000000..68cccfd --- /dev/null +++ b/example/basic_example/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.13) + +# initialize the SDK based on PICO_SDK_PATH +# note: this must happen before project() +include(pico_sdk_import.cmake) + +project(ssd1306-examples) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +option(USE_DMA_FOR_DISPLAY "Use the DMA engine to move the display buffer to the display" OFF) + +# initialize the Raspberry Pi Pico SDK +pico_sdk_init() + +# rest of your project +add_subdirectory(../../src lib) +add_executable(ssd1306-example + example.c +) + +target_include_directories(ssd1306-example + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../fonts +) + +if(USE_DMA_FOR_DISPLAY) + message(STATUS "Using DMA") + target_compile_definitions(ssd1306-example + PUBLIC + SSD1306_USE_DMA + ) + target_link_libraries(ssd1306-example + hardware_dma + ) +endif(USE_DMA_FOR_DISPLAY) + +target_link_libraries(ssd1306-example + pico_stdlib + hardware_i2c + ssd1306 + ssd1306_widgets + ) + +pico_enable_stdio_usb(ssd1306-example 0) +pico_enable_stdio_uart(ssd1306-example 1) +# create map/bin/hex/uf2 file in addition to ELF. +pico_add_extra_outputs(ssd1306-example) + diff --git a/example/example.c b/example/basic_example/example.c similarity index 100% rename from example/example.c rename to example/basic_example/example.c diff --git a/example/image.h b/example/basic_example/image.h similarity index 100% rename from example/image.h rename to example/basic_example/image.h diff --git a/example/pico_sdk_import.cmake b/example/basic_example/pico_sdk_import.cmake similarity index 100% rename from example/pico_sdk_import.cmake rename to example/basic_example/pico_sdk_import.cmake diff --git a/example/test.bmp b/example/basic_example/test.bmp similarity index 100% rename from example/test.bmp rename to example/basic_example/test.bmp From 3537055729e7282c515b01ac2e1b5d209fe882fc Mon Sep 17 00:00:00 2001 From: Alexande Becker Date: Sun, 30 Jun 2024 18:55:14 +0800 Subject: [PATCH 16/16] Add Example printing the data sent via usb-serial The new example shows the text typed over a serial interface on the ssd1306, including basic control char handling --- example/usb_cdc_example/CMakeLists.txt | 56 ++++ example/usb_cdc_example/pico_sdk_import.cmake | 62 ++++ example/usb_cdc_example/tusb_config.h | 126 ++++++++ example/usb_cdc_example/usb_cdc_descriptors.c | 280 ++++++++++++++++++ example/usb_cdc_example/usb_descriptors.h | 37 +++ example/usb_cdc_example/usb_ssd1306.c | 197 ++++++++++++ 6 files changed, 758 insertions(+) create mode 100644 example/usb_cdc_example/CMakeLists.txt create mode 100644 example/usb_cdc_example/pico_sdk_import.cmake create mode 100644 example/usb_cdc_example/tusb_config.h create mode 100644 example/usb_cdc_example/usb_cdc_descriptors.c create mode 100644 example/usb_cdc_example/usb_descriptors.h create mode 100644 example/usb_cdc_example/usb_ssd1306.c diff --git a/example/usb_cdc_example/CMakeLists.txt b/example/usb_cdc_example/CMakeLists.txt new file mode 100644 index 0000000..d9a805c --- /dev/null +++ b/example/usb_cdc_example/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.13) + +# initialize the SDK based on PICO_SDK_PATH +# note: this must happen before project() +include(pico_sdk_import.cmake) + +project(usb-cdc-example) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +option(USE_DMA_FOR_DISPLAY "Use the DMA engine to move the display buffer to the display" ON) + +# initialize the Raspberry Pi Pico SDK +pico_sdk_init() + +# add the subdirectory containing the ssd1306 library code +add_subdirectory(../../src lib) + +# create the example target +add_executable(usb-cdc-example + usb_ssd1306.c + usb_cdc_descriptors.c +) + +# make sure the example target sees the fonts +target_include_directories(usb-cdc-example + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/../../fonts +) + +if(USE_DMA_FOR_DISPLAY) + message(STATUS "Using DMA") + target_compile_definitions(usb-cdc-example + PUBLIC + SSD1306_USE_DMA + ) + target_link_libraries(usb-cdc-example + hardware_dma + ) +endif(USE_DMA_FOR_DISPLAY) + +target_link_libraries(usb-cdc-example + pico_stdlib + pico_unique_id + hardware_i2c + ssd1306 + ssd1306_widgets + tinyusb_device + tinyusb_board + ) + +pico_enable_stdio_usb(usb-cdc-example 0) +pico_enable_stdio_uart(usb-cdc-example 1) +# create map/bin/hex/uf2 file in addition to ELF. +pico_add_extra_outputs(usb-cdc-example) diff --git a/example/usb_cdc_example/pico_sdk_import.cmake b/example/usb_cdc_example/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/example/usb_cdc_example/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/example/usb_cdc_example/tusb_config.h b/example/usb_cdc_example/tusb_config.h new file mode 100644 index 0000000..73fd5db --- /dev/null +++ b/example/usb_cdc_example/tusb_config.h @@ -0,0 +1,126 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// stuff from the tinyusb exaple for CDC +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +#define CFG_TUD_ENABLED 1 + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_HID 0 +#define CFG_TUD_CDC 1 +#define CFG_TUD_MSC 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_EP_BUFSIZE 16 +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/example/usb_cdc_example/usb_cdc_descriptors.c b/example/usb_cdc_example/usb_cdc_descriptors.c new file mode 100644 index 0000000..e926ee6 --- /dev/null +++ b/example/usb_cdc_example/usb_cdc_descriptors.c @@ -0,0 +1,280 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "pico/unique_id.h" +#include "tusb.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) + +#define USB_VID 0xCafe +#define USB_BCD 0x0200 + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, + + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +enum +{ + ITF_NUM_CDC_0 = 0, + ITF_NUM_CDC_0_DATA, + // ITF_NUM_CDC_1, + // ITF_NUM_CDC_1_DATA, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... + #define EPNUM_CDC_0_NOTIF 0x81 + #define EPNUM_CDC_0_OUT 0x02 + #define EPNUM_CDC_0_IN 0x82 + + #define EPNUM_CDC_1_NOTIF 0x84 + #define EPNUM_CDC_1_OUT 0x05 + #define EPNUM_CDC_1_IN 0x85 + +#elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X + // SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT + // e.g EP1 OUT & EP1 IN cannot exist together + #define EPNUM_CDC_0_NOTIF 0x81 + #define EPNUM_CDC_0_OUT 0x02 + #define EPNUM_CDC_0_IN 0x83 + + #define EPNUM_CDC_1_NOTIF 0x84 + #define EPNUM_CDC_1_OUT 0x05 + #define EPNUM_CDC_1_IN 0x86 + +#elif CFG_TUSB_MCU == OPT_MCU_FT90X || CFG_TUSB_MCU == OPT_MCU_FT93X + // FT9XX doesn't support a same endpoint number with different direction IN and OUT + // e.g EP1 OUT & EP1 IN cannot exist together + #define EPNUM_CDC_0_NOTIF 0x81 + #define EPNUM_CDC_0_OUT 0x02 + #define EPNUM_CDC_0_IN 0x83 + + #define EPNUM_CDC_1_NOTIF 0x84 + #define EPNUM_CDC_1_OUT 0x05 + #define EPNUM_CDC_1_IN 0x86 + +#else + #define EPNUM_CDC_0_NOTIF 0x81 + #define EPNUM_CDC_0_OUT 0x02 + #define EPNUM_CDC_0_IN 0x82 + + #define EPNUM_CDC_1_NOTIF 0x83 + #define EPNUM_CDC_1_OUT 0x04 + #define EPNUM_CDC_1_IN 0x84 +#endif + +uint8_t const desc_fs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + // 1st CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, 4, EPNUM_CDC_0_NOTIF, 8, EPNUM_CDC_0_OUT, EPNUM_CDC_0_IN, 64), + + // 2nd CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. + // TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1, 4, EPNUM_CDC_1_NOTIF, 8, EPNUM_CDC_1_OUT, EPNUM_CDC_1_IN, 64), +}; + +#if TUD_OPT_HIGH_SPEED +// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration + +uint8_t const desc_hs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + // 1st CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, 4, EPNUM_CDC_0_NOTIF, 8, EPNUM_CDC_0_OUT, EPNUM_CDC_0_IN, 512), + + // 2nd CDC: Interface number, string index, EP notification address and size, EP data address (out, in) and size. + //TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1, 4, EPNUM_CDC_1_NOTIF, 8, EPNUM_CDC_1_OUT, EPNUM_CDC_1_IN, 512), +}; + +// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed +tusb_desc_device_qualifier_t const desc_device_qualifier = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, + + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +uint8_t const* tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const*) &desc_device_qualifier; +} + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + // if link speed is high return fullspeed config, and vice versa + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration; +} + +#endif // highspeed + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + +#if TUD_OPT_HIGH_SPEED + // Although we are highspeed, host may be fullspeed. + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; +#else + return desc_fs_configuration; +#endif +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// String Descriptor Index +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, +}; + + +// buffer to hold flash ID +char serial[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1]; + +// array of pointer to string descriptors +char const *string_desc_arr[] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "TinyUSB", // 1: Manufacturer + "TinyUSB Device", // 2: Product + serial, // 3: Serials will use unique ID if possible + "TinyUSB CDC", // 4: CDC Interface +}; + +static uint16_t _desc_str[32 + 1]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + (void) langid; + size_t chr_count; + + switch ( index ) { + case STRID_LANGID: + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + break; + + case STRID_SERIAL: + // get the string from the pico sdk and do an intentional fallthrough + pico_get_unique_board_id_string(serial, sizeof(serial)); + default: + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL; + + const char *str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type + if ( chr_count > max_count ) chr_count = max_count; + + // Convert ASCII string into UTF-16 + for ( size_t i = 0; i < chr_count; i++ ) { + _desc_str[1 + i] = str[i]; + } + break; + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); + + return _desc_str; +} diff --git a/example/usb_cdc_example/usb_descriptors.h b/example/usb_cdc_example/usb_descriptors.h new file mode 100644 index 0000000..ca8925a --- /dev/null +++ b/example/usb_cdc_example/usb_descriptors.h @@ -0,0 +1,37 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef USB_DESCRIPTORS_H_ +#define USB_DESCRIPTORS_H_ + +enum +{ + REPORT_ID_KEYBOARD = 1, + REPORT_ID_MOUSE, + REPORT_ID_CONSUMER_CONTROL, + REPORT_ID_GAMEPAD, + REPORT_ID_COUNT +}; + +#endif /* USB_DESCRIPTORS_H_ */ diff --git a/example/usb_cdc_example/usb_ssd1306.c b/example/usb_cdc_example/usb_ssd1306.c new file mode 100644 index 0000000..a061ec6 --- /dev/null +++ b/example/usb_cdc_example/usb_ssd1306.c @@ -0,0 +1,197 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include +#include + +#include "pico/stdlib.h" +#include "bsp/board.h" +#include "tusb.h" +#include "hardware/i2c.h" +#include "ssd1306.h" +#include "ssd1306_widgets.h" +#include "usb_descriptors.h" +#include "basic_font.h" + +#define TXT_BUFFER_SIZE 1024 +#define ITF 0 + +// I2C defines +#define I2C_ID i2c1 +#define I2C_BAUD 200000 +#define I2C_SDA_PIN 2 +#define I2C_SCL_PIN 3 + +// Display Defines +#define DISPLAY_SIZE_X 128 +#define DISPLAY_SIZE_Y 64 +#define FONT_SIZE_X 8 +#define FONT_SIZE_Y 8 +#define DCHAR_BUF_SIZE 8 * 25 +#define DISPLAY_I2C_ADDR 0x3C +#define SLEEPTIME 500 + +CREATE_DISPLAY(DISPLAY_SIZE_X, DISPLAY_SIZE_Y, I2C_ID, DISPLAY_I2C_ADDR, 0, 0, main_display); + +CREATE_TEXT_BOX_WIDGET(DISPLAY_SIZE_X, DISPLAY_SIZE_Y, 0, 0, basic_font, main_text_box); + +void display_hw_init() { + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SCL_PIN); + gpio_pull_up(I2C_SDA_PIN); + i2c_init(I2C_ID, I2C_BAUD); +} + +void display_init() { + text_buffer_clear(&main_text_box.tb); + ssd1306_init(&main_display); + ssd1306_clear(&main_display); + ssd1306_show(&main_display); +} + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +void led_blinking_task(void); +void cdc_task(); +void display_task(); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + display_hw_init(); + tusb_init(); + display_init(); + + + while (1) + { + tud_task(); // tinyusb device task + cdc_task(); + led_blinking_task(); + display_task(); + } +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// USB CDC +void cdc_task() { + + static char display_text_buffer[TXT_BUFFER_SIZE]; + // connected() check for DTR bit + // Most but not all terminal client set this when making connection + // if ( tud_cdc_n_connected(itf) ) + { + if (tud_cdc_n_available(ITF)) { + uint8_t input_buf[64]; + + uint32_t count = tud_cdc_n_read(ITF, input_buf, sizeof(input_buf)); + + // echo back to both serial ports + for (uint32_t i = 0; i < count; i++) { + snprintf(display_text_buffer, TXT_BUFFER_SIZE, "0x%2X ", input_buf[i]); + tud_cdc_n_write_str(ITF, display_text_buffer); + if (input_buf[i] == 13 || input_buf[i] == 10) { + tud_cdc_n_write_char(ITF, 13); + tud_cdc_n_write_char(ITF, 10); + } + process_char(&main_text_box, input_buf[i]); + } + tud_cdc_n_write_flush(ITF); + } + } +} + +void display_task() { + if (main_text_box.updated) { + ssd1306_clear(&main_display); + render_textbox(&main_display, &main_text_box); + ssd1306_show(&main_display); + } +} + +void led_blinking_task(void) +{ + static uint32_t start_ms = 0; + static bool led_state = false; + + // blink is disabled + if (!blink_interval_ms) return; + + // Blink every interval ms + if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +}