diff --git a/Clenites/LCD.c b/Clenites/LCD.c new file mode 100644 index 0000000..5770549 --- /dev/null +++ b/Clenites/LCD.c @@ -0,0 +1,134 @@ +/* Demonstration of Séléné LCD module. + * Display all native characters + * + * 08/09/2024 - First version + */ + +/* Basic modules needed by almost all applications */ +#include /* Modules : the only part hardly linked */ +#include /* Selene's core functionalities */ +#include /* Logging : not really mandatory but very useful in most of the cases */ + +#include + + /* Here start 'standard' C code */ +#include /* dlerror(), ... */ +#include /* exit(), ... */ +#include + +int main( int ac, char ** av){ + uint16_t verfound; + + /* Load core functionalities */ + struct SeleneCore *SeleneCore = (struct SeleneCore *)loadModule("SeleneCore", SELENECORE_VERSION, &verfound); +#ifdef DEBUG + printf("*D* SeleneCore %s : version %u\n", SeleneCore ? "found":"not found", verfound); +#endif + if(!SeleneCore){ /* Needs to do checks manually as SeleneCore is ... not loaded */ + printf("*F* : can't load SeleneCore "); + if(verfound) + printf("(%u instead of expected %u)\n", verfound, SELENECORE_VERSION); + else { + char *err = dlerror(); + if(!err) + puts(" : missing InitModule() or newer SelModule expected"); + else + printf("(%s)\n", dlerror()); + } + + exit(EXIT_FAILURE); + } + + struct SelLog *SelLog = (struct SelLog *)SeleneCore->loadModule("SelLog", SELLOG_VERSION, &verfound, 'F'); + + /* We still need to do it manually as SeleneCore->loadModule() still can't logging */ + if(!SelLog){ + printf("*F* : can't load SelLog "); + if(verfound) + printf("(%u instead of expected %u)\n", verfound, SELLOG_VERSION); + else { + char *err = dlerror(); + if(!err) + puts(" : missing InitModule() or outdated dependency found"); + else + printf("(%s)\n", dlerror()); + } + + exit(EXIT_FAILURE); + } + + /* *** + * Here your application code + * ***/ + + /* Load SelLCD module ... using SeleneCore's facilities as logging is enabled */ + struct SelLCD *SelLCD = (struct SelLCD *)SeleneCore->loadModule("SelLCD", SELLCD_VERSION, &verfound, 'F'); + if(!SelLCD) + exit(EXIT_FAILURE); + + /* LCD own code starting here */ + uint16_t nbus = 2; /* BananaPI bus by default */ + uint8_t addr = 0x27; /* screen address */ + bool verbose = false; + + int c; + while((c = getopt(ac, av, "hvb:a:")) != EOF) switch(c){ + case 'b': + nbus = atoi(optarg); + break; + case 'a': + addr = atoi(optarg); + break; + case 'v': + verbose = true; + break; + case 'h': + default : + printf("Known options :\n" + "\t-b : i2c bus number (default 2)\n" + "\t-a ! device's address (default 0x27)\n" + "\t-v : be verbose\n" + ); + exit( c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE); + } + + if(verbose) + printf("Targeting slave 0x%02x on /dev/i2c-%d\n", addr, nbus); + + struct LCDscreen lcd; + + if(!SelLCD->Init(&lcd, nbus, addr, true, false)) /* 16x02 screen */ + exit(EXIT_FAILURE); + + SelLCD->Clear(&lcd); + SelLCD->WriteString(&lcd, "Hello"); + SelLCD->Backlight(&lcd, true); /* Backlight on */ + SelLCD->DisplayCtl(&lcd, true, true, true); /* On, cursor blinking */ + getchar(); + + SelLCD->DisplayCtl(&lcd, true, false, true); /* Only the block blinking */ + for(uint16_t i = 0x00; i < 0x100; i += 0x10){ + char t[6]; + sprintf(t, "0x%02x", i); + + /* Write the characters' group */ + SelLCD->Clear(&lcd); + SelLCD->WriteString(&lcd, t); + + SelLCD->SetCursor(&lcd, 0,1); /* 2nd lines */ + t[1] = 0; + + for(uint8_t j = 0x00; j < 0x10; j++){ + *t = i+j; + SelLCD->WriteString(&lcd, t); + } + + SelLCD->SetCursor(&lcd, 15,0); /* Display the cursor on the top right */ + getchar(); + } + + /* The end */ + SelLCD->Backlight(&lcd, false); + SelLCD->Shutdown(&lcd); +} + diff --git a/SelenitesLCD/AllChar.sel b/SelenitesLCD/AllChar.sel new file mode 100644 index 0000000..787f816 --- /dev/null +++ b/SelenitesLCD/AllChar.sel @@ -0,0 +1,33 @@ +#!./Selene +--- Demonstration of subsurface usage + +Selene.Use("SelLCD") +Selene.LetsGo() -- ensure late building dependencies + + -- Init the screen handle + -- Suitable for my BananaPI +lcd = SelLCD.Init(2, 0x27, true, false) + +lcd:Clear() +lcd:WriteString("Hello") +lcd:Backlight(true) +lcd:DisplayCtl(true, true, true) -- On, cursor blinking +io.stdin:read'*l' -- wait for enter + +lcd:DisplayCtl(true, false, true) -- Only the block blinking + +for i = 0x00, 0xf0, 0x10 do + lcd:Clear() + lcd:WriteString( string.format('0x%02x', i) ) + + lcd:SetCursor(0,1) + for j = 0x00, 0x0f do + lcd:WriteString( string.char(i+j) ) + end + + lcd:SetCursor(15,0) + io.stdin:read'*l' -- wait for enter +end + +lcd:Backlight(false) +lcd:Shutdown() diff --git a/remake.sh b/remake.sh index 76227de..b8ac700 100644 --- a/remake.sh +++ b/remake.sh @@ -11,7 +11,10 @@ USE_CURSES=1 # Build OLED screen plug-in -# USE_OLED=1 +USE_OLED=1 + +# Build LCD1602 plug-in +USE_LCD=1 # Build DRMCairo plug-in USE_DRMCAIRO=1 @@ -37,9 +40,9 @@ DRMC_WITH_FB=1 # where to install plugins # production -PLUGIN_DIR=/usr/local/lib +# PLUGIN_DIR=/usr/local/lib # for development -# PLUGIN_DIR=$( pwd )/lib +PLUGIN_DIR=$( pwd )/lib if [ ${PLUGIN_DIR+x} ] then @@ -194,6 +197,24 @@ else fi +echo +echo "LCD plug-in" +echo "-----------" + +if [ ${USE_LCD+x} ]; then + echo "LCD used" + USE_LCD="-DUSE_LCD" + + cd src/SelPlugins/LCD/ + LFMakeMaker -v +f=Makefile --opts="-I../../include $CFLAGS $DEBUG $MCHECK $LUA $USE_LCD " *.c -so=../../../lib/Selene/SelLCD.so > Makefile + cd ../../.. + + echo -e '\t$(MAKE) -C src/SelPlugins/LCD' >> Makefile +else + echo "LCD not used" +fi + + echo echo "DRMCairo plugin" echo "---------------" @@ -502,7 +523,7 @@ rm -f make.sh for f in *.c do - echo "cc -I../src/include/ \$( pkg-config --cflags lua$VERLUA ) $CFLAGS $DEBUG $MCHECK $MCHECK_LIB $USE_PLUGDIR -L../lib -l:libSelene.so.2 -lpaho-mqtt3c -lm -ldl -Wl,--export-dynamic -lpthread $f -o $( basename $f .c )" >> make.sh + echo "cc -I../src/include/ $CFLAGS $DEBUG $MCHECK $MCHECK_LIB $USE_PLUGDIR -L../lib -l:libSelene.so.2 -lpaho-mqtt3c -lm -ldl -Wl,--export-dynamic -lpthread $f -o $( basename $f .c )" >> make.sh done cd ../.. diff --git a/src/SelPlugins/LCD/SelLCD.c b/src/SelPlugins/LCD/SelLCD.c new file mode 100644 index 0000000..5473e72 --- /dev/null +++ b/src/SelPlugins/LCD/SelLCD.c @@ -0,0 +1,445 @@ +/*** + * Display messages on an LCD textual screen (like 1602 one) + * + * Based on https://fr.wikipedia.org/wiki/HD44780 + * and inspired by BitBank https://github.com/bitbank2/LCD1602 + +@classmod SelOLED + + * 06/09/2024 LF : First version + */ + +#include +#include +#include + +#include +#include +#include +#include + +static struct SelLCD selLCD; + +static struct SeleneCore *selCore; +static struct SelLog *selLog; +static struct SelLua *selLua; + +/* I2C expender bits usage : + * + * 0 : RS (0 = command, 1 = data) + * 1 : Read (0) / Write (1) + * 2 : Enable + * 3 : Backlight + */ +#define RS_CMD 0 +#define RS_DATA 0x01 +#define RW_R 0 +#define RW_W 0x02 +#define ENABLE 0x04 +#define BACKLIGHT 0x08 + +static struct LCDscreen *checkSelLCD(lua_State *L){ + void *r = selLua->testudata(L, 1, "SelLCD"); + luaL_argcheck(L, r != NULL, 1, "'SelLCD' expected"); + + return (struct LCDscreen *)r; +} + +static void lcdc_SendQuarter(struct LCDscreen *lcd, uint8_t b){ +/* Send the provided quarter */ + + write(lcd->bus, &b, 1); /* Present the data on the gpio */ + usleep(selLCD.clock_pulse); + + b |= ENABLE; /* Rise 'E' */ + write(lcd->bus, &b, 1); + usleep(selLCD.clock_pulse); + + b &= ~(ENABLE); /* Lower 'E' */ + write(lcd->bus, &b, 1); + usleep(selLCD.clock_process); +} + +static void lcdc_SendCmd(struct LCDscreen *lcd, uint8_t dt){ +/** + * @brief Send a command to the LCD controller + * + * @function SendCmd + * @tparam uint8_t command to send + */ + uint8_t t = RS_CMD; /* It's a Command */ + t |= lcd->backlight ? BACKLIGHT : 0; /* Is the backlight on ? */ + + /* Most significant quarter first */ + t |= dt & 0xf0; + selLCD.SendQuarter(lcd, t); + + t &= 0x0f; /* Keep only control bits */ + t |= dt << 4; /* send less significant quarter */ + selLCD.SendQuarter(lcd, t); +} + +static void lcdc_SendData(struct LCDscreen *lcd, uint8_t dt){ +/** + * @brief Send a data to the LCD controller + * + * @function SendData + * @tparam uint8_t data to send + */ + uint8_t t = RS_DATA; /* It's a Command */ + t |= lcd->backlight ? BACKLIGHT : 0; /* Is the backlight on ? */ + + /* Most significant quarter first */ + t |= dt & 0xf0; + selLCD.SendQuarter(lcd, t); + + t &= 0x0f; /* Keep only control bits */ + t |= dt << 4; /* send less significant quarter */ + selLCD.SendQuarter(lcd, t); +} + +static bool lcdc_Init(struct LCDscreen *lcd, uint16_t bus_number, uint8_t address, bool twolines, bool y11){ +/** + * @brief Initialize connection to the screen + * + * @function Init + * @param screen point to the screen handle + * @tparam uint16_t I2C bus number + * @tparam uint8_t Screen I2C address + * @tparam boolean true if the screen has 2 lines + * @tparam boolean true if the screen is 11 pixel hight + * @treturn boolean false if we faced a technical error + */ + char sbus[16]; + sprintf(sbus, "/dev/i2c-%u", bus_number); + + if((lcd->bus = open(sbus, O_RDWR)) < 0) + return false; + + if(ioctl(lcd->bus, I2C_SLAVE, address) < 0) + return false; + + /* Initializing + * SET + 4 bits mode + * We're sending the upper quarter first so 0x02 is really 0x20. + */ + selLCD.SendCmd(lcd, 0x02); + + /* Now sending the full configuration */ + selLCD.SendCmd(lcd, 0x02 | (twolines ? 0x08 : 0) | (y11 ? 0x04 : 0)); + + return true; +} + +static int lcdl_Init(lua_State *L){ + uint16_t nbus = luaL_checkinteger(L, 1); + uint8_t addr = luaL_checkinteger(L, 2); + bool twolines = lua_toboolean(L, 3); + bool y11 = lua_toboolean(L, 4); + + struct LCDscreen *lcd = (struct LCDscreen *)lua_newuserdata(L, sizeof(struct LCDscreen)); + assert(lcd); + + luaL_getmetatable(L, "SelLCD"); + lua_setmetatable(L, -2); + + selLCD.Init(lcd, nbus, addr, twolines, y11); + + return 1; +} + +static void lcdc_Shutdown(struct LCDscreen *lcd){ +/** + * @brief Turn off the screen + * + * @function Shutdown + * @param screen point to the screen handle + */ + selLCD.DisplayCtl(lcd, false, false, false); + close(lcd->bus); + lcd->bus = -1; +} + +static int lcdl_Shutdown(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + + selLCD.Shutdown(lcd); + + return 0; +} + +static void lcdc_Backlight(struct LCDscreen *lcd, bool bl){ +/** + * @brief Turn backlight on or off (for next command) + * + * @function backlight + * @param screen point to the screen handle + * @tparam boolean status of the backlight + */ + lcd->backlight = bl; +} + +static int lcdl_Backlight(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + bool bl = lua_toboolean(L, 2); + + selLCD.Backlight(lcd, bl); + + return 0; +} + +static void lcdc_DisplayCtl(struct LCDscreen *lcd, bool screen, bool cursor, bool blink){ +/** + * @brief Display control + * + * @function DisplayCtl + * @param screen point to the screen handle + * @tparam boolean is the screen on ? + * @tparam boolean is the _ cursor on ? + * @tparam boolean is the block cursor on ? + */ + uint8_t t = 0x08; /* Display control */ + t |= screen ? 0x04:0x00; + t |= cursor ? 0x02:0x00; + t |= blink ? 0x01:0x00; + + selLCD.SendCmd(lcd, t); +} + +static int lcdl_DisplayCtl(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + bool screen = lua_toboolean(L, 2); + bool cursor = lua_toboolean(L, 3); + bool blink = lua_toboolean(L, 4); + + selLCD.DisplayCtl(lcd, screen, cursor, blink); + + return 0; +} + +static void lcdc_EntryCtl(struct LCDscreen *lcd, bool inc, bool shift){ +/** + * @brief Entry control + * + * @function EntryCtl + * + * @param screen point to the screen handle + * @tparam boolean increment the cursor when a character is sent + * @tparam boolean shift the screen if the cursor leaves it + */ + uint8_t t = 0x04; /* Entry control */ + t |= inc ? 0x02 : 0x00; + t |= shift ? 0x01 : 0x00; + + selLCD.SendCmd(lcd, t); +} + +static int lcdl_EntryCtl(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + bool inc = lua_toboolean(L, 2); + bool shift = lua_toboolean(L, 3); + + selLCD.EntryCtl(lcd, inc, shift); + + return 0; +} + +static void lcdc_Clear(struct LCDscreen *lcd){ +/** + * @brief Clear the screen + * + * @function Clear + * + * @param screen point to the screen handle + */ + selLCD.SendCmd(lcd, 0x01); +} + +static int lcdl_Clear(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + + selLCD.Clear(lcd); + + return 0; +} + +static void lcdc_Home(struct LCDscreen *lcd){ +/** + * @brief Places cursor at up-left position + * + * @function Home + * + * @param screen point to the screen handle + */ + selLCD.SendCmd(lcd, 0x02); +} + +static int lcdl_Home(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + + selLCD.Home(lcd); + + return 0; +} + +static void lcdc_SetDDRAM(struct LCDscreen *lcd, uint8_t pos){ +/** + * @brief Set display ram pointer + * + * @function SetDDRAM + * + * @param screen point to the screen handle + * @tparam uint8_t position (<80 otherwise reset to 0) + */ + if(pos > 79) + pos = 0; + + selLCD.SendCmd(lcd, 0x80 | pos); +} + +static int lcdl_SetDDRAM(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + uint8_t pos = lua_toboolean(L, 2); + + selLCD.SetDDRAM(lcd, pos); + + return 0; +} + +static void lcdc_SetCursor(struct LCDscreen *lcd, uint8_t x, uint8_t y){ +/** + * @brief set the cursor at x,y position + * + * @function SetCursor + * + * @param screen point to the screen handle + * @param uint8_t x position + * @param uint8_t y position + * + * Notez-bien : there is no boundary check. Up to the developer to know what + * it is doing. + */ + selLCD.SetDDRAM(lcd, y*0x40 + x); +} + +static int lcdl_SetCursor(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + uint8_t x = lua_toboolean(L, 2); + uint8_t y = lua_toboolean(L, 3); + + selLCD.SetCursor(lcd, x,y); + + return 0; +} + +static void lcdc_WriteString(struct LCDscreen *lcd, const char *txt){ +/** + * @brief Write a characters string to the screen. + * + * @function WriteString + * + * @param screen point to the screen handle + * @param string to be displayed + * + * Notez-bien : there is no limits, up to the programmer to know + * what it's doing. + */ + for(;*txt; txt++) + selLCD.SendData(lcd, *txt); +} + +static int lcdl_WriteString(lua_State *L){ + struct LCDscreen *lcd = checkSelLCD(L); + const char *s = luaL_checkstring(L, 2); + + selLCD.WriteString(lcd, s); + + return 0; +} + +static const struct luaL_Reg LCDM[] = { + {"Shutdown", lcdl_Shutdown}, + {"Backlight", lcdl_Backlight}, + {"DisplayCtl", lcdl_DisplayCtl}, + {"EntryCtl", lcdl_EntryCtl}, + {"Clear", lcdl_Clear}, + {"Home", lcdl_Home}, + {"SetDDRAM", lcdl_SetDDRAM}, + {"SetCursor", lcdl_SetCursor}, + {"WriteString", lcdl_WriteString}, + {NULL, NULL} /* End of definition */ +}; + +static const struct luaL_Reg LCDLib[] = { + {"Init", lcdl_Init}, + {"Attach", lcdl_Init}, + {NULL, NULL} /* End of definition */ +}; + +static void registerSelLCD(lua_State *L){ + selLua->libCreateOrAddFuncs(L, "SelLCD", LCDLib); + selLua->objFuncs(L, "SelLCD", LCDM); +} + +/* *** + * This function MUST exist and is called when the module is loaded. + * Its goal is to initialize module's configuration and register the module. + * If needed, it can also do some internal initialisation work for the module. + * ***/ +bool InitModule( void ){ + /* Core modules */ + selCore = (struct SeleneCore *)findModuleByName("SeleneCore", SELENECORE_VERSION); + if(!selCore) + return false; + + selLog = (struct SelLog *)selCore->findModuleByName("SelLog", SELLOG_VERSION,'F'); + if(!selLog) + return false; + + /* Other mandatory modules */ + selLua = (struct SelLua *)selCore->findModuleByName("SelLua", SELLUA_VERSION,0); + + /* optional modules */ + + /* Initialise module's glue */ + if(!initModule((struct SelModule *)&selLCD, "SelLCD", SELLCD_VERSION, LIBSELENE_VERSION)) + return false; + + registerModule((struct SelModule *)&selLCD); + + if(selLua){ /* Only if Lua is used */ + registerSelLCD(NULL); + selLua->AddStartupFunc(registerSelLCD); + } +#ifdef DEBUG + else + selLog->Log('D', "SelLua not loaded"); +#endif + + /* Default timings */ + + selLCD.clock_pulse = 500; + selLCD.clock_process = 4100; + + /* This low level can be overwritten for example to use 1-Wire instead + * of I2C + */ + selLCD.SendQuarter = lcdc_SendQuarter; + + /* Callbacks */ + + selLCD.Init = lcdc_Init; + selLCD.Shutdown = lcdc_Shutdown; + selLCD.SendCmd = lcdc_SendCmd; + selLCD.SendData = lcdc_SendData; + selLCD.Backlight = lcdc_Backlight; + selLCD.DisplayCtl = lcdc_DisplayCtl; + selLCD.EntryCtl = lcdc_EntryCtl; + selLCD.Clear = lcdc_Clear; + selLCD.Home = lcdc_Home; + selLCD.SetDDRAM = lcdc_SetDDRAM; + selLCD.SetCursor = lcdc_SetCursor; + selLCD.WriteString = lcdc_WriteString; + + return true; +} diff --git a/src/include/Selene/SelPlug-in/SelLCD.h b/src/include/Selene/SelPlug-in/SelLCD.h new file mode 100644 index 0000000..3b51e31 --- /dev/null +++ b/src/include/Selene/SelPlug-in/SelLCD.h @@ -0,0 +1,50 @@ +/* SelLCD.h + * + * Display messages on an LCD textual screen (like 1602 one) + * + * Have a look and respect Selene Licence. + */ + +#ifndef SELLCD_VERSION + +#include +#include + +#include + +/* *********** + * /!\ CAUTION : BUMP THIS VERSION AT EVERY CHANGE INSIDE GLUE STRUCTURE + * ***********/ +#define SELLCD_VERSION 1 + +struct LCDscreen { + int bus; /* I2C bus file descriptor */ + bool backlight; /* is backlight enabled */ +}; + +struct SelLCD { + struct SelModule module; + + useconds_t clock_pulse; /* 'E' clock */ + useconds_t clock_process; /* time to process */ + + /* Call backs */ + void (*SendQuarter)(struct LCDscreen *, uint8_t); + + bool (*Init)(struct LCDscreen *, uint16_t bus_number, uint8_t address, bool twolines, bool y11); + void (*Shutdown)(struct LCDscreen *); + + void (*SendCmd)(struct LCDscreen *, uint8_t); + void (*SendData)(struct LCDscreen *, uint8_t); + + void (*Backlight)(struct LCDscreen *, bool); + void (*DisplayCtl)(struct LCDscreen *, bool screen, bool cursor, bool blink); + void (*EntryCtl)(struct LCDscreen *, bool inc, bool shift); + void (*Clear)(struct LCDscreen *); + void (*Home)(struct LCDscreen *); + void (*SetDDRAM)(struct LCDscreen *, uint8_t); + void (*SetCursor)(struct LCDscreen *, uint8_t, uint8_t); + void (*WriteString)(struct LCDscreen *, const char *); +}; + +#endif diff --git a/src/include/Selene/SeleneVersion.h b/src/include/Selene/SeleneVersion.h index 20ea3c1..40df354 100644 --- a/src/include/Selene/SeleneVersion.h +++ b/src/include/Selene/SeleneVersion.h @@ -55,7 +55,9 @@ * 09/03/2020 LF : v6.11.00 - Use StartupFunc to declare objects in slave threads * * 06/02/2024 LF : V7.00.00 - Switch to weak linked modules + * + * 08/09/2024 LF : V8.00.00 - Add LCD module */ /* Version exposed to application (including Lua side) */ -#define SELENE_VERSION 7.0010 /* major, minor, sub */ +#define SELENE_VERSION 8.0000 /* major, minor, sub */