LUA移植到STM32F4,移植REPL,通过RTT Viewer交互
本文移植了LUA虚拟机到STM32F407之中,成功在RTT View上展示了REPL界面,文章末尾做了一些简单的LUA测试,比如print,运算等
概述
站内移植LUA多数是使用C函数调用LUA,并没有移植REPL交互端口
本文将REPL也移植进去,做了简单的适配
LUA源码使用标准C库函数,如fgets,fwrite等,在嵌入式环境中要使用fgets,fwrite等C库函数,需要做的工作就是重定向。
本文重定向了STDIN和STDOUT数据流到J-Link RTT Viewer,可以通过 RTT Viewer向LUA虚拟机进行交互。
环境
MCU:STM32F407, 192KB RAM, 1MFLASH。建议运行平台至少有256KBRAM,256KB的FLASH,否则加载lib的时候会爆内存或者FLASH。
KEIL:527
编译器:AC6
准备工程
从https://www.lua.org/download.html网站下载LUA源码,在KEIL中新建一个LUA文件夹,将所有文件添加到里面。luac.c不要添加进去,这个文件是用来编译lua脚本的,我们不需要。
准备SEGGER RTT打印相关文件
新建一个空文件syscall.c,后面的C库系统调用函数我们会写入到此文件中。
处理好所有文件的头文件,包含路径问题。
对接C库系统调用函数
以下函数的编写参考了如下资源
- <rt_sys.h>文件定义了函数头文件,里面还有函数功能和返回值的描述
- Arm® C and C++ Libraries and Floating-Point Support User Guide:一些函数描述
主要实现了
- 关闭半主机模式
- _sys_open:打开文件,返回STDIN,STDOUT,STDERR的文件描述符,,普通文件流不处理
- _sys_write:写文件,向STDOUT写入的数据流流向SEGGER RTT,普通文件流不处理
- _sys_read:读文件,SEGGER RTT读取的数据流向STDIN,普通文件流不处理、
- _sys_istty:判断文件描述符是否为终端
- time:事件相关的函数,对接的是hal_gettick,返回系统上电运行了多少ms
#include <rt_sys.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "SEGGER_RTT.h"
#include "main.h"
//关闭半主机模式
/********************************************************************************/
#if defined(__clang__)
__asm(".global __use_no_semihosting\n\t");
#elif defined(__CC_ARM)
#pragma import(__use_no_semihosting)
#endif
#define STDOUT 0x00000001
#define STDIN 0x00000002
#define STDERR 0x00000003
const char __stdin_name[] = "STDIN";
const char __stdout_name[] = "STDOUT";
const char __stderr_name[] = "STDERR";
FILEHANDLE _sys_open(const char *pcFile, int openmode)
{
if(0 == strncmp(pcFile, __stdin_name, strlen(__stdin_name))) return STDIN;
if(0 == strncmp(pcFile, __stdout_name, strlen(__stdout_name))) return STDOUT;
if(0 == strncmp(pcFile, __stderr_name, strlen(__stderr_name))) return STDERR;
//pcFile :文件路径
//openmode :文件打开模式
//返回值 :文件描述符
return 0;
}
int _sys_close(FILEHANDLE fh)
{
return 0;
}
int _sys_write(FILEHANDLE fh, const unsigned char * buf, unsigned len, int mode)
{
if (fh == STDOUT){
SEGGER_RTT_Write(0, (const char*)buf, len);
return 0;
}
return 0;
}
int _sys_read(FILEHANDLE fh, unsigned char * buf, unsigned len, int mode)
{
//读取一行数据,回车结束。读取完毕之后在字符串末尾添加结束符
static int count_p = 0;
if (fh == STDIN){
count_p = 0;
buf[count_p] = SEGGER_RTT_WaitKey();
while(buf[count_p] != '\n')
{
count_p++;
buf[count_p] = SEGGER_RTT_WaitKey();
}
buf[count_p + 1] = '\0';
return 0;
}
return 0; //EOF
}
void _ttywrch(int ch)
{
fputc(ch, stdout); // stdout
fflush(stdout);
}
int _sys_istty(FILEHANDLE fh)
{
return (fh==STDIN || fh==STDOUT || fh==STDERR);
}
int _sys_seek(FILEHANDLE fh, long pos)
{
return 0;
}
int _sys_ensure(FILEHANDLE fh)
{
return 0;
}
long _sys_flen(FILEHANDLE fh)
{
return 0;
}
int _sys_tmpnam(char * name, int sig, unsigned maxlen)
{
return 0;
}
void _sys_exit(int returncode) /* never returns */
{
}
char *_sys_command_string(char * cmd, int len)
{
return 0;
}
int remove(const char *filename)
{
return 0;
}
int system(const char *string)
{
return 0;
}
int rename(const char *old, const char *new)
{
return 0;
}
time_t time(time_t *timer)
{
return HAL_GetTick();
}
clock_t clock(void)
{
return 0;
}
修改LUA源码
LUA源码中操作行数据使用fgets和puts,这个函数我的对接始终有问题,这里更改为fread和fwrite函数
在luaconf.h末尾添加如下代码
/* =================================================================== */
/*
** Local configuration. You can use this space to add your redefinitions
** without modifying the main part of the file.
*/
#define LUA_MAXINPUT 128
#define lua_readline(L,b,p) ( fread(b, 1, LUA_MAXINPUT, stdin) != 0)
#define lua_initreadline(L) ( (void)L )
#define lua_saveline(L,line) { (void)L; (void)line; }
#define lua_freeline(L,b) { (void)L; (void)b; }
#define lua_writestring(s,l) fwrite(s, 1, l, stdout)
#define lua_writeline() fwrite("\n", 1, 1, stdout)
#define lua_writestringerror(...) printf(__VA_ARGS__)
lua.c中已经有一个main函数,我们需要将这个main函数改名为lua_main,在keil中的main函数调用lua_main来启动LUA
int lua_main (int argc, char **argv) { //修改函数名
int status, result;
lua_State *L = luaL_newstate(); /* create state */
if (L == NULL) {
l_message(argv[0], "cannot create state: not enough memory");
return EXIT_FAILURE;
}
lua_gc(L, LUA_GCSTOP); /* stop GC while building state */
lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */
lua_pushinteger(L, argc); /* 1st argument */
lua_pushlightuserdata(L, argv); /* 2nd argument */
status = lua_pcall(L, 2, 1, 0); /* do the call */
result = lua_toboolean(L, -1); /* get result */
report(L, status);
lua_close(L);
return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
}
lua .h中增加lua_main的函数声明
int lua_main (int argc, char **argv);
启动LUA虚拟机
main函数中,增加如下代码
这里我们要给lua_main 传递两个假参数,如下
int fake_argc = 1;
char *fake_argv = NULL;
lua_main (fake_argc, &fake_argv);
启动
启动前要先配置好RTT VIEWER,复位启动即可.
测试指令如下
< _VERSION
< print("hello world")
< print("abc".."666")
< print("system run "..os.time().." msec")
错误指令和提示如下
< print("system run "..XXX.time().." msec")
自建C库
流程
自建C语言库,实现延时和LED控制
在linit.c中,lua定义了顶层lib库, 并且在后面的函数中加载了所有lib。
库中第一个参数是库名称,第二个参数是对应的库打开函数。
static const luaL_Reg loadedlibs[] = {
{LUA_GNAME, luaopen_base},
{LUA_LOADLIBNAME, luaopen_package},
{LUA_COLIBNAME, luaopen_coroutine},
{LUA_TABLIBNAME, luaopen_table},
{LUA_IOLIBNAME, luaopen_io},
{LUA_OSLIBNAME, luaopen_os},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_UTF8LIBNAME, luaopen_utf8},
{LUA_DBLIBNAME, luaopen_debug},
{NULL, NULL}
};
我们打开一个函数看一下,可以看到它使用luaL_newlib创建了子lib pk_funcs, pk_funcs中定义了lua中的函数名称和函数声明。
LUAMOD_API int luaopen_package (lua_State *L) {
createclibstable(L);
luaL_newlib(L, pk_funcs); /* create 'package' table */
createsearcherstable(L);
···
static const luaL_Reg pk_funcs[] = {
{"loadlib", ll_loadlib},
{"searchpath", ll_searchpath},
/* placeholders */
{"preload", NULL},
{"cpath", NULL},
{"path", NULL},
{"searchers", NULL},
{"loaded", NULL},
{NULL, NULL}
};
我们需要做的就是先在顶层lib中增加我们自己的lib名称”HAL“,增加自己的lib打开函数luaopen_hal,之后再创建一个子lib HAL_lib,在子lib中定义函数名称和声明即可。
//增加自定义顶层lib
extern int luaopen_hal(lua_State *L);
/*
** these libs are loaded by lua.c and are readily available to any Lua
** program
*/
static const luaL_Reg loadedlibs[] = {
{LUA_GNAME, luaopen_base},
{LUA_LOADLIBNAME, luaopen_package},
{LUA_COLIBNAME, luaopen_coroutine},
{LUA_TABLIBNAME, luaopen_table},
{LUA_IOLIBNAME, luaopen_io},
{LUA_OSLIBNAME, luaopen_os},
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_UTF8LIBNAME, luaopen_utf8},
{LUA_DBLIBNAME, luaopen_debug},
{"HAL", luaopen_hal},
{NULL, NULL}
};
//增加自己的lib打开函数luaopen_hal,之后再创建一个子lib HAL_lib,在子lib中定义函数名称和声明
static int LUA_HAL_GPIO_Write(lua_State *L)
{
int r = (luaL_checkinteger(L, 1) == 0);
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, r ? GPIO_PIN_SET:GPIO_PIN_RESET);
return 0;
}
static int LUA_HAL_delay(lua_State *L)
{
HAL_Delay(luaL_checkinteger(L, 1));
return 0;
}
static const struct luaL_Reg HAL_lib[] = {
{"delay", LUA_HAL_delay},
{"led", LUA_HAL_GPIO_Write},
{NULL, NULL}
};
int luaopen_hal(lua_State *L)
{
luaL_newlib(L, HAL_lib);
return 1;
}
测试
HAL
HAL.delay
HAL.led
while(1) do HAL.delay(1000) HAL.led(1) HAL.delay(1000) HAL.led(0) end
最后一个测试中,LED应该开始闪烁
TODO&其他
工程参考:https://gitee.com/nwwhhh/stm32f407
TODO:对接文件函数,调用本地文件
更多推荐
所有评论(0)