瀏覽代碼

u8g2: add rfb support

devsaurus 6 年之前
父節點
當前提交
64f8ac2876

+ 149 - 72
components/modules/u8g2.c

@@ -569,96 +569,173 @@ static const LUA_REG_TYPE lu8g2_display_map[] = {
 };
 
 
+uint8_t u8x8_d_overlay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
+
+typedef void (*display_setup_fn_t)(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb);
 
 // ***************************************************************************
 // Device constructors
 //
-//
 // I2C based devices will use this function template to implement the Lua binding.
+//
+static int ldisplay_i2c( lua_State *L, display_setup_fn_t setup_fn )
+{
+  int stack = 0;
+
+  int id = -1;
+  int i2c_addr = -1;
+  int rfb_cb_ref = LUA_NOREF;
+
+  if (lua_type( L, ++stack) == LUA_TNUMBER) {
+    /* hardware display connected */
+    id = luaL_checkint( L, stack );
+    i2c_addr = luaL_checkint( L, ++stack );
+    luaL_argcheck( L, i2c_addr >= 0 && i2c_addr <= 0x7f, stack, "invalid i2c address" );
+  } else
+    stack--;
+  if (lua_isfunction( L, ++stack )) {
+    lua_pushvalue( L, stack );
+    rfb_cb_ref = luaL_ref( L, LUA_REGISTRYINDEX );
+  }
+  if (id < 0 && rfb_cb_ref == LUA_NOREF)
+    return luaL_error( L, "wrong args" );
+
+  u8g2_ud_t *ud = (u8g2_ud_t *)lua_newuserdata( L, sizeof( u8g2_ud_t ) );
+  u8g2_nodemcu_t *ext_u8g2 = &(ud->u8g2);
+  ud->font_ref = LUA_NOREF;
+  ud->host_ref = LUA_NOREF;
+  /* the i2c driver id is forwarded in the hal member */
+  ext_u8g2->hal = id >= 0 ? (void *)id : NULL;
+
+  u8g2_t *u8g2 = (u8g2_t *)ext_u8g2;
+  u8x8_t *u8x8 = (u8x8_t *)u8g2;
+  setup_fn( u8g2, U8G2_R0, u8x8_byte_nodemcu_i2c, u8x8_gpio_and_delay_nodemcu );
+
+  /* prepare overlay data */
+  if (rfb_cb_ref != LUA_NOREF) {
+    ext_u8g2->overlay.template_display_cb = u8x8->display_cb;
+    ext_u8g2->overlay.hardware_display_cb = NULL;
+    ext_u8g2->overlay.rfb_cb_ref = LUA_NOREF;
+    u8x8->display_cb = u8x8_d_overlay;
+  }
+  if (id >= 0) {
+    /* hardware device specific initialization */
+    u8x8_SetI2CAddress( u8x8, i2c_addr );
+    ext_u8g2->overlay.hardware_display_cb = ext_u8g2->overlay.template_display_cb;
+  }
+
+  u8g2_InitDisplay( (u8g2_t *)u8g2 );
+  u8g2_ClearDisplay( (u8g2_t *)u8g2 );
+  u8g2_SetPowerSave( (u8g2_t *)u8g2, 0 );
+
+  if (rfb_cb_ref != LUA_NOREF) {
+    /* finally enable rfb display driver */
+    ext_u8g2->overlay.rfb_cb_ref = rfb_cb_ref;
+  }
+
+  /* set its metatable */
+  luaL_getmetatable(L, "u8g2.display");
+  lua_setmetatable(L, -2);
+
+  return 1;
+}
+//
+// SPI based devices will use this function template to implement the Lua binding.
+//
+static int ldisplay_spi( lua_State *L, display_setup_fn_t setup_fn )
+{
+  int stack = 0;
+
+  lspi_host_t *host = NULL;
+  int host_ref = LUA_NOREF;
+  int cs = -1;
+  int dc = -1;
+  int res = -1;
+  int rfb_cb_ref = LUA_NOREF;
+
+  if (lua_type( L, ++stack) == LUA_TUSERDATA) {
+    host = (lspi_host_t *)luaL_checkudata( L, stack, "spi.master" );
+    /* reference host object to avoid automatic gc */
+    lua_pushvalue( L, stack );
+    host_ref = luaL_ref( L, LUA_REGISTRYINDEX );
+
+    cs = luaL_checkint( L, ++stack );
+    dc = luaL_checkint( L, ++stack );
+    res = luaL_optint( L, ++stack, -1 );
+  } else
+    stack--;
+  if (lua_isfunction( L, ++stack )) {
+    lua_pushvalue( L, stack );
+    rfb_cb_ref = luaL_ref( L, LUA_REGISTRYINDEX );
+  }
+  if (!host && rfb_cb_ref == LUA_NOREF)
+    return luaL_error( L, "wrong args" );
+
+  u8g2_ud_t *ud = (u8g2_ud_t *)lua_newuserdata( L, sizeof( u8g2_ud_t ) );
+  u8g2_nodemcu_t *ext_u8g2 = &(ud->u8g2);
+  ud->font_ref = LUA_NOREF;
+  ud->host_ref = host_ref;
+  /* the spi host id is forwarded in the hal member */
+  ext_u8g2->hal = host ? (void *)(host->host) : NULL;
+
+  u8g2_t *u8g2 = (u8g2_t *)ext_u8g2;
+  u8x8_t *u8x8 = (u8x8_t *)u8g2;
+  setup_fn( u8g2, U8G2_R0, u8x8_byte_nodemcu_spi, u8x8_gpio_and_delay_nodemcu );
+
+  /* prepare overlay data */
+  if (rfb_cb_ref != LUA_NOREF) {
+    ext_u8g2->overlay.template_display_cb = u8x8->display_cb;
+    ext_u8g2->overlay.hardware_display_cb = NULL;
+    ext_u8g2->overlay.rfb_cb_ref = LUA_NOREF;
+    u8x8->display_cb = u8x8_d_overlay;
+  }
+  if (host) {
+    /* hardware specific device initialization */
+    u8x8_SetPin( u8x8, U8X8_PIN_CS, cs );
+    u8x8_SetPin( u8x8, U8X8_PIN_DC, dc );
+    if (res >= 0)
+      u8x8_SetPin( u8x8, U8X8_PIN_RESET, res );
+    ext_u8g2->overlay.hardware_display_cb = ext_u8g2->overlay.template_display_cb;
+  }
+
+  u8g2_InitDisplay( (u8g2_t *)u8g2 );
+  u8g2_ClearDisplay( (u8g2_t *)u8g2 );
+  u8g2_SetPowerSave( (u8g2_t *)u8g2, 0 );
+
+  if (rfb_cb_ref != LUA_NOREF) {
+    /* finally enable rfb display driver */
+    ext_u8g2->overlay.rfb_cb_ref = rfb_cb_ref;
+  }
+
+  /* set its metatable */
+  luaL_getmetatable(L, "u8g2.display");
+  lua_setmetatable(L, -2);
+
+  return 1;
+}
+//
+//
 #undef U8G2_DISPLAY_TABLE_ENTRY
