目录

点击这里查看所有博文

2.3、硬件功能模块

  上一节我们简单介绍了下lua解析器的代码结构,lua解析器默认自带了一些基础的运算库、操作库,只能实现很简单的功能。那么到现在为止lua脚本简单的运行起来应该是没有什么问题了,但是这个运行的结果我们却没办法看到,因为它里面还没有写任何和硬件打交道的代码。即使是一个简单的串口打印helloworld它也需要使用到串口这个硬件驱动。

  那其他的硬件那就更不必说,都是需要人为加进去的,lua解析器自身并不带这些功能。在elua/modules文件夹内,我们也集成了一部分常用的硬件驱动。

易语言插入lua_lua

  上图中只是列出了一部分常用的驱动库,实际的modules文件夹下的的库文件有很多。接下来拿出adc库进行相应的说明,怎么才能把adc的驱动代码注册到lua虚拟机中。让用户能够在lua脚本中进行adc的采集控制。

2.3.1、注册adc

  用户如果想要让lua内核能够调用adc的相关库,首先需要在auxmods.h中声明模块名字和adc模块初始化函数。

#define AUXLIB_ADC      "adc"
LUALIB_API int ( luaopen_adc )( lua_State *L );

  声明之后需要在platform_conf.h文件中完成将模块名和初始化函数注册到lua内核中。

_ROM( AUXLIB_ADC, luaopen_adc, adc_map )

  adc模块注册之后,相当于我们只是告诉了lua内核我们提供了adc的相关驱动,但是这个驱动内有什么函数可以调用lua内核却不知道。这时候我们还需要另外在lua模块内完成adc驱动的相关调用函数的注册。

  下面的代码中我们为adc模块注册了三个可用lua脚本调用的函数,分别是openreadclose。这三条lua函数对应的c语言中的实现函数分别是adc_open()adc_read()adc_close()

const LUA_REG_TYPE adc_map[] =
{ 
  { LSTRKEY( "open" ),  LFUNCVAL( adc_open ) },
  { LSTRKEY( "read" ),  LFUNCVAL( adc_read ) },
  { LSTRKEY( "close" ),  LFUNCVAL( adc_close ) },

  { LNILKEY, LNILVAL }
};

LUALIB_API int luaopen_adc( lua_State *L )
{
    luaL_register( L, AUXLIB_ADC, adc_map );

    return 1;
}

  那这样写为什么就能被lua脚本调用呢,之间又有什么关系。下面我把这三条脚本换个写法,openreadclose三个函数对应的lua调用脚本是adc.open()adc.read()adc.close(),这样看起来是不是有点似曾相识的感觉。但一时半会好像又想不起来在哪见过,给你们五秒钟时间想,给我使劲想!!!

易语言插入lua_调用函数_02

  对,没错!如果各位同学之前使用过我们模块的adc采集功能,我猜各位应该已经想到了在luat脚本的adc例程中,有这么一段。

local function ADC0_Task()
    local adcValue, voltValue = 0, 0
    local result = adc.open(0)----------1、这一行是不是很眼熟
    while true do
        adcValue, voltValue = adc.read(0)----------2、这一行好像也有点眼熟
        if adcValue ~= 0xffff then
            log.info("ADC 0的原始测量数据和电压值:", adcValue, voltValue)
        end
        sys.wait(1000)
    end
    adc.close(0)----------3、看到这一行的话,应该就没有什么疑问了吧
end

  上面的Luat脚本中看起来有很多东西,实际上和adc有关的就用到了三行,而这三行正好就是我们的adc注册函数中注册的三条语句。我们底层中只要把这三条注册的语句写出来了,就算这三个函数的里面没有任何实现(函数还是要写的哈),是一个空函数。你也能写lua脚本给lua底层去解析,lua底层就能找到这个函数的位置然后去执行。你可以在这个函数内写很多功能,做非常复杂的事。这对于lua脚本而言,就只需要一行。

2.3.2、adc调用函数的实现

  虽然现在我们所做的工作已经能够让lua内核来运行我们的自定义模块了,当然这个模块现在还是空的,啥也没有。lua解析器跑到这里转一圈又回去了,什么事情都没干。

// adc.open(id)
static int adc_open(lua_State *L) {
    return 1; 
}

// adc.read(id)
static int adc_read(lua_State *L) {    
    return 2; 
}

static int adc_close(lua_State *L) {
    return 1; 
}

  我们还需要按照特定的规则,完成调用函数内部具体的实现方法。来大家注意看了哈,我变。

易语言插入lua_调用函数_03

  咳咳,变出来了。

// adc.open(id)
static int adc_open(lua_State *L) {
    int id = luaL_checkinteger(L, 1);
    int ret;

    MOD_CHECK_ID(adc, id);

    ret = platform_adc_open(id,0);

    lua_pushinteger(L, ret);

    return 1; 
}

// adc.read(id)
static int adc_read(lua_State *L) {    
    int id = luaL_checkinteger(L,1);
    int adc, volt;

    MOD_CHECK_ID(adc, id);

    platform_adc_read(id, &adc, &volt);

    lua_pushinteger(L, adc);
    lua_pushinteger(L, volt);
   
    return 2; 
}

static int adc_close(lua_State *L) {
    int id = luaL_checkinteger(L, 1);
    int ret;

    MOD_CHECK_ID(adc, id);

    ret = platform_adc_close(id);

    lua_pushinteger(L, ret);

    return 1; 
}

2.3.3、几个无关紧要的函数

  上面的三个代码块中我们分别为adc的打开,查询和关闭功能进行了相应的实现。我们可以找一下规律。上面三个函数的实现结构很相似,可以用下面的伪代码表示。

// adc.open(id)
static int adc_open(lua_State *L) {
    int id = luaL_checkinteger(L, 1);
    MOD_CHECK_ID(adc, id);
   	/*用户代码*/
    ...........
    /*用户代码*/
    lua_pushinteger(L, ret1);
    lua_pushinteger(L, ret2);
    lua_pushinteger(L, ret3);
    return 1; 
}

  luaL_checkinteger作用是检查函数的第 arg 个参数是否是一个字符串,并返回该字符串。

  MOD_CHECK_ID是一个宏,对应的代码如下,目的是判断传入的操作数是不是允许的允许的。打个比方,我们的设备假如只有两个adc,那我们可操作的adc编号就是0和1,你给我来个2,那程序就终止运行了并且打印adc 2 does not exist

// Helper macros
#define MOD_CHECK_ID( mod, id )\
  if( !platform_ ## mod ## _exists( id ) )\
    return luaL_error( L, #mod" %d does not exist", ( unsigned )id )

  在lua的语法中允许函数一次返回多个值,lua_pushinteger作用就是返回lua的运行结果的。

  在上面说到的lua脚本中的读取adc这一句。等号左边的两个返回值。就是由c代码中的两句lua_pushinteger代码推送的。

adcValue, voltValue = adc.read(0)
lua_pushinteger(L, adc);
    lua_pushinteger(L, volt);