25.0 Lua Scripting [T-Series Datasheet] | LabJack
 
« Close

Datasheets and User Guides

App Notes

Software & Driver

 

25.0 Lua Scripting [T-Series Datasheet]

Lua Scripting Overview

T-Series devices can execute Lua code to allow independent operation. A Lua script can be used to collect data without a host computer or be used to perform complex tasks producing simple results that a host can read.  For a good overview on the capabilities of scripting, see this LabJack Lua blog post.

LabJack T-Series Programmable PLC/DAQ Devices

Getting Started

1. Connect your device to your computer, launch Kipling, and navigate to the Lua Script Debugger tab.

2. Open the "Get Device Temperature" example and click the Run button. The console should show the current device temperature. Now click the Stop button.

3. Try out some other examples.

Running a script when the device powers up

A T-Series device can be configured to run a script when it powers on or resets.  Typically you should test scripts for a while with the Run/Stop buttons while viewing the debug output in the console. Then once everything is working correctly, enable the script at startup and close Kipling.

To enable the script at startup:

1. Click Save Script to Flash.

2. Click Enable Script at Startup.  Now when the device is powered on or reset, it will run your script.

3. After power cycling the device, if it becomes un-usable and the COMM/Status lights are constantly blinking the Lua Script is likely causing the device to enter an invalid state.  The device can be fixed by connecting a jumper wire between the SPC terminal and either FIO0 or FIO4, see the SPC section of the datasheet for more details.

Learning more about Lua

Learning Lua is very easy. There are good tutorials on Lua.org as well as on several other independent sites. If you are familiar with the basics of programming, such as loops and functions, then you should be able to get going just by looking at the examples.

Lua for T-Series Devices

Try to keep names short. String length directly affects execution speed and code size.