-#define U8G2_DISPLAY_TABLE_ENTRY(function, binding)                     \
-  static int l ## binding( lua_State *L )                               \
-  {                                                                     \
-    int stack = 0;                                                      \
-                                                                        \
-    int id = luaL_checkint( L, ++stack );                 \
-                                                                        \
-    int i2c_addr = luaL_checkint( L, ++stack );                         \
-    luaL_argcheck( L, i2c_addr >= 0 && i2c_addr <= 0x7f, stack, "invalid i2c address" ); \
-                                                                        \
-    u8g2_ud_t *ud = (u8g2_ud_t *)lua_newuserdata( L, sizeof( u8g2_ud_t ) ); \
-    u8g2_nodemcu_t *u8g2 = &(ud->u8g2);                                 \
-    ud->font_ref = LUA_NOREF;                                           \
-    ud->host_ref = LUA_NOREF;                                           \
-    /* the i2c driver id is forwarded in the hal member */              \
-    u8g2->hal = (void *)id;                                             \
-                                                                        \
-    function( (u8g2_t *)u8g2, U8G2_R0, u8x8_byte_nodemcu_i2c, u8x8_gpio_and_delay_nodemcu ); \
-    u8x8_SetI2CAddress( (u8x8_t *)u8g2, i2c_addr );                     \
-                                                                        \
-    u8g2_InitDisplay( (u8g2_t *)u8g2 );                                 \
-    u8g2_ClearDisplay( (u8g2_t *)u8g2 );                                \
-    u8g2_SetPowerSave( (u8g2_t *)u8g2, 0 );                             \
-                                                                        \
-    /* set its metatable */                                             \
-    luaL_getmetatable(L, "u8g2.display");                               \
-    lua_setmetatable(L, -2);                                            \
-                                                                        \
-    return 1;                                                           \
+#define U8G2_DISPLAY_TABLE_ENTRY(function, binding) \
+  static int l ## binding( lua_State *L )           \
+  {                                                 \
+    return ldisplay_i2c( L, function );             \
   }
 //
 // Unroll the display table and insert binding functions for I2C based displays.
 U8G2_DISPLAY_TABLE_I2C
 //
 //
-//
-
-
-// SPI based devices will use this function template to implement the Lua binding.
 #undef U8G2_DISPLAY_TABLE_ENTRY
-#define U8G2_DISPLAY_TABLE_ENTRY(function, binding)                     \
-  static int l ## binding( lua_State *L )                               \
-  {                                                                     \
-    int stack = 0;                                                      \
-                                                                        \
-    lspi_host_t *host = (lspi_host_t *)luaL_checkudata( L, ++stack, "spi.master" ); \
-    /* reference host object to avoid automatic gc */                   \
-    lua_pushvalue( L, stack );                                          \
-    int host_ref = luaL_ref( L, LUA_REGISTRYINDEX );                    \
-                                                                        \
-    int cs = luaL_checkint( L, ++stack );                               \
-    int dc = luaL_checkint( L, ++stack );                               \
-    int res = luaL_optint( L, ++stack, -1 );                            \
-                                                                        \
-    u8g2_ud_t *ud = (u8g2_ud_t *)lua_newuserdata( L, sizeof( u8g2_ud_t ) ); \
-    u8g2_nodemcu_t *u8g2 = &(ud->u8g2);                                 \
-    ud->font_ref = LUA_NOREF;                                           \
-    ud->host_ref = host_ref;                                            \
-    /* the spi host id is forwarded in the hal member */                \
-    u8g2->hal = (void *)(host->host);                                   \
-                                                                        \
-    function( (u8g2_t *)u8g2, U8G2_R0, u8x8_byte_nodemcu_spi, u8x8_gpio_and_delay_nodemcu ); \
-    u8x8_SetPin( (u8x8_t *)u8g2, U8X8_PIN_CS, cs );                     \
-    u8x8_SetPin( (u8x8_t *)u8g2, U8X8_PIN_DC, dc );                     \
-    if (res >= 0)                                                       \
-      u8x8_SetPin( (u8x8_t *)u8g2, U8X8_PIN_RESET, res );               \
-                                                                        \
-    u8g2_InitDisplay( (u8g2_t *)u8g2 );                                 \
-    u8g2_ClearDisplay( (u8g2_t *)u8g2 );                                \
-    u8g2_SetPowerSave( (u8g2_t *)u8g2, 0 );                             \
-                                                                        \
-    /* set its metatable */                                             \
-    luaL_getmetatable(L, "u8g2.display");                               \
-    lua_setmetatable(L, -2);                                            \
-                                                                        \
-    return 1;                                                           \
+#define U8G2_DISPLAY_TABLE_ENTRY(function, binding) \
+  static int l ## binding( lua_State *L )           \
+  {                                                 \
+    return ldisplay_spi( L, function );             \
   }
 //
 // Unroll the display table and insert binding functions for SPI based displays.
 U8G2_DISPLAY_TABLE_SPI
 //
-//
-//
 
 
 #undef U8G2_FONT_TABLE_ENTRY

+ 7 - 0
components/platform/include/u8x8_nodemcu_hal.h

@@ -9,6 +9,13 @@
 typedef struct {
   u8g2_t u8g2;
   void *hal;
+
+  // elements for the overlay display driver
+  struct {
+    u8x8_msg_cb hardware_display_cb, template_display_cb;
+    int rfb_cb_ref;
+    uint8_t fb_update_ongoing;
+  } overlay;
 } u8g2_nodemcu_t;
 
 

+ 1 - 1
components/u8g2/component.mk

@@ -1,3 +1,3 @@
-COMPONENT_SRCDIRS:=u8g2/src/clib
+COMPONENT_SRCDIRS:=u8g2/src/clib .
 COMPONENT_ADD_INCLUDEDIRS:=u8g2/src/clib
 CPPFLAGS+=-DU8X8_USE_PINS

+ 183 - 0
components/u8g2/u8x8_d_fbrle.c

@@ -0,0 +1,183 @@
+
+#include "u8x8.h"
+#include "lauxlib.h"
+
+#include "u8x8_nodemcu_hal.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+static const u8x8_display_info_t u8x8_fbrle_display_info =
+{
+  /* chip_enable_level = */ 0,
+  /* chip_disable_level = */ 1,
+  
+  /* post_chip_enable_wait_ns = */ 0,
+  /* pre_chip_disable_wait_ns = */ 0,
+  /* reset_pulse_width_ms = */ 0,
+  /* post_reset_wait_ms = */ 0,
+  /* sda_setup_time_ns = */ 0,
+  /* sck_pulse_width_ns = */ 0,
+  /* sck_clock_hz = */ 0,
+  /* spi_mode = */ 0,
+  /* i2c_bus_clock_100kHz = */ 4,
+  /* data_setup_time_ns = */ 0,
+  /* write_pulse_width_ns = */ 0,
+  /* tile_width = */ 16,
+  /* tile_hight = */ 8,
+  /* default_x_offset = */ 0,
+  /* flipmode_x_offset = */ 0,
+  /* pixel_width = */ 128,
+  /* pixel_height = */ 64
+};
+
+static int bit_at( uint8_t *buf, int line, int x )
+{
+  return buf[x] & (1 << line) ? 1 : 0;
+}
+
+struct fbrle_item
+{
+  uint8_t start_x;
+  uint8_t len;
+};
+
+struct fbrle_line
+{
+  uint8_t num_valid;
+  struct fbrle_item items[0];
+};
+
+static uint8_t u8x8_d_fbrle(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
+{
+  u8g2_nodemcu_t *ext_u8g2 = (u8g2_nodemcu_t *)u8x8;
+
+  switch(msg)
+  {
+  case U8X8_MSG_DISPLAY_SETUP_MEMORY:
+    // forward to template display driver
+    return ext_u8g2->overlay.template_display_cb(u8x8, msg, arg_int, arg_ptr);
+
+  case U8X8_MSG_DISPLAY_INIT:
+    //u8x8_d_helper_display_init(u8x8);
+    ext_u8g2->overlay.fb_update_ongoing = 0;
+    break;
+
+  case U8X8_MSG_DISPLAY_SET_POWER_SAVE:
+  case U8X8_MSG_DISPLAY_SET_FLIP_MODE:
+    break;
+
+#ifdef U8X8_WITH_SET_CONTRAST
+  case U8X8_MSG_DISPLAY_SET_CONTRAST:
+    break;
+#endif
+
+  case U8X8_MSG_DISPLAY_REFRESH:
+    ext_u8g2->overlay.fb_update_ongoing = 0;
+    break;
+    
+  case U8X8_MSG_DISPLAY_DRAW_TILE:
+    if (ext_u8g2->overlay.fb_update_ongoing == 0) {
+      // tell rfb callback that a new framebuffer starts
+      if (ext_u8g2->overlay.rfb_cb_ref != LUA_NOREF) {
+        // fire callback with nil argument
+        lua_State *L = lua_getstate();
+        lua_rawgeti( L, LUA_REGISTRYINDEX, ext_u8g2->overlay.rfb_cb_ref );
+        lua_pushnil( L );
+        lua_call( L, 1, 0 );
+      }
+      // and note ongoing framebuffer update
+      ext_u8g2->overlay.fb_update_ongoing = 1;
+    }
+
+    {
+      // TODO: transport tile_y, needs structural change!
+      uint8_t tile_x = ((u8x8_tile_t *)arg_ptr)->x_pos;
+      tile_x *= 8;
+      tile_x += u8x8->x_offset;
+      uint8_t tile_w = ((u8x8_tile_t *)arg_ptr)->cnt * 8;
+
+      size_t fbrle_line_size = sizeof( struct fbrle_line ) + sizeof( struct fbrle_item ) * (tile_w/2);
+      int num_lines = 8; /*arg_val / (xwidth/8);*/
+      uint8_t *buf = ((u8x8_tile_t *)arg_ptr)->tile_ptr;
+
+      struct fbrle_line *fbrle_line;
+      if (!(fbrle_line = (struct fbrle_line *)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 = tile_x; x < tile_x+tile_w; 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 >= tile_w/2) break;
+        }
+
+        // active run?
+        if (start_run >= 0 && fbrle_line->num_valid < tile_w/2) {
+          fbrle_line->items[fbrle_line->num_valid].start_x = start_run;
+          fbrle_line->items[fbrle_line->num_valid++].len = tile_w - start_run;
+        }
+
+        // line done, trigger callback
+        if (ext_u8g2->overlay.rfb_cb_ref != LUA_NOREF) {
+          lua_State *L = lua_getstate();
+
+          lua_rawgeti( L, LUA_REGISTRYINDEX, ext_u8g2->overlay.rfb_cb_ref );
+          lua_pushlstring( L, (const char *)fbrle_line, fbrle_line_size );
+          lua_call( L, 1, 0 );
+        }
+      }
+
+      free( fbrle_line );
+    }
+    break;
+
+  default:
+    return 0;
+    break;
+  }
+
+  return 1;
+}
+
+uint8_t u8x8_d_overlay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
+{
+  uint8_t res = 1;
+  u8g2_nodemcu_t *ext_u8g2 = (u8g2_nodemcu_t *)u8x8;
+
+  switch(msg)
+  {
+  case U8X8_MSG_DISPLAY_SETUP_MEMORY:
+    // only call for hardware display
+    if (ext_u8g2->overlay.hardware_display_cb)
+      return ext_u8g2->overlay.hardware_display_cb(u8x8, msg, arg_int, arg_ptr);
+    break;
+
+  default:
+    // forward all messages first to hardware display and then to fbrle
+    if (ext_u8g2->overlay.hardware_display_cb)
+      res = ext_u8g2->overlay.hardware_display_cb(u8x8, msg, arg_int, arg_ptr);
+    u8x8_d_fbrle(u8x8, msg, arg_int, arg_ptr);
+    break;
+  }
+
+  return res;
+}

+ 29 - 2
docs/en/modules/u8g2.md

@@ -120,11 +120,12 @@ Initialize a display via I²C.
 - `u8g2.uc1611_i2c_ea_dogxl240()`
 
 #### Syntax
-`u8g2.ssd1306_i2c_128x64_noname(id, address)`
+`u8g2.ssd1306_i2c_128x64_noname(id, address[, cb_fn])`
 
 #### Parameters
 - `id` i2c interface id, see [i2c module](i2c.md)
 - `address` I²C slave address of display (unshifted)
+- `cb_fn` optional callback function, see [Framebuffer callback](#framebuffer-callback)
 
 #### Returns
 u8g2 display object
@@ -184,12 +185,13 @@ Initialize a display via Hardware SPI.
 - `u8g2.uc1701_mini12864()`
 
 #### Syntax
-`u8g2.ssd1306_128x64_noname(cs, dc[, res])`
+`u8g2.ssd1306_128x64_noname(cs, dc[, [res][, cb_fn]])`
 
 #### Parameters
 - `cs` GPIO pin for CS
 - `dc` GPIO pin for DC
 - `res` GPIO pin for RES, none if omitted
+- `cb_fn` optional callback function, see [Framebuffer callback](#framebuffer-callback)
 
 #### Returns
 u8g2 display object
@@ -205,6 +207,31 @@ bus = spi.master(spi.HSPI, {sclk=sclk, mosi=mosi})
 disp = u8g2.ssd1306_128x64_noname(bus, cs, dc, res)
 ```
 
+## Framebuffer callback
+Each display type can be initialized to provide the framebuffer contents in run-length encoded format to a Lua callback. This mode is enabled when a callback function is specified for the setup function. Hardware display and framebuffer callback can be operated in parallel. If the callback function is the only parameter then no signals for a hardware display are generated, leaving a virtual display.
+
+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
+```lua
+u8g2.ssd1306_i2c_128x64_noname(id, address[, cb_fn])
+u8g2.ssd1306_i2c_128x64_noname(cb_fn)
+u8g2.ssd1306_128x64_noname(cs, dc[, [res][, cb_fn]])
+u8g2.ssd1306_128x64_noname(cb_fn)
+```
+
+#### Parameters
+- `id`, `address`, `cs`, `dc`, `res` see above
+- `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.
+
 ## Constants
 Constants for various functions.