为STM32添加Lua脚本🤗 软件:keil 所需库环境:Malloc, Fatfs
声明,这里使用的是正点原子家的源码,感谢开源!
上述库环境是为了实现Lua能够从外置存储介质读取文件所准备的,如果没有需求可以不用。
主控:STM32F401RET6
运行频率:84MHz
ROM:512KB
RAM:96KB
关于Lua Lua 语言是由巴西里约热内卢天主教大学 ([Pontifical Catholic University of Rio de janeiro ) 里的一个研究小组与 1993年开发的一种轻量小巧 的脚本(弱语言)语言 ,用标准 C 语言编写,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
作为一种扩展语言,Lua 没有“主”程序的概念:它嵌入在宿主客户端中运行,称为嵌入程序或简称为宿主。(通常这个宿主是单机lua程序) 宿主程序可以调用函数执行一段Lua代码,可以读写Lua变量,可以注册Lua代码调用的C函数 。通过使用 C 函数,可以增强 Lua 以应对广泛的不同领域,从而创建共享语法框架的定制编程语言。
简单来说,Lua是一种轻量级的基于C编写的运行高效的脚本语言(解释性语言like:Python、shell、Matlab等。
在单片机环境下移植Lua,因为Lua和C的超级无敌兼容性,相当于你同时拥有了两种语言加持(C和Lua),你可以直接用Lua内部提供的几个简单的API,使得C内运行Lua脚本,特别方便,避免了重复烧录的麻烦。
Lua解释器的移植,最小占用ROM: 70KB,占用RAM: 7.5KB(很小很小)
简单的lua程序跟C程序效率比是1:100。而lua运算量越大。与C程序效率差距就越小。
准备工作
在github上拉取Lua-v5.3的版本库: lua/lua at v5.3
建立一个基于主控STM32F401RET6的Keil文件(已经有的话就不需要,直接哐哐移植)
开始移植 移植Lua库文件
将github上拉取的 lua-5.3 文件夹移入工程文件夹。
打开Keil,点击魔术棒,将..\lua-5.3 相对路径添加到环境变量。
点击三个盒子,创建一个文件夹命名为Lua,将..\lua-5.3 相对路径下的所有.c文件(除了Lua.c和Luac.c以外,如果有的话,没有就不管)添加到其中。
更改 loslib.c 文件下部分内容:
将 os_exit(lua_State * L) 函数中 if(L) exit(status) 注释,并添加 status=status 语句。
添加 time(time_t *time) 和 system(const char * string) 。
将魔术棒里的 Use MicroLIB 模式关闭(不打勾!)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static int os_exit (lua_State *L) { int status; if (lua_isboolean(L, 1 )) status = (lua_toboolean(L, 1 ) ? EXIT_SUCCESS : EXIT_FAILURE); else status = (int )luaL_optinteger(L, 1 , EXIT_SUCCESS); if (lua_toboolean(L, 2 )) lua_close(L); status=status; return 0 ; } time_t time (time_t *time) { return 0 ; } int system (const char * string ) { return 0 ; }
最后可以去linit.c 注释一些用不到的Lua库,当然,不注释也不会影响太大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static const luaL_Reg loadedlibs[] = { {"_G" , luaopen_base}, {LUA_LOADLIBNAME, luaopen_package}, {LUA_COLIBNAME, luaopen_coroutine}, {LUA_TABLIBNAME, luaopen_table}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, {LUA_UTF8LIBNAME, luaopen_utf8},#if defined(LUA_COMPAT_BITLIB) {LUA_BITLIBNAME, luaopen_bit32},#endif {NULL , NULL } };
添加retarget.c
放哪随意,我基于正点原子的工程放在了system的文件夹中
引进这个库的目的是为了实现Lua从外置的存储介质中获取文件内容,我们需要用Fatfs的API去实现Lua所需的fopen、fclose、fread等函数(如果没有这个需求,可以跳过此步骤)
声明本次移植使用的Fatfs,来自正点原子的Fatfs实验源码,好用爱用,给个好评。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 #include <ctype.h> #include <rt_sys.h> #include <stdint.h> #include <time.h> #include "usart.h" #define FATFS_EN 1 #if FATFS_EN #include <ff.h> #include <stdlib.h> #endif #pragma import(__use_no_semihosting_swi) #define STDIN 0 #define STDOUT 1 #define STDERR 2 #define IS_STD(fh) ((fh) >= 0 && (fh) <= 2) const char __stdin_name[] = ":kkl" ;const char __stdout_name[] = "kkl" ;const char __stderr_name[] = "kkl" ; FILEHANDLE _sys_open(const char *name, int openmode) {#if FATFS_EN BYTE mode; FIL *fp; FRESULT fr;#endif if (name == __stdin_name) return STDIN; else if (name == __stdout_name) { uart_init(115200 ); return STDOUT; } else if (name == __stderr_name) return STDERR;#if FATFS_EN if (sizeof (FILEHANDLE) < sizeof (void *)) { USART1_SendBuf("sizeof(FILEHANDLE) should be no less than sizeof(void *)!\n" ); return -1 ; } fp = ff_memalloc(sizeof (FIL)); if (fp == NULL ) return -1 ; if (openmode & OPEN_W) { mode = FA_CREATE_ALWAYS | FA_WRITE; if (openmode & OPEN_PLUS) mode |= FA_READ; } else if (openmode & OPEN_A) { mode = FA_OPEN_APPEND | FA_WRITE; if (openmode & OPEN_PLUS) mode |= FA_READ; } else { mode = FA_READ; if (openmode & OPEN_PLUS) mode |= FA_WRITE; } fr = f_open(fp, name, mode); if (fr == FR_OK) return (uintptr_t )fp; ff_memfree(fp); #endif return -1 ; }int _sys_close(FILEHANDLE fh) {#if FATFS_EN FRESULT fr;#endif if (IS_STD(fh)) { if (fh == STDOUT) return 0 ; }#if FATFS_EN fr = f_close((FIL *)fh); if (fr == FR_OK) { ff_memfree((void *)fh); return 0 ; }#endif return -1 ; }int _sys_write(FILEHANDLE fh, const unsigned char *buf, unsigned len, int mode) {#if FATFS_EN FRESULT fr; UINT bw;#endif if (fh == STDIN) return -1 ; if (fh == STDOUT || fh == STDERR) { USART1_SendBuf((unsigned char *)buf); return 0 ; }#if FATFS_EN fr = f_write((FIL *)fh, buf, len, &bw); if (fr == FR_OK) return len - bw;#endif return -1 ; }int _sys_read(FILEHANDLE fh, unsigned char *buf, unsigned len, int mode) { int i = 0 ;#if FATFS_EN FRESULT fr; UINT br;#endif if (fh == STDIN) { while (i < len) { i--; } return len - i; } else if (fh == STDOUT || fh == STDERR) return -1 ;#if FATFS_EN fr = f_read((FIL *)fh, buf, len, &br); if (fr == FR_OK) return len - br;#endif return -1 ; }int _sys_istty(FILEHANDLE fh) { return IS_STD(fh); }int _sys_seek(FILEHANDLE fh, long pos) {#if FATFS_EN FRESULT fr; if (!IS_STD(fh)) { fr = f_lseek((FIL *)fh, pos); if (fr == FR_OK) return 0 ; }#endif return -1 ; }int _sys_ensure(FILEHANDLE fh) { return 0 ; }long _sys_flen(FILEHANDLE fh) {#if FATFS_EN if (!IS_STD(fh)) return f_size((FIL *)fh);#endif return -1 ; }int _sys_tmpnam(char *name, int fileno, unsigned maxlength) { return 0 ; }void _ttywrch(int ch) { USART1_SendChar(ch); }int remove (const char *filename) { return 0 ; }int rename (const char *oldname, const char *newname) { return 0 ; }char *_sys_command_string(char *cmd, int len) { return NULL ; }clock_t clock (void ) { return 0 ; }
排查错误 因为我用的是正点原子的USART的代码,它们家是没有勾Use MicroLIB 模式的,一些配置会重复,所以我们要自己改一些东西,不然编译没法通过。
注释FILE __stdout;
因为移植了Lua解释器,所以我们的堆栈分配 应该相应的分配更大一些。
打开启动文件(startup_stm32f40_41xxx.s ),更改以下部分内容:
修改栈:Stack_Size EQU 0x00001000 //4k //不行就改成0x00004000
修改堆:Heap_Size EQU 0x00002c00 //11k //不行就改成0x00004000
添加 #define LUA_32BITS
1 2 3 4 5 6 7 8 #define LUA_32BITS
OK!这时候再编译应该不会有报错,是可以通过的!
使用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 static int lua_led_on (lua_State * L) { LED0 = 0 ; return 1 ; }static int lua_print_hello (lua_State * L) { printf ("Hello this is lua!\r\n" ); return 1 ; }static const struct luaL_Reg mylib [] = { {"led_on" ,lua_led_on}, {"print_hello" ,lua_print_hello}, {NULL , NULL } };const char LUA_SCRIPT_GLOBAL_ON[]="\ led_on()\ print_hello()\ " ;static int do_file_script (void ) { lua_State *L; L = luaL_newstate(); luaopen_base(L); luaL_setfuncs(L, mylib, 0 ); luaL_dostring(L, LUA_SCRIPT_GLOBAL_ON); return 0 ; }
Lua还有更多的可玩性,上面的仅仅只是其中一种 - luaL_dostring !
下面再介绍一种 - luaL_dofile !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static int lua_led_on (lua_State * L) { LED0 = 0 ; return 1 ; }static int lua_print_hello (lua_State * L) { printf ("Hello this is lua!\r\n" ); return 1 ; }static const struct luaL_Reg mylib [] = { {"led_on" ,lua_led_on}, {"print_hello" ,lua_print_hello}, {NULL , NULL } };static int do_file_script (void ) { uint8_t res; lua_State *L; L = luaL_newstate(); luaopen_base(L); luaL_openlibs(L); luaL_setfuncs(L, mylib, 0 ); res = luaL_dofile(L, "1:/test.lua" ); if (res) { printf ("err\r\n" ); } else { printf ("ok\r\n" ); } return 0 ; }
Lua调用C函数的注意事项: 对于可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即typedef int (lua_CFunction)(lua_State* L); 接收一个参数Lua_State*,即Lua的状态,返回值表示压入栈中的结果个数。
如果想要注册有传入参数且有返回值的函数,可以参考以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static int Add (lua_State *L) { int count; int x,y,res; x = lua_tointeger(L,1 ); y = lua_tointeger(L,2 ); res = x + y; lua_pushnumber(L,res); return 1 ; } local res res = Add(5 ,6 ) print("Result = " ,res)
写在后面 写完啦…
Author: @kkl