Lua 5.3.5 Mods
Sounds great, @Dog-One! Can you give a little background on what changes you had to make to get it to build? I'd like to get it working under Arduino and PlatformIO.
Let me see here...
There are two ways to go about driving the interpreter: The first method is to use the Lua "main" function (rename it of course), send it "-E" "-i" and let it run the doREPL loop. If you go this route, you'll probably want to stub out usb_serial_gets and a usb_serial_puts functions, then remap:
Code:
// int usb_serial_puts(const char *str);
// char *usb_serial_gets(char *str, int n);
// Override this to redirect interactive session to hardware I/O
#if 0
#define lua_readline(L,b,p) \
((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \
fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */
#endif
extern int usb_serial_puts(const char *);
extern char *usb_serial_gets(char *, int);
#define lua_readline(L,b,p) \
((void)L, usb_serial_puts(p), usb_serial_gets(b, LUA_MAXINPUT) != NULL)
Or...
My second approach was to just use the Lua dostring() and stub out:
Code:
// Setup stdout console primitive (enable direct, i.e. printf, etc)
// Code added to send a carriage return following any newline found
// in buffer--needed for raw-mode I/O.
int _write(int file, char *ptr, int len) {
(void) file;
const char nl = '\r';
int idx = 0;
while (idx < len) {
usb_serial_write(&(ptr[idx]), 1);
if (ptr[idx] == '\n') {
usb_serial_write(&nl, 1);
}
idx++;
}
usb_serial_flush_output();
return len;
}
Then you can build your own entry point like:
Code:
// Lua entry point to run ramfile script
int lua_main(void) {
int idx;
lua_State *L = luaL_newstate(); // create state
// Register custom C functions here
lua_register(L, "pinset", pinset); // ...
lua_register(L, "setRTC", setRTC);
lua_register(L, "wait", wait);
lua_register(L, "sendSPI", sendSPI);
lua_pushboolean(L, 1);
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
luaL_openlibs(L); /* open standard libraries */
print_version();
dostring (L, ramfile, "ramfile.lua");
lua_close(L);
return EXIT_SUCCESS;
To get everything to build, the only major changes I made were to the functions in the oslib; everything else I left active:
Code:
static const luaL_Reg syslib[] = {
{"clock", os_clock},
{"date", os_date},
{"difftime", os_difftime},
// {"execute", os_execute},
{"exit", os_exit},
// {"getenv", os_getenv},
// {"remove", os_remove},
// {"rename", os_rename},
{"setlocale", os_setlocale},
{"time", os_time},
{"tmpname", os_tmpname},
{NULL, NULL}
};
Still toying with the os_exit() as this basically hard crashes the controller. I left it in as sort of an emergency stop button if I need it.
Keep in mind Lau still thinks it has a full-blown OS living underneath it, so some functions are going to fail--I have no file system for example.
There are some #define directives for LINUX, POSIX, etc. I didn't use any of those and just let the preprocessor pick its way through the header files. Best I can tell the defaults are fairly sound for bare-metal hardware. I did declare #define LUA_32BITS, but could probably work around that if I really needed to. For what I'm doing, 32-bits is plenty accurate for integers and floats. I also removed the luac compiler generation as this really isn't needed if you want a true run-time interpreter.
For those not really in-the-know, Lua uses the stack for everything and it processes scripts as chunks. This later attribute is why I moved away from a fully interactive interpreter. In interactive mode, each line is a chunk instead of the whole group of lines. Think of chunks as fully independent constructs of processing--you break them apart and the pieces become independent and no longer work together as a whole.
What I've done so far is quite usable on the Teensy 4.0, but it could be better. For example, I'm really not using
ChibiOS to it's full advantage--Lua coroutines don't do SMP (by design of course, but this could be implemented in a future version) and
I could never get the ChibiOS heap management to work at all, so I disabled it and used the compiler malloc, realloc and free functions instead. My implementation of a ramfile is kind of lame, but it works. I'd rather have a file system that can lay on top of RAM and flash and be able to put scripts in either place on the fly. Not sure how much code would be necessary to make this all work. I'll keep investigating and see what I can come up with.
Overall, I spent a lot more time getting
SCE ported to this platform than I did
Lua. SCE is written in C but it's all in Spanish and I google translated tons of comments to get to a point where I understood how it worked. I then stripped everything out of it not needed to work with
PuTTY. Before embarking on this task, I did look at
nano and other screen editors first. They all seem tightly hooked into an OS and were still
bloated beyond belief. SCE works nicely now. It gives you a very functional screen editor that you can plop in a Lua script, ESC, ESC, to save and run. And with this editor, now you have a means to work with an entire Lua chunk which turns out to be a big plus (bigger than I thought at first).
The build environment I took from
VisualTeensy which generates some json files used by
Visual Studio Code and a Makefile. Once I had that, I moved things around more to my liking, replaced the GNU ARM compiler with the
latest version and broke all dependencies from the
Arduino IDE and
Teensyduino. My template no longer needs VisualTeensy either. You're all free to beat me up for this, but I wanted something self-contained focused on Teensy 4.0 development. Five years from now, I can unzip this file and recompile exactly what I have today and it will still work. This is a lesson I had to learn the hard way before and won't make the same mistake again. Visual Studio Code is about the only real dependency I'm locked into and I'm pretty sure if I had to move to
emacs on a Linux machine I could make things work again in a few days. Basically I have an environment where I feel comfortable buying a couple dozen Teensy 4.0s and know I can build and load code to them without any outside forces derailing my attempt.
About using Lua as a language to drive a micro-controller...
I'm feeling pretty good about my decision. I see a lot of Python out there, some BASIC, various precompiled shells and scripts, but I like this Lua stuff. It's new to me, so maybe when the new-car smell fades I won't feel the same, but for now, I think it will work great and do what I want, they way I want. For those that haven't dipped their toe in the Brazilian moonlight, you may want to try it. And when you tie into a micro-controller such as the Teensy 4.0, it might just occur to you (as it did me), this is powerful stuff. I'm certain I could whip out a script to control a HVAC system with no trouble at all. And I could do it now without anything but a serial connection. It's not everyday you run into a situation where less really is more.
I have to say a word about the Teensy 4.0...
The folks at
PJRC really out-did themselves with this one. I have used Cypress PSoC devices for years due to their ability to program logic into the hardware. Then just recently, Cypress went off in a different direction with their
PSoC 6 devices and IoT. They pretty much gave up on the integrated progammable logic and started focusing on the power of the ARM Cortex processor. All well in good for some, not so much for others, me being one of the others. Okay fine Cypress, you want to change gears and leave your customers flapping in the breeze, do it. So I went on the hunt. I tried the
LinkIt device, the
NanoPi and a couple of other ARM based devices. Nothing really fit what I was looking for. Then one bright day I went browsing the
SparkFun website and came across the Teensy line of micro-controllers. Looky what I found, this shiney new Teensy 4.0. It's got RAM, Flash, speed, it's definitely Teensy and relatively inexpensive too. I think we have only scratched the surface of what this device has to offer. Maybe it's just me, but trying to shoehorn this device into the Arduino box is... Well... One way to do it. Could there be another...?