Use local variables instead of global variables (it's faster and less error-prone). For example, use:

local a = 10

instead of:

a = 10

You can also assign a function to a local for further optimization:

local locW = MB.W
locW(2003, 0, 1) --Write to address 2003

Lua supports multi-return. Here, both table and error are returned values:

table, error = MB.RA(Address, dataType, nValues)

Freeing Lua memory

Since Lua occupies system RAM, it's good to clean up a Lua script when it's no longer needed. When a Lua script ends, by default it does not free memory used by the Lua. The following methods clean up Lua memory:

  • Press the stop button in the Lua script tab in Kipling
  • Write 0 to LUA_RUN
  • Write 0 to LUA_RUN as the last command of your Lua script

For more memory cleaning options, see the system RAM section.

Limitations of Lua on T-Series Devices

Lua on the T4 and T7 has several limitations:

Speed: Maximum data rates can only be achieved with a host computer. Lua can not handle as much data, but is not limited by the communication overhead that a host computer is. The lack of overhead means that Lua can respond more quickly. Several benchmarking scripts are available in the Lua example set. The analog input benchmark runs at about 12.5kHz. The DIO benchmark runs at about 16kHz.

Data Types: On T-Series devices, Lua's only numeric data type is IEEE 754 single precision (float). This is more important than it sounds. Here is a good article on floating point numbers and their pitfalls: Floating Point Numbers. The single precision data type means that working with 32-bit values requires extra consideration. See the Working with 32-bit Values section below.

Script size: The T4 and T7 have 64 kBytes of RAM available. The Lua virtual machine requires about 25 kBytes. Script code adds memory requirements from there. When memory starts to get full, there are two places that are likely to throw errors:

  • The first is when a Lua script is loaded and started, but has not yet started running. When a script is started, the source code is transferred to the device and that code is compiled into byte code. Memory for the compilation process can be freed up by reducing comments and the lengths of variables and functions.
  • The second place memory errors can occur is while the script is running. Memory can be freed up using the collectgarbage function. Collecting prevents garbage from building up as much. If more memory is still needed, the script needs to be simplified or shrunk.

Notice: From a practical standpoint, Lua Scripts will start becoming too long and throwing out-of-ram errors after around 150 lines (depending on how long each line of code is).  Once this limitation is encountered, there are a few tricks that can be used to implement additional features.  They all involve making your code less readable but will assist in implementing additional features.

  1. Remove any comments from your code as these consume precious RAM.
  2. Edit the lua script file using an editor outside of Kipling and upload a minified version of the script instead of the full script file.  The suggested tool to use for minifying Lua Scripts is here: https://mothereff.in/lua-minifier.

Lua libraries

Most of the Lua 5.1 libraries are available, with the exception of functions that rely on a host operating systems, such as Time and Networking.

There are some LabJack-specific libraries:

  • I2C Library: Provides functions which simplify and reduce the memory requirements of scripts that use I2C.
  • Bit Library: Provides bitwise functions such as AND, OR, NOR, and XOR.
  • LabJack Library: Provides control of script timing and access hardware features of the LabJack device.

Passing data into/out of Lua

User RAM consists of a list of Modbus addresses where data can be sent to and read from a Lua script. Lua writes to the Modbus registers, and then a host device can read that information. 

There are a total of 200 registers of pre-allocated RAM, which is split into several groups so that users may access it conveniently with different data types.

Use the following USER_RAM registers to store information:

User RAM Registers
Name Start Address Type Access

USER_RAM#(0:39)_F32             Generic RAM registers. Useful for passing data between a host computer and a Lua script. Will not return an error if alternate data types are used.

46000 FLOAT32 R/W

USER_RAM#(0:9)_I32             Generic RAM registers. Useful for passing data between a host computer and a Lua script. Will not return an error if alternate data types are used.

46080 INT32 R/W

USER_RAM#(0:39)_U32             Generic RAM registers. Useful for passing data between a host computer and a Lua script. Will not return an error if alternate data types are used.

46100 UINT32 R/W

USER_RAM#(0:19)_U16             Generic RAM registers. Useful for passing data between a host computer and a Lua script. Will not return an error if alternate data types are used.

46180 UINT16 R/W

USER_RAM example script:

while true do
  if LJ.CheckInterval(0) then
    Enable = MB.R(46000, 3) --host may disable portion of the script
    if Enable >= 1 then
     val = val + 1
     print("New value:", val)
     MB.W(46002, 3, val)  --provide a new value to host
    end
  end
end

 

There is also a more advanced system for passing data to/from a Lua script referred to as FIFO buffers. These buffers are useful if you want to send an array of information in sequence to/from a Lua script. Usually 2 buffers are used for each endpoint, one buffer dedicated for each communication direction (read and write). For example, a host may write new data for the Lua script into FIFO0, then once the script reads the data out of that buffer, it responds by writing data into FIFO1, and then the host may read the data out of FIFO1.

User RAM FIFO Registers - Advanced
Name Start Address Type Access

USER_RAM_FIFO#(0:3)_DATA_U16             Generic FIFO buffer. Useful for passing ORDERED or SEQUENTIAL data between various endpoints, such as between a host and a Lua script. Use up to 4 FIFO buffers simultaneously->1 of each data type, all 4 different data types, or a mixture. e.g. FIFO0_DATA_U16 points to the same memory as other FIFO0 registers, such that there are a total of 4 memory blocks: FIFO0, FIFO1, FIFO2 and FIFO3. It is possible to write into a FIFO buffer using a different datatype than is being used to read out of it. This register is a buffer. Underrun behavior - throws an error.

47000 UINT16 R/W

USER_RAM_FIFO#(0:3)_DATA_U32             Generic FIFO buffer. Useful for passing ORDERED or SEQUENTIAL data between various endpoints, such as between a host and a Lua script. Use up to 4 FIFO buffers simultaneously->1 of each data type, all 4 different data types, or a mixture. e.g. FIFO0_DATA_U16 points to the same memory as other FIFO0 registers, such that there are a total of 4 memory blocks: FIFO0, FIFO1, FIFO2 and FIFO3. It is possible to write into a FIFO buffer using a different datatype than is being used to read out of it. This register is a buffer. Underrun behavior - throws an error.

47010 UINT32 R/W

USER_RAM_FIFO#(0:3)_DATA_I32             Generic FIFO buffer. Useful for passing ORDERED or SEQUENTIAL data between various endpoints, such as between a host and a Lua script. Use up to 4 FIFO buffers simultaneously->1 of each data type, all 4 different data types, or a mixture. e.g. FIFO0_DATA_U16 points to the same memory as other FIFO0 registers, such that there are a total of 4 memory blocks: FIFO0, FIFO1, FIFO2 and FIFO3. It is possible to write into a FIFO buffer using a different datatype than is being used to read out of it. This register is a buffer. Underrun behavior - throws an error.

47020 INT32 R/W

USER_RAM_FIFO#(0:3)_DATA_F32             Generic FIFO buffer. Useful for passing ORDERED or SEQUENTIAL data between various endpoints, such as between a host and a Lua script. Use up to 4 FIFO buffers simultaneously->1 of each data type, all 4 different data types, or a mixture. e.g. FIFO0_DATA_U16 points to the same memory as other FIFO0 registers, such that there are a total of 4 memory blocks: FIFO0, FIFO1, FIFO2 and FIFO3. It is possible to write into a FIFO buffer using a different datatype than is being used to read out of it. This register is a buffer. Underrun behavior - throws an error.

47030 FLOAT32 R/W

USER_RAM_FIFO#(0:3)_ALLOCATE_NUM_BYTES             Allocate memory for a FIFO buffer. Number of bytes should be sufficient to store users max transfer array size. Note that FLOAT32, INT32, and UINT32 require 4 bytes per value, and UINT16 require 2 bytes per value. Maximum size is limited by available memory. Care should be taken to conserve enough memory for other operations such as AIN_EF, Lua, Stream etc.

47900 UINT32 R/W

USER_RAM_FIFO#(0:3)_NUM_BYTES_IN_FIFO             Poll this register to see when new data is available/ready. Each read of the FIFO buffer decreases this value, and each write to the FIFO buffer increases this value. At any point in time, the following equation holds: Nbytes = Nwritten - Nread.

47910 UINT32 R

USER_RAM_FIFO#(0:3)_EMPTY             Write any value to this register to efficiently empty, flush, or otherwise clear data from the FIFO.

47930 UINT32 W

USER_RAM_FIFO example script:

aF32_Out= {}  --array of 5 values(floats)
aF32_Out[1] = 10.0
aF32_Out[2] = 20.1
aF32_Out[3] = 30.2
aF32_Out[4] = 40.3
aF32_Out[5] = 50.4

aF32_In = {}
numValuesFIO0 = 5
ValueSizeInBytes = 4
numBytesAllocFIFO0 = numValuesFIO0*ValueSizeInBytes
MB.W(47900, 1, numBytesAllocFIFO0) --allocate USER_RAM_FIFO0_NUM_BYTES_IN_FIFO to 20 bytes

LJ.IntervalConfig(0, 2000)
while true do
  if LJ.CheckInterval(0) then
    --write out to the host with FIFO0
    for i=1, numValuesFIO0 do
      ValOutOfLua = aF32_Out[i]
      numBytesFIFO0 = MB.R(47910, 1)
      if (numBytesFIFO0 < numBytesAllocFIFO0) then
        MB.W(47030, 3, ValOutOfLua)  --provide a new array to host
        print ("Next Value FIFO0: ", ValOutOfLua)
      else
        print ("FIFO0 buffer is full.")
      end
    end
    --read in new data from the host with FIFO1
    --Note that an external computer must have previously written to FIFO1
    numBytesFIFO1 = MB.R(47912, 1) --USER_RAM_FIFO1_NUM_BYTES_IN_FIFO
    if (numBytesFIFO1 == 0) then
      print ("FIFO1 buffer is empty.")
    end
    for i=1, ((numBytesFIFO1+1)/ValueSizeInBytes) do
      ValIntoLua = MB.R(47032, 3)
      aF32_In[i] = ValIntoLua
      print ("Next Value FIFO1: ", ValIntoLua)
    end
  end
end

 

Working with 32-bit Values

Lua is using a single precision float for its data-type. This means that working with 32-bit integer registers is difficult (see examples below). If any integer exceeds 24-bits (including sign), the lower bits will be lost. The workaround is to access the Modbus register using two numbers, each 16-bits. Lua can specify the data type for the register being written, so if you are expecting a large number that will not fit in a float (>24 bits), such as a MAC address, then read or write the value as a series of 16-bit integers.

If you expect the value to be counting up or down, use MB.RA or MB.RW to access the U32 as a contiguous set of 4 bytes.

If the value isn't going to increment (e.g. the MAC address) it is permissible to read it in two separate packets using MB.R.

Read a 32-bit register

--If value is expected to be changing and is >24 bits: Use MB.RA
aU32[1] = 0x00
aU32[2] = 0x00
aU32, error = MB.RA(3000, 0, 2)   --DIO0_EF_READ_A. Type is 0 instead of 1
DIO0_EF_READ_A_MSW = aU32[1]
DIO0_EF_READ_A_LSW = aU32[2]

--If value is constant and is >16,777,216 (24 bits): Use MB.R twice
--Read ETHERNET_MAC (address 60020)  
MAC_MSW = MB.R(60020, 0)  --Read upper 16 bits. Type is 0 instead of 1
MAC_LSW = MB.R(60021, 0)  --Read lower 16 bits.

--If value is <16,777,216 (24 bits): Use MB.R
--Read AIN0_EF_INDEX (address 9000)
AIN0_index = MB.R(9000, 1)  --Type can be 1, since the value will be smaller than 24 bits.
 

Write a 32-bit register

--If value might be changed or incremented by the T7 and is >24 bits: Use MB.WA
aU32[1] = 0xFF2A
aU32[2] = 0xFB5F
error = MB.WA(44300, 0, 2, aU32) --Write DIO0_EF_VALUE_A. Type is 0 instead of 1

--If value is constant and is >24 bits: Use MB.W twice
MB.W(44300, 0, 0xFF2A) --Write upper 16 bits. Type is 0 instead of 1
MB.W(44301, 0, 0xFB5F) --Write lower 16 bits.

--If value is <16,777,216 (24 bits): Use MB.W
--Write DIO0_EF_INDEX (address 44100)
MB.W(44100, 1, 7)  --Type can be 1, since the value(7) is smaller than 24 bits.

 

Loading a Lua Script to a T7 Manually

Load Lua Script Manually To Device

While Kipling handles Lua scripting details easily and automatically, the example below shows how to load a Lua script to a T7 manually.  The general process as well as some example psuedocode is below:

  1. Define or load a Lua Script and make sure a device has been opened.
  2. Make sure there is a null-character at the end of the string.
  3. Make sure a Lua Script is not currently running.  If one is, stop it and wait for it to be stopped.
  4. Write to the "LUA_SOURCE_SIZE" and "LUA_SOURCE_WRITE" registers to instruct the T7 to allocate space for a script and to transfer it to the device.
  5. (Optional) Enable debugging.
  6. Instruct the T-Series device to run the loaded Lua Script.

The C example below opens a T7, shuts down any Lua script that may be running, loads the script, and runs the script.

const char * luaScript =
    "LJ.IntervalConfig(0, 500)\n"
    "while true do\n"
    "  if LJ.CheckInterval(0) then\n"
    "    print(LJ.Tick())\n"
    "  end\n"
    "end\n"
    "\0";

const unsigned scriptLength = strlen(luaScript) + 1;
// strlen does not include the null-terminating character, so we add 1
// byte to include it.

int handle = OpenOrDie(LJM_dtT7, LJM_ctANY, "LJM_idANY");

// Disable a running script by writing 0 to LUA_RUN twice
WriteNameOrDie(handle, "LUA_RUN", 0);
// Wait for the Lua VM to shut down (and some T7 firmware versions need
// a longer time to shut down than others):
MillisecondSleep(600);
WriteNameOrDie(handle, "LUA_RUN", 0);

// Write the size and the Lua Script to the device
WriteNameOrDie(handle, "LUA_SOURCE_SIZE", scriptLength);
WriteNameByteArrayOrDie(handle, "LUA_SOURCE_WRITE", scriptLength, luaScript);

// Start the script with debug output enabled
WriteNameOrDie(handle, "LUA_DEBUG_ENABLE", 1);
WriteNameOrDie(handle, "LUA_DEBUG_ENABLE_DEFAULT", 1);
WriteNameOrDie(handle, "LUA_RUN", 1);

The above example is valid C code, where the following functions are error-handling functions that cause the program to exit if an error occurs:

  • OpenOrDie wraps LJM_Open
  • WriteNameOrDie wraps LJM_eWriteName
  • WriteNameByteArrayOrDie wraps LJM_eWriteNameByteArray
  • The Lua script in the example above is in the form of a C-string, e.g. a string with a null-terminator as the last byte.

    To download a version of the above example, see utilities/lua_script_basic.c, which includes a function that reads debug data from the T7.

    Reading Debug Data/Print Statements

    After a script has started (with debugging enabled) any information printed by a lua script can be read by a user application using the "LUA_DEBUG_NUM_BYTES" and "LUA_DEBUG_DATA" registers.