diff --git a/app/include/u8g_config.h b/app/include/u8g_config.h index f2aaeddf..99a137e4 100644 --- a/app/include/u8g_config.h +++ b/app/include/u8g_config.h @@ -77,6 +77,10 @@ U8G_DISPLAY_TABLE_ENTRY(ssd1306_128x64_hw_spi) \ #undef U8G_DISPLAY_TABLE_ENTRY + +// Special display device to provide run-length encoded framebuffer contents +// to a Lua callback: +//#define U8G_DISPLAY_FB_RLE // // *************************************************************************** diff --git a/app/modules/u8g.c b/app/modules/u8g.c index 5d7a1ef3..65edd901 100644 --- a/app/modules/u8g.c +++ b/app/modules/u8g.c @@ -2,24 +2,13 @@ #include "module.h" #include "lauxlib.h" -#include "platform.h" - -#include "c_stdlib.h" +#include "lmem.h" #include "u8g.h" +#include "u8g_glue.h" #include "u8g_config.h" -struct _lu8g_userdata_t -{ - u8g_t u8g; -}; - -typedef struct _lu8g_userdata_t lu8g_userdata_t; - -// shorthand macro for the u8g structure inside the userdata -#define LU8G (&(lud->u8g)) - // helper function: retrieve and check userdata argument static lu8g_userdata_t *get_lud( lua_State *L ) @@ -780,214 +769,6 @@ static int lu8g_setFontLineSpacingFactor( lua_State *L ) } -// ------------------------------------------------------------ -// comm functions -// -#define I2C_CMD_MODE 0x000 -#define I2C_DATA_MODE 0x040 - -#define ESP_I2C_ID 0 - - -static uint8_t do_i2c_start(uint8_t id, uint8_t sla) -{ - platform_i2c_send_start( id ); - - // ignore return value -> tolerate missing ACK - platform_i2c_send_address( id, sla, PLATFORM_I2C_DIRECTION_TRANSMITTER ); - - return 1; -} - -static uint8_t u8g_com_esp8266_ssd_start_sequence(u8g_t *u8g) -{ - /* are we requested to set the a0 state? */ - if ( u8g->pin_list[U8G_PI_SET_A0] == 0 ) - return 1; - - /* setup bus, might be a repeated start */ - if ( do_i2c_start( ESP_I2C_ID, u8g->i2c_addr ) == 0 ) - return 0; - if ( u8g->pin_list[U8G_PI_A0_STATE] == 0 ) - { - // ignore return value -> tolerate missing ACK - if ( platform_i2c_send_byte( ESP_I2C_ID, I2C_CMD_MODE ) == 0 ) - ; //return 0; - } - else - { - platform_i2c_send_byte( ESP_I2C_ID, I2C_DATA_MODE ); - } - - u8g->pin_list[U8G_PI_SET_A0] = 0; - return 1; -} - - -static void lu8g_digital_write( u8g_t *u8g, uint8_t pin_index, uint8_t value ) -{ - uint8_t pin; - - pin = u8g->pin_list[pin_index]; - if ( pin != U8G_PIN_NONE ) - platform_gpio_write( pin, value ); -} - -void u8g_Delay(u8g_t *u8g, uint16_t msec) -{ - const uint16_t chunk = 50; - - if (u8g->use_delay == 0) - return; - - while (msec > chunk) - { - os_delay_us( chunk*1000 ); - msec -= chunk; - } - if (msec > 0) - os_delay_us( msec*1000 ); -} -void u8g_MicroDelay(void) -{ - os_delay_us( 1 ); -} -void u8g_10MicroDelay(void) -{ - os_delay_us( 10 ); -} - - -uint8_t u8g_com_esp8266_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) -{ - switch(msg) - { - case U8G_COM_MSG_STOP: - break; - - case U8G_COM_MSG_INIT: - // we assume that the SPI interface was already initialized - // just care for the /CS and D/C pins - lu8g_digital_write( u8g, U8G_PI_CS, PLATFORM_GPIO_HIGH ); - platform_gpio_mode( u8g->pin_list[U8G_PI_CS], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT ); - platform_gpio_mode( u8g->pin_list[U8G_PI_A0], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT ); - break; - - case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ - lu8g_digital_write( u8g, U8G_PI_A0, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH ); - break; - - case U8G_COM_MSG_CHIP_SELECT: - if (arg_val == 0) - { - /* disable */ - lu8g_digital_write( u8g, U8G_PI_CS, PLATFORM_GPIO_HIGH ); - } - else - { - /* enable */ - //u8g_com_arduino_digital_write(u8g, U8G_PI_SCK, LOW); - lu8g_digital_write( u8g, U8G_PI_CS, PLATFORM_GPIO_LOW ); - } - break; - - case U8G_COM_MSG_RESET: - if ( u8g->pin_list[U8G_PI_RESET] != U8G_PIN_NONE ) - lu8g_digital_write( u8g, U8G_PI_RESET, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH ); - break; - - case U8G_COM_MSG_WRITE_BYTE: - platform_spi_send( 1, 8, arg_val ); - break; - - case U8G_COM_MSG_WRITE_SEQ: - case U8G_COM_MSG_WRITE_SEQ_P: - { - register uint8_t *ptr = arg_ptr; - while( arg_val > 0 ) - { - platform_spi_send( 1, 8, *ptr++ ); - arg_val--; - } - } - break; - } - return 1; -} - - -uint8_t u8g_com_esp8266_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) -{ - switch(msg) - { - case U8G_COM_MSG_INIT: - // we assume that the i2c bus was already initialized - //u8g_i2c_init(u8g->pin_list[U8G_PI_I2C_OPTION]); - - break; - - case U8G_COM_MSG_STOP: - break; - - case U8G_COM_MSG_RESET: - /* Currently disabled, but it could be enable. Previous restrictions have been removed */ - /* u8g_com_arduino_digital_write(u8g, U8G_PI_RESET, arg_val); */ - break; - - case U8G_COM_MSG_CHIP_SELECT: - u8g->pin_list[U8G_PI_A0_STATE] = 0; - u8g->pin_list[U8G_PI_SET_A0] = 1; /* force a0 to set again, also forces start condition */ - if ( arg_val == 0 ) - { - /* disable chip, send stop condition */ - platform_i2c_send_stop( ESP_I2C_ID ); - } - else - { - /* enable, do nothing: any byte writing will trigger the i2c start */ - } - break; - - case U8G_COM_MSG_WRITE_BYTE: - //u8g->pin_list[U8G_PI_SET_A0] = 1; - if ( u8g_com_esp8266_ssd_start_sequence(u8g) == 0 ) - return platform_i2c_send_stop( ESP_I2C_ID ), 0; - // ignore return value -> tolerate missing ACK - if ( platform_i2c_send_byte( ESP_I2C_ID, arg_val) == 0 ) - ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0; - // platform_i2c_send_stop( ESP_I2C_ID ); - break; - - case U8G_COM_MSG_WRITE_SEQ: - case U8G_COM_MSG_WRITE_SEQ_P: - //u8g->pin_list[U8G_PI_SET_A0] = 1; - if ( u8g_com_esp8266_ssd_start_sequence(u8g) == 0 ) - return platform_i2c_send_stop( ESP_I2C_ID ), 0; - { - register uint8_t *ptr = arg_ptr; - while( arg_val > 0 ) - { - // ignore return value -> tolerate missing ACK - if ( platform_i2c_send_byte( ESP_I2C_ID, *ptr++) == 0 ) - ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0; - arg_val--; - } - } - // platform_i2c_send_stop( ESP_I2C_ID ); - break; - - case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ - u8g->pin_list[U8G_PI_A0_STATE] = arg_val; - u8g->pin_list[U8G_PI_SET_A0] = 1; /* force a0 to set again */ - - break; - } - return 1; -} - - - - // device destructor static int lu8g_close_display( lua_State *L ) { @@ -996,6 +777,19 @@ static int lu8g_close_display( lua_State *L ) if ((lud = get_lud( L )) == NULL) return 0; + if (lud->cb_ref != LUA_NOREF) { + // this is the fb_rle device + u8g_dev_t *fb_dev = LU8G->dev; + u8g_pb_t *fb_dev_pb = (u8g_pb_t *)(fb_dev->dev_mem); + uint8_t *fb_dev_buf = fb_dev_pb->buf; + + luaM_free( L, fb_dev_buf ); + luaM_free( L, fb_dev_pb ); + luaM_free( L, fb_dev ); + + luaL_unref( L, lud->cb_ref, LUA_REGISTRYINDEX ); + } + return 0; } @@ -1016,9 +810,10 @@ static int lu8g_close_display( lua_State *L ) return luaL_error( L, "i2c address required" ); \ \ lu8g_userdata_t *lud = (lu8g_userdata_t *) lua_newuserdata( L, sizeof( lu8g_userdata_t ) ); \ + lud->cb_ref = LUA_NOREF; \ \ - lud->u8g.i2c_addr = (uint8_t)addr; \ - lud->u8g.use_delay = del > 0 ? 1 : 0; \ + lud->i2c_addr = (uint8_t)addr; \ + lud->use_delay = del > 0 ? 1 : 0; \ \ u8g_InitI2C( LU8G, &u8g_dev_ ## device, U8G_I2C_OPT_NONE); \ \ @@ -1049,8 +844,9 @@ U8G_DISPLAY_TABLE_I2C unsigned del = luaL_optinteger( L, 4, 0 ); \ \ lu8g_userdata_t *lud = (lu8g_userdata_t *) lua_newuserdata( L, sizeof( lu8g_userdata_t ) ); \ + lud->cb_ref = LUA_NOREF; \ \ - lud->u8g.use_delay = del > 0 ? 1 : 0; \ + lud->use_delay = del > 0 ? 1 : 0; \ \ u8g_InitHWSPI( LU8G, &u8g_dev_ ## device, cs, dc, res ); \ \ @@ -1064,6 +860,53 @@ U8G_DISPLAY_TABLE_I2C // Unroll the display table and insert binding functions for SPI based displays. U8G_DISPLAY_TABLE_SPI // +// +// +// This display forwards the framebuffer contents as run-length encoded chunks to a Lua callback +static int lu8g_fb_rle( lua_State *L ) { + lu8g_userdata_t *lud; + + if ((lua_type( L, 1 ) != LUA_TFUNCTION) && + (lua_type( L, 1 ) != LUA_TLIGHTFUNCTION)) { + luaL_typerror( L, 1, "function" ); + } + + int width = luaL_checkint( L, 2 ); + int height = luaL_checkint( L, 3 ); + + luaL_argcheck( L, (width > 0) && (width < 256) && ((width % 8) == 0), 2, "invalid width" ); + luaL_argcheck( L, (height > 0) && (height < 256) && ((height % 8) == 0), 3, "invalid height" ); + + // construct display device structures manually because width and height are configurable + uint8_t *fb_dev_buf = (uint8_t *)luaM_malloc( L, width ); + + u8g_pb_t *fb_dev_pb = (u8g_pb_t *)luaM_malloc( L, sizeof( u8g_pb_t ) ); + fb_dev_pb->p.page_height = 8; + fb_dev_pb->p.total_height = height; + fb_dev_pb->p.page_y0 = 0; + fb_dev_pb->p.page_y1 = 0; + fb_dev_pb->p.page = 0; + fb_dev_pb->width = width; + fb_dev_pb->buf = fb_dev_buf; + + u8g_dev_t *fb_dev = (u8g_dev_t *)luaM_malloc( L, sizeof( u8g_dev_t ) ); + fb_dev->dev_fn = u8g_dev_gen_fb_fn; + fb_dev->dev_mem = fb_dev_pb; + fb_dev->com_fn = u8g_com_esp8266_fbrle_fn; + + lud = (lu8g_userdata_t *) lua_newuserdata( L, sizeof( lu8g_userdata_t ) ); + lua_pushvalue( L, 1 ); // copy argument (func) to the top of stack + lud->cb_ref = luaL_ref( L, LUA_REGISTRYINDEX ); + + /* set metatable for userdata */ + luaL_getmetatable(L, "u8g.display"); + lua_setmetatable(L, -2); + + u8g_Init8BitFixedPort( LU8G, fb_dev, width, height, 0, 0, 0); + + return 1; +} +// // *************************************************************************** @@ -1137,6 +980,8 @@ static const LUA_REG_TYPE lu8g_map[] = { #define U8G_FONT_TABLE_ENTRY(font) \ { LSTRKEY( #font ), LUDATA( (void *)(u8g_ ## font) ) }, U8G_FONT_TABLE + // + { LSTRKEY( "fb_rle" ), LFUNCVAL( lu8g_fb_rle ) }, // Options for circle/ ellipse drawing { LSTRKEY( "DRAW_UPPER_RIGHT" ), LNUMVAL( U8G_DRAW_UPPER_RIGHT ) }, { LSTRKEY( "DRAW_UPPER_LEFT" ), LNUMVAL( U8G_DRAW_UPPER_LEFT ) }, diff --git a/app/modules/u8g_glue.c b/app/modules/u8g_glue.c new file mode 100644 index 00000000..41fe2b72 --- /dev/null +++ b/app/modules/u8g_glue.c @@ -0,0 +1,347 @@ +/* + Functions for integrating U8glib into the nodemcu platform. +*/ + +#include "lauxlib.h" +#include "platform.h" + +#include "c_stdlib.h" + +#include "u8g.h" +#include "u8g_glue.h" + + +// ------------------------------------------------------------ +// comm functions +// +#define I2C_CMD_MODE 0x000 +#define I2C_DATA_MODE 0x040 + +#define ESP_I2C_ID 0 + + +static uint8_t do_i2c_start(uint8_t id, uint8_t sla) +{ + platform_i2c_send_start( id ); + + // ignore return value -> tolerate missing ACK + platform_i2c_send_address( id, sla, PLATFORM_I2C_DIRECTION_TRANSMITTER ); + + return 1; +} + +static uint8_t u8g_com_esp8266_ssd_start_sequence(struct _lu8g_userdata_t *lu8g) +{ + /* are we requested to set the a0 state? */ + if ( lu8g->u8g.pin_list[U8G_PI_SET_A0] == 0 ) + return 1; + + /* setup bus, might be a repeated start */ + if ( do_i2c_start( ESP_I2C_ID, lu8g->i2c_addr ) == 0 ) + return 0; + if ( lu8g->u8g.pin_list[U8G_PI_A0_STATE] == 0 ) + { + // ignore return value -> tolerate missing ACK + if ( platform_i2c_send_byte( ESP_I2C_ID, I2C_CMD_MODE ) == 0 ) + ; //return 0; + } + else + { + platform_i2c_send_byte( ESP_I2C_ID, I2C_DATA_MODE ); + } + + lu8g->u8g.pin_list[U8G_PI_SET_A0] = 0; + return 1; +} + + +static void lu8g_digital_write( struct _lu8g_userdata_t *lu8g, uint8_t pin_index, uint8_t value ) +{ + uint8_t pin; + + pin = lu8g->u8g.pin_list[pin_index]; + if ( pin != U8G_PIN_NONE ) + platform_gpio_write( pin, value ); +} + +void u8g_Delay(u8g_t *u8g, uint16_t msec) +{ + struct _lu8g_userdata_t *lu8g = (struct _lu8g_userdata_t *)u8g; + const uint16_t chunk = 50; + + if (lu8g->use_delay == 0) + return; + + while (msec > chunk) + { + os_delay_us( chunk*1000 ); + msec -= chunk; + } + if (msec > 0) + os_delay_us( msec*1000 ); +} +void u8g_MicroDelay(void) +{ + os_delay_us( 1 ); +} +void u8g_10MicroDelay(void) +{ + os_delay_us( 10 ); +} + + +uint8_t u8g_com_esp8266_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) +{ + struct _lu8g_userdata_t *lu8g = (struct _lu8g_userdata_t *)u8g; + + switch(msg) + { + case U8G_COM_MSG_STOP: + break; + + case U8G_COM_MSG_INIT: + // we assume that the SPI interface was already initialized + // just care for the /CS and D/C pins + lu8g_digital_write( lu8g, U8G_PI_CS, PLATFORM_GPIO_HIGH ); + platform_gpio_mode( lu8g->u8g.pin_list[U8G_PI_CS], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT ); + platform_gpio_mode( lu8g->u8g.pin_list[U8G_PI_A0], PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT ); + break; + + case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ + lu8g_digital_write( lu8g, U8G_PI_A0, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH ); + break; + + case U8G_COM_MSG_CHIP_SELECT: + if (arg_val == 0) + { + /* disable */ + lu8g_digital_write( lu8g, U8G_PI_CS, PLATFORM_GPIO_HIGH ); + } + else + { + /* enable */ + lu8g_digital_write( lu8g, U8G_PI_CS, PLATFORM_GPIO_LOW ); + } + break; + + case U8G_COM_MSG_RESET: + if ( lu8g->u8g.pin_list[U8G_PI_RESET] != U8G_PIN_NONE ) + lu8g_digital_write( lu8g, U8G_PI_RESET, arg_val == 0 ? PLATFORM_GPIO_LOW : PLATFORM_GPIO_HIGH ); + break; + + case U8G_COM_MSG_WRITE_BYTE: + platform_spi_send( 1, 8, arg_val ); + break; + + case U8G_COM_MSG_WRITE_SEQ: + case U8G_COM_MSG_WRITE_SEQ_P: + { + register uint8_t *ptr = arg_ptr; + while( arg_val > 0 ) + { + platform_spi_send( 1, 8, *ptr++ ); + arg_val--; + } + } + break; + } + return 1; +} + + +uint8_t u8g_com_esp8266_ssd_i2c_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) +{ + struct _lu8g_userdata_t *lu8g = (struct _lu8g_userdata_t *)u8g; + + switch(msg) + { + case U8G_COM_MSG_INIT: + // we assume that the i2c bus was already initialized + //u8g_i2c_init(u8g->pin_list[U8G_PI_I2C_OPTION]); + + break; + + case U8G_COM_MSG_STOP: + break; + + case U8G_COM_MSG_RESET: + /* Currently disabled, but it could be enable. Previous restrictions have been removed */ + /* u8g_com_arduino_digital_write(u8g, U8G_PI_RESET, arg_val); */ + break; + + case U8G_COM_MSG_CHIP_SELECT: + lu8g->u8g.pin_list[U8G_PI_A0_STATE] = 0; + lu8g->u8g.pin_list[U8G_PI_SET_A0] = 1; /* force a0 to set again, also forces start condition */ + if ( arg_val == 0 ) + { + /* disable chip, send stop condition */ + platform_i2c_send_stop( ESP_I2C_ID ); + } + else + { + /* enable, do nothing: any byte writing will trigger the i2c start */ + } + break; + + case U8G_COM_MSG_WRITE_BYTE: + //u8g->pin_list[U8G_PI_SET_A0] = 1; + if ( u8g_com_esp8266_ssd_start_sequence(lu8g) == 0 ) + return platform_i2c_send_stop( ESP_I2C_ID ), 0; + // ignore return value -> tolerate missing ACK + if ( platform_i2c_send_byte( ESP_I2C_ID, arg_val) == 0 ) + ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0; + // platform_i2c_send_stop( ESP_I2C_ID ); + break; + + case U8G_COM_MSG_WRITE_SEQ: + case U8G_COM_MSG_WRITE_SEQ_P: + //u8g->pin_list[U8G_PI_SET_A0] = 1; + if ( u8g_com_esp8266_ssd_start_sequence(lu8g) == 0 ) + return platform_i2c_send_stop( ESP_I2C_ID ), 0; + { + register uint8_t *ptr = arg_ptr; + while( arg_val > 0 ) + { + // ignore return value -> tolerate missing ACK + if ( platform_i2c_send_byte( ESP_I2C_ID, *ptr++) == 0 ) + ; //return platform_i2c_send_stop( ESP_I2C_ID ), 0; + arg_val--; + } + } + // platform_i2c_send_stop( ESP_I2C_ID ); + break; + + case U8G_COM_MSG_ADDRESS: /* define cmd (arg_val = 0) or data mode (arg_val = 1) */ + lu8g->u8g.pin_list[U8G_PI_A0_STATE] = arg_val; + lu8g->u8g.pin_list[U8G_PI_SET_A0] = 1; /* force a0 to set again */ + + break; + } + return 1; +} + + +// *************************************************************************** +// Generic framebuffer device and RLE comm driver +// +uint8_t u8g_dev_gen_fb_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t msg, void *arg) +{ + switch(msg) + { + case U8G_DEV_MSG_PAGE_FIRST: + // tell comm driver to start new framebuffer + u8g_SetChipSelect(u8g, dev, 1); + break; + case U8G_DEV_MSG_PAGE_NEXT: + { + u8g_pb_t *pb = (u8g_pb_t *)(dev->dev_mem); + if ( u8g_pb_WriteBuffer(pb, u8g, dev) == 0 ) + return 0; + } + break; + } + + return u8g_dev_pb8v1_base_fn(u8g, dev, msg, arg); +} + +static int bit_at( uint8_t *buf, int line, int x ) +{ + uint8_t byte = buf[x]; + + return buf[x] & (1 << line) ? 1 : 0; +} + +struct _lu8g_fbrle_item +{ + uint8_t start_x; + uint8_t len; +}; + +struct _lu8g_fbrle_line +{ + uint8_t num_valid; + struct _lu8g_fbrle_item items[0]; +}; + +uint8_t u8g_com_esp8266_fbrle_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr) +{ + struct _lu8g_userdata_t *lud = (struct _lu8g_userdata_t *)u8g; + + switch(msg) + { + case U8G_COM_MSG_INIT: + // allocate memory -> done + // init buffer + break; + + case U8G_COM_MSG_CHIP_SELECT: + if (arg_val == 1) { + // new frame starts + if (lud->cb_ref != LUA_NOREF) { + // fire callback with nil argument + lua_State *L = lua_getstate(); + lua_rawgeti( L, LUA_REGISTRYINDEX, lud->cb_ref ); + lua_pushnil( L ); + lua_call( L, 1, 0 ); + } + } + break; + + case U8G_COM_MSG_WRITE_SEQ: + case U8G_COM_MSG_WRITE_SEQ_P: + { + uint8_t xwidth = u8g->pin_list[U8G_PI_EN]; + size_t fbrle_line_size = sizeof( struct _lu8g_fbrle_line ) + sizeof( struct _lu8g_fbrle_item ) * (xwidth/2); + int num_lines = arg_val / (xwidth/8); + uint8_t *buf = (uint8_t *)arg_ptr; + + struct _lu8g_fbrle_line *fbrle_line; + if (!(fbrle_line = (struct _lu8g_fbrle_line *)c_malloc( fbrle_line_size ))) { + break; + } + + for (int line = 0; line < num_lines; line++) { + int start_run = -1; + fbrle_line->num_valid = 0; + + for (int x = 0; x < xwidth; x++) { + if (bit_at( buf, line, x ) == 0) { + if (start_run >= 0) { + // inside run, end it and enter result + fbrle_line->items[fbrle_line->num_valid].start_x = start_run; + fbrle_line->items[fbrle_line->num_valid++].len = x - start_run; + //NODE_ERR( " line: %d x: %d len: %d\n", line, start_run, x - start_run ); + start_run = -1; + } + } else { + if (start_run < 0) { + // outside run, start it + start_run = x; + } + } + + if (fbrle_line->num_valid >= xwidth/2) break; + } + + // active run? + if (start_run >= 0 && fbrle_line->num_valid < xwidth/2) { + fbrle_line->items[fbrle_line->num_valid].start_x = start_run; + fbrle_line->items[fbrle_line->num_valid++].len = xwidth - start_run; + } + + // line done, trigger callback + if (lud->cb_ref != LUA_NOREF) { + lua_State *L = lua_getstate(); + + lua_rawgeti( L, LUA_REGISTRYINDEX, lud->cb_ref ); + lua_pushlstring( L, (const char *)fbrle_line, fbrle_line_size ); + lua_call( L, 1, 0 ); + } + } + + c_free( fbrle_line ); + } + break; + } + return 1; +} diff --git a/app/modules/u8g_glue.h b/app/modules/u8g_glue.h new file mode 100644 index 00000000..e8057651 --- /dev/null +++ b/app/modules/u8g_glue.h @@ -0,0 +1,22 @@ + +#ifndef _U8G_GLUE_H_ +#define _U8G_GLUE_H_ + +#include "u8g.h" + +struct _lu8g_userdata_t +{ + u8g_t u8g; + uint8_t i2c_addr; + uint8_t use_delay; + int cb_ref; +}; +typedef struct _lu8g_userdata_t lu8g_userdata_t; + +// shorthand macro for the u8g structure inside the userdata +#define LU8G (&(lud->u8g)) + +uint8_t u8g_com_esp8266_fbrle_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr); +uint8_t u8g_dev_gen_fb_fn(u8g_t *u8g, u8g_dev_t *dev, uint8_t msg, void *arg); + +#endif diff --git a/app/u8glib/u8g.h b/app/u8glib/u8g.h index 6103ffdc..c1d6e02b 100644 --- a/app/u8glib/u8g.h +++ b/app/u8glib/u8g.h @@ -1178,9 +1178,6 @@ struct _u8g_t u8g_state_cb state_cb; u8g_box_t current_page; /* current box of the visible page */ - - uint8_t i2c_addr; - uint8_t use_delay; }; #define u8g_GetFontAscent(u8g) ((u8g)->font_ref_ascent) diff --git a/docs/en/modules/u8g.md b/docs/en/modules/u8g.md index 7b477638..8d022f31 100644 --- a/docs/en/modules/u8g.md +++ b/docs/en/modules/u8g.md @@ -204,6 +204,30 @@ disp = u8g.ssd1306_128x64_hw_spi(cs, dc, res) #### See also [I²C Display Drivers](#i2c-display-drivers) +## u8g.fb_rle +Initialize a virtual display that provides run-length encoded framebuffer contents to a Lua callback. + +The callback function can be used to process the framebuffer line by line. It's called with either `nil` as parameter to indicate the start of a new frame or with a string containing a line of the framebuffer with run-length encoding. First byte in the string specifies how many pairs of (x, len) follow, while each pair defines the start (leftmost x-coordinate) and length of a sequence of lit pixels. All other pixels in the line are dark. + +```lua +n = struct.unpack("B", rle_line) +print(n.." pairs") +for i = 0,n-1 do + print(string.format(" x: %d len: %d", struct.unpack("BB", rle_line, 1+1 + i*2))) +end +``` + +#### Syntax +`u8g.fb_rle(cb_fn, width, height)` + +#### Parameters +- `cb_fn([rle_line])` callback function. `rle_line` is a string containing a run-length encoded framebuffer line, or `nil` to indicate start of frame. +- `width` of display. Must be a multiple of 8, less than or equal to 248. +- `height` of display. Must be a multiple of 8, less than or equal to 248. + +#### Returns +u8g display object + ___ ## Constants