Skip to content

Instantly share code, notes, and snippets.

@devsaurus
Last active May 15, 2020 07:29
Show Gist options
  • Save devsaurus/ced9608828430672c4767ea29f2f6a4f to your computer and use it in GitHub Desktop.
Save devsaurus/ced9608828430672c4767ea29f2f6a4f to your computer and use it in GitHub Desktop.
Lua u8g2 integration with ffi
*~
build/
u8g2lib/
c-periphery/
u8g2_header.lua

POC integration of u8g2 library with LuaJIT ffi

Preconditions

Raspbian Stretch

I2C device setup

You need the following line in /boot/config.txt:

dtparam=i2c_arm=on

You need the following line in /etc/modules:

i2c-dev

Remove any other i2c related lines.

Reboot.

Device permissions

Add /etc/udev/rules.d/51-i2c.rules:

KERNEL=="i2c*", GROUP="users", MODE="0660"

Display connection

On Raspbian Stretch the I2C bus 1 (/dev/i2c-1) is available at the follwing pins:

  • SDA: GPIO2 / pin 3
  • SCL: GPIO3 / pin 5

LuaJIT and lua-periphery

sudo apt -y install luajit lua5.1 luarocks
sudo luarocks install lua-periphery

Compilation

Use make download to download u8g2 v2.21.8 and c-periphery v1.1.1 source releases once.

Type make to compile to library build/u8g2.so.

Example usage

See test_u8g2.lua.

