u8g2: add rfb support

This commit is contained in:
devsaurus 2017-07-25 21:16:51 +02:00
parent b3eaba86e4
commit 64f8ac2876
5 changed files with 369 additions and 75 deletions

View File

@ -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

View File

@ -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;

View File

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

View File

@ -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;
}

View File

@ -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.