#include <stdlib.h>
#include <string.h>
#include "lua_integration.h"
/* constants to transport magic values from #define to Lua */
const uint8_t lu8x8_msg_byte_send = U8X8_MSG_BYTE_SEND;
const uint8_t lu8x8_msg_byte_init = U8X8_MSG_BYTE_INIT;
const uint8_t lu8x8_msg_byte_set_dc = U8X8_MSG_BYTE_SET_DC;
const uint8_t lu8x8_msg_byte_start_transfer = U8X8_MSG_BYTE_START_TRANSFER;
const uint8_t lu8x8_msg_byte_end_transfer = U8X8_MSG_BYTE_END_TRANSFER;
// static variables containing info about the i2c link
// TODO: move to user space in u8x8_t once available
typedef struct {
i2c_t *i2c;
struct {
uint8_t *data;
size_t size, used;
} buffer;
} hal_i2c_t;
uint8_t i2c_byte_cperiphery_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
hal_i2c_t *hal = ((u8g2_nodemcu_t *)u8x8)->hal;
switch (msg) {
case U8X8_MSG_BYTE_SEND:
if (!hal)
return 0;
while (hal->buffer.size - hal->buffer.used < arg_int) {
hal->buffer.size *= 2;
if (!(hal->buffer.data = (uint8_t *)realloc( hal->buffer.data, hal->buffer.size )))
return 0;
}
memcpy( hal->buffer.data + hal->buffer.used, arg_ptr, arg_int );
hal->buffer.used += arg_int;
break;
case U8X8_MSG_BYTE_INIT:
{
// the hal member initially contains the i2c device
i2c_t *i2c = (i2c_t *)hal;
((u8g2_nodemcu_t *)u8x8)->hal = NULL;
// sanity check
if (u8x8->i2c_address == 0xff)
return 0;
if (!(hal = malloc( sizeof( hal_i2c_t ) )))
return 0;
hal->i2c = i2c;
((u8g2_nodemcu_t *)u8x8)->hal = hal;
hal->buffer.size = 0;
}
break;
case U8X8_MSG_BYTE_SET_DC:
break;
case U8X8_MSG_BYTE_START_TRANSFER:
if (!hal)
return 0;
if (!hal->buffer.size) {
hal->buffer.size = 256;
if (!(hal->buffer.data = (uint8_t *)malloc( hal->buffer.size )))
return 0;
}
hal->buffer.used = 0;
break;
case U8X8_MSG_BYTE_END_TRANSFER:
if (!hal)
return 0;
if (hal->buffer.used > 0) {
struct i2c_msg msg;
msg.addr = u8x8->i2c_address >> 1;
msg.flags = I2C_M_IGNORE_NAK;
msg.len = hal->buffer.used;
msg.buf = hal->buffer.data;
i2c_transfer( hal->i2c, &msg, 1 );
hal->buffer.used = 0;
}
break;
default:
return 0;
}
return 1;
}
#include "u8g2.h"
#include "c-periphery/src/i2c.h"
// extend standard u8g2_t struct with info that's needed in the communication callbacks
typedef struct {
u8g2_t u8g2;
void *hal;
} u8g2_nodemcu_t;
/* constants to transport magic values from #define to Lua */
extern const uint8_t lu8x8_msg_byte_send;
extern const uint8_t lu8x8_msg_byte_init;
extern const uint8_t lu8x8_msg_byte_set_dc;
extern const uint8_t lu8x8_msg_byte_start_transfer;
extern const uint8_t lu8x8_msg_byte_end_transfer;
uint8_t i2c_byte_cperiphery_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
.PHONY: all
all: build build/u8g2.so u8g2_header.lua
.PHONY: clean
clean:
cd c-periphery; $(MAKE) clean
rm -rf build u8g2_header.lua *~
build:
mkdir -p build
download:
git clone --depth 1 --single-branch --branch 2.21.8 https://github.com/olikraus/U8g2_Arduino.git u8g2lib
git clone --depth 1 --single-branch --branch v1.1.1 https://github.com/vsergeev/c-periphery.git c-periphery
ODIR = build
OBJODIR = $(ODIR)
SRCDIR = u8g2lib/src/clib
ADDINC = -Iu8g2lib/src/clib
CSRCS ?= $(wildcard $(SRCDIR)/*.c)
CXXSRCS ?= $(wildcard $(SRCDIR)/*.cpp)
ASRCs ?= $(wildcard $(SRCDIR)/*.s)
ASRCS ?= $(wildcard $(SRCDIR)/*.S)
TOBJS := $(CSRCS:%.c=$(OBJODIR)/%.o) \
$(CXXSRCS:%.cpp=$(OBJODIR)/%.o) \
$(ASRCs:%.s=$(OBJODIR)/%.o) \
$(ASRCS:%.S=$(OBJODIR)/%.o)
OBJS := $(subst $(SRCDIR)/,,$(TOBJS))
CC = gcc
CFLAGS += -fPIC -O2
CFLAGS += -std=c99
CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-pointer-to-int-cast $(DEBUG)
CFLAGS+=-DU8X8_USE_PINS
LDFLAGS += -shared
$(OBJODIR)/lua_integration.o: lua_integration.c
@mkdir -p $(OBJODIR);
$(CC) $(CFLAGS) $(ADDINC) -o $@ -c $<
$(OBJODIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(OBJODIR);
$(CC) $(CFLAGS) -o $@ -c $<
c-periphery/periphery.a:
cd c-periphery; $(MAKE)
$(OBJODIR)/u8g2.so: u8g2lib c-periphery/periphery.a $(OBJS) $(OBJODIR)/lua_integration.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(OBJODIR)/lua_integration.o c-periphery/periphery.a
u8g2_header.lua: u8g2lib c-periphery lua_integration.h
echo 'ffi.cdef[[' > $@
cpp -P -DU8X8_USE_PINS $(ADDINC) lua_integration.h >> $@
echo ']]' >> $@
-- I2C address of display
sla = 0x3c
-- select Lua or C callback
use_lua_i2c_byte_cb = false
I2C = require("periphery").I2C
i2c = I2C("/dev/i2c-1")
ffi = require("ffi")
dofile("u8g2_header.lua")
u8g2lib = ffi.load("build/u8g2.so")
if use_lua_i2c_byte_cb then
--
-- i2c byte callback implemented in Lua
--
u8g2_data = ffi.new("u8g2_t")
-- cache constants
U8X8_MSG_BYTE_SEND = u8g2lib.lu8x8_msg_byte_send
U8X8_MSG_BYTE_INIT = u8g2lib.lu8x8_msg_byte_init
U8X8_MSG_BYTE_SET_DC = u8g2lib.lu8x8_msg_byte_set_dc
U8X8_MSG_BYTE_START_TRANSFER = u8g2lib.lu8x8_msg_byte_start_transfer
U8X8_MSG_BYTE_END_TRANSFER = u8g2lib.lu8x8_msg_byte_end_transfer
function i2c_byte_cb(u8x8, msg, arg_int, arg_ptr)
--print(string.format("i2c called - msg: %i, arg_int: %i", msg, arg_int))
local data = ffi.cast("uint8_t *", arg_ptr)
if msg == U8X8_MSG_BYTE_SEND then
for idx = 0, arg_int-1, 1 do
i2c_buffer[#i2c_buffer+1] = data[idx]
end
elseif msg == U8X8_MSG_BYTE_INIT then
i2c_buffer = {}
elseif msg == U8X8_MSG_BYTE_START_TRANSFER then
i2c_buffer = {}
elseif msg == U8X8_MSG_BYTE_END_TRANSFER then
if false then
local str = string.format("sending %d bytes ", #i2c_buffer)
for idx = 1, #i2c_buffer, 1 do
str = string.format("%s 0x%02x", str, i2c_buffer[idx])
end
print(str)
end
i2c_buffer.flags = I2C.I2C_M_IGNORE_NAK
i2c:transfer(sla, {i2c_buffer})
elseif msg == U8X8_MSG_BYTE_SET_DC then
else
return 0
end
return 1
end
u8g2lib.u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_data, u8g2lib.u8g2_cb_r0, i2c_byte_cb, u8g2lib.u8x8_dummy_cb)
else
--
-- i2c byte callback implemented in C
--
u8g2_nodemcu = ffi.new("u8g2_nodemcu_t")
u8g2_nodemcu.hal = ffi.cast("void *", i2c)
u8g2_data = ffi.cast("u8g2_t *", u8g2_nodemcu)
u8g2_data.u8x8.i2c_address = sla
u8g2lib.u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_data, u8g2lib.u8g2_cb_r0, u8g2lib.i2c_byte_cperiphery_cb, u8g2lib.u8x8_dummy_cb)
end
u8g2lib.u8x8_InitDisplay( ffi.cast("u8x8_t *", u8g2_data) )
u8g2lib.u8x8_ClearDisplay( ffi.cast("u8x8_t *", u8g2_data) )
u8g2lib.u8x8_SetPowerSave( ffi.cast("u8x8_t *", u8g2_data), 0 )
u8g2lib.u8g2_SetFont(u8g2_data, u8g2lib.u8g2_font_6x10_tf)
u8g2lib.u8g2_SetFontRefHeightExtendedText(u8g2_data)
u8g2lib.u8g2_SetDrawColor(u8g2_data, 1)
u8g2lib.u8g2_SetFontPosTop(u8g2_data)
u8g2lib.u8g2_SetFontDirection(u8g2_data, 0)
u8g2lib.u8g2_DrawStr(u8g2_data, 0, 0, "drawBox")
u8g2lib.u8g2_DrawBox(u8g2_data, 5,10,20,10)
u8g2lib.u8g2_SendBuffer(u8g2_data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment