How to use Lua scripts
First use
Setting Up Lua Scripting with Visual Studio Code
To make Lua scripting easier, follow these steps to set up Visual Studio Code with the Lua extension:
-
Install Visual Studio Code
- If you don't have it yet, download and install Visual Studio Code.
-
Add the Lua Extension
-
Create a Folder for Libraries
- Make a new folder on your computer to store the emdrive library. For example, you can name it
D:\LuaLibraries
.
- Make a new folder on your computer to store the emdrive library. For example, you can name it
-
Configure Developer Mode and Add Library Path
-
Edit User Settings (JSON)
- In the opened
settings.json
file, add the following code.
"Lua.misc.parameters": [ "--develop=true" ], "Lua.workspace.checkThirdParty": true, "Lua.workspace.userThirdParty": ["path_to_our_library"]
- Make sure to keep any existing code intact. Add a comma at the end of the last line of the existing code, then paste the new code below it. Update the path to match your folder (
D:/LuaLibraries
). Use forward slashes instead of backslashes.
- In the opened
-
Save and Close
- Save the changes to
settings.json
. - Close Visual Studio Code.
- Save the changes to
Example of "settings.json":
Getting Started with Lua Scripting
-
Create a New Folder
- Make a new folder for your project.
-
Open the Folder with Visual Studio Code
-
Create a New Lua Script
Using the emdrive Library
Option 1: Automatic Setup
-
Add Code to Script
- In
firstscript.lua
, add the following line:
require('emdrive')
- In
-
Apply Path Modification
-
Check .vscode Folder
- A new folder named
.vscode
will be created. - Inside, there will be a
settings.json
file containing the path to the library.
- A new folder named
Option 2: Manual Setup
-
Create .vscode Folder
- Manually create a
.vscode
folder in your workspace.
- Manually create a
-
Create settings.json File
- Inside the
.vscode
folder, create asettings.json
file.
- Inside the
-
Add Library Path to settings.json
- Add the following code to
settings.json
:{ "lua.workspace.library": [ "D:/LuaLibraries" ] }
- Add the following code to
Examples
Example 1: "Hello World"
Description:
Print "Hello World" every 100ms to IO_Output.ForTo make the script to workwork, you need to have 2two functionsfunctions: "Initialite"Initialize
and "Loop"Loop
.
-
Copy the Code
Copy the following code to an
example.lua
file.file:function Initialize() LoopPeriodMs = 100 end function Loop() IO.Write("Hello World ") end
-
To loadLoad thescriptScript on theinverterInverteryou- Go to
go into0x2040 0x05 - Script
,. - Select
you select write"Write fromfilefile" andthenclick "...". - A
poppop-up windowappearswillwhereappear.you then locatedLocate the saved script and click "Open",. - Click
that you click write."Write".
needwhereafter - Go to
-
IfCheck if thescriptScript iscorrectlyValidwritten- Go
can se under objectto0x2040 0x02 - Status
. - The value "3"
- Which means thatindicates the downloaded file is valid.
youtheTorun - Go
-
Run the
scriptScriptyou- Set
0x2040 0x03 - Control
=to2
. - If the script is
runningrunning, the status value should be "5".
need to set objectYou can see in status ifTose - Set
-
View the
theOutput"Hello- Go to
go to object0x2040 0x07 - IO_output
,. - Click
you"Read"cantoclick read andsee the "Hello World" stringwill appearin the Description window.
World" you needwhere - Go to
Example 2: Data types & variables
Example 2.1: Data types
Lua is a lightweight, high-level programming language known for its simplicity and flexibility. It has several basic data types, each serving a different purpose. Here are the main data types in Lua along with examples for each:
- Nil
Represents the absence of a value. - Boolean
Represents a boolean value, either true or false. - Number
Represents both integer and floating-point numbers. - String
Represents a sequence of characters. - Table
Represents associative arrays, which can be used as arrays, dictionaries, or other data structures. - Function
Represents a callable function.
function Initialize()
LoopPeriodMs = 100
end
function Loop()
-- 1. Nil
local myVariable = nil
IO.Print(myVariable) -- Output: nil
-- 2. Boolean
local isTrue = true
local isFalse = false
IO.Print(isTrue) -- Output: true
IO.Print(isFalse) -- Output: false
-- 3. Number
local integerNumber = 42
local floatingNumber = 3.14
IO.Print(integerNumber) -- Output: 42
IO.Print(floatingNumber) -- Output: 3.14
-- 4. String
local myString = "Hello, Lua!"
IO.Print(myString) -- Output: Hello, Lua!
-- 5. Table
local myTable = { key1 = "value1", key2 = "value2" }
IO.Print(myTable.key1) -- Output: value1
IO.Print(myTable.key2) -- Output: value2
local arrayTable = { "apple", "banana", "cherry" }
IO.Print(arrayTable[1]) -- Output: apple
-- 6. Function
local function myFunction(a, b)
return a + b
end
IO.Print(myFunction(2, 3)) -- Output: 5
-- 6. Anonymous function
local anonFunction = function(x, y)
return x * y
end
IO.Print(anonFunction(4, 5)) -- Output: 20
end
Example 2.2: Variables
- Global variables
Global variables are accessible from anywhere in the program unless shadowed by a local variable of the same name. By default, any variable declared without thelocal
keyword is global.
myGlobalVariable = 10 -- Global variable function printGlobal() print(myGlobalVariable) end printGlobal() -- Output: 10
- Local Variables
Local variables are only accessible within the block or function where they are declared. They help avoid polluting the global namespace and can be used to manage scope more effectively.
local myLocalVariable = 20 -- Local variable function printLocal() local myLocalVariable = 30 -- Local to this function print(myLocalVariable) end printLocal() -- Output: 30 print(myLocalVariable) -- Output: 20
- Table fields
Variables can also be fields of tables, allowing for the creation of more complex data structures like arrays, dictionaries, and objects.
local myTable = { field1 = "Hello", field2 = "World" } print(myTable.field1) -- Output: Hello print(myTable.field2) -- Output: World
Variable scope
- Global Scope: Variables declared outside of any function or block are global by default.
- Local Scope: Variables declared with the
local
keyword within a function, block, or loop are local to that block.
- Global vs. Local
myGlobal = "I am global" function testScope() local myLocal = "I am local" print(myGlobal) -- Accessible print(myLocal) -- Accessible end testScope() print(myGlobal) -- Accessible -- print(myLocal) -- Error: myLocal is not accessible here
- Local Variable Shadowing
local x = 5 -- Local variable in the main chunk function shadowTest() local x = 10 -- Local variable in the function print(x) -- Output: 10 end shadowTest() print(x) -- Output: 5
Global variables and tables use a lot of memory, especially in embedded systems. To save memory, it's best to use local variables whenever possible.
If you use a global variable at the start of your code and don't need it later, clear the global variable to free up memory. You can do this by adding the following code:
myGlobalVariable = nil
Example 3: Inputs/Outputs
Example 3.1: Use of digital inputs & outputs
Turn low side 1 - ON when switch on digital pin 1 is switched ON.
function Initialize()
LoopPeriodMs = 10
end
function Loop()
local switch = Digital.Get(DIGITAL_IN_1)
if switch == 1.0 then
Digital.Set(LOW_SIDE_1, 1)
else
Digital.Set(LOW_SIDE_1, 0)
end
end
Example 3.2: Use of analog inputs
Print analog value of analog input 1 when switch on digital pin 1 is switched ON.
function Initialize()
LoopPeriodMs = 10
end
function Loop()
local switch = Digital.Get(DIGITAL_IN_1)
if switch == 1.0 then
local rawVal = Analog.Get(ANALOG_IN_1)
IO.Print(rawVal)
end
end
Example 4: Blink LED
Example 4.1: Blink LED - "delay"
Loop period is set to 1000ms
LED is connected on low side 1.
Blink LED every 0.5s with Time.WaitMs().
function Initialize()
LoopPeriodMs = 1000
end
function Loop()
local ledState = Digital.Get(LOW_SIDE_1)
if(ledState == 0.0) then
Digital.Set(LOW_SIDE_1, 1.0)
else
Digital.Set(LOW_SIDE_1, 0.0)
end
Time.WaitMs(500)
end
NowIf we set the LoopPeriodMs =to 100We see that100ms, the script doesstops notrunning runand anymore,shows we get undera status the numberof "6", which meansmeaning "Script timed out and was stopped".stopped."
Here are the functionkey points to understand:
- Time.WaitMs()
,weFunction:waitThisinfunction pauses thecodescript forthea specifiedtimetime.(inInthisourexampleexample,0,5s).itButpausesweforspecified0.5thatseconds. - LoopPeriodMs Setting: We set the script to loop every 100ms.
- Error Explanation: Since the script must loop
througheveryunder100ms100ms,butandpausesthusforwe0.5getseconds, it causes an error.
Important functionNote: Be very careful when using the Time.WaitMs() we havefunction to beavoid verysuch careful.errors.
Example 4.2: Blink LED - using loop time
We want the LED to blink the LED every 0.5s5 butseconds, still want to havewhile the main loop to be executedruns every 10ms. WeHere's achivehow thiswe withdo theit:
Main_loop_period = 10
Counter = 1
function Initialize()
LoopPeriodMs = Main_loop_period
end
function Loop()
if ((Counter)*Main_loop_period)%500==0 then
local ledState = Digital.Get(LOW_SIDE_1)
if(ledState == 0.0) then
Digital.Set(LOW_SIDE_1, 1.0)
else
Digital.Set(LOW_SIDE_1, 0.0)
end
end
if Counter<50 then
Counter=Counter+1
else
Counter=1
end
end
Example 4.3: Blink LED - using processor time
The script initializes a start time variable and defines two functions. In the Loop
function, the script checks if the StartTime
is nil and if so, sets it to the current time in milliseconds.
The Loop
function then continuously gets the current time in milliseconds. If 500 milliseconds have passed since the StartTime
, it resets the StartTime
to the current time.
It then gets the state of a digital output LOW_SIDE_1
.
If LOW_SIDE_1
is off (state is 0.0), it turns it on by setting its (state to 1.0.0).
If LOW_SIDE_1
is on (state is not 0.1.0), it turns it off by setting its (state to 0.0.0).
This process repeats every 500 milliseconds, toggling the state of LOW_SIDE_1
each time.
StartTime = nil
function Initialize()
LoopPeriodMs = 10
end
function Loop()
--Execute only once at the start of the loop
if(StartTime == nil) then
StartTime = Time.GetMs()
end
--Chechk every loop what is the time
local CurrentTime = Time.GetMs()
if (CurrentTime - StartTime >= 500) then
StartTime = CurrentTime
local ledState = Digital.Get(LOW_SIDE_1)
if(ledState == 0.0) then
Digital.Set(LOW_SIDE_1, 1.0)
else
Digital.Set(LOW_SIDE_1, 0.0)
end
end
end
Example 5: CAN send
Example 5.1: (CAN send simple message)
We want toTo send a simple CAN message frame from lua script every 100ms with an ID =of 0x205 and data value 500 using the first 2 bytes.bytes, follow these steps:
For
-
canUse
useTemplate:the- Start with example 4.3 as a template.
Weneed -
addAdd Variables and Functions:
- Define a
CanID
variable,variable. - Initialize CAN with the
CAN.
function.Initialite(Initialize()
Forsending - Define a
-
onlySending
needParameters:- Use the
paramaterCAN_TX_ONLY
CAN_TX_ONLY,parametermessagesfor sending. - Messages will not be
extendedextended. - Set filters to 0 since we are only sending.
and - Use the
-
Modify the
filtersCode:can- Delete the code for toggling the
outputoutput.
also be 0, because as mentioned we will only send.When that is finished we deleteandcreate - Delete the code for toggling the
-
Create Custom Function:
- Create a
customfunctionwhichthatwill beis called every 100ms.The - Name
takes in onethe argumentwhichData_raw
. - Split
be called Data_raw, that data is then splitData_raw
into 2bytesbytes. - Send the data using the
CAN.Send()
function.
functionwilland sent via - Create a
CanID = 0x205
StartTime = nil
function Initialize()
CAN.Initialize(CAN_TX_ONLY,false,0,0)
LoopPeriodMs = 10
end
function Loop()
--Execute only once at the start of the loop
if(StartTime == nil) then
StartTime = Time.GetMs()
end
--Chechk every loop what is the time
local CurrentTime = Time.GetMs()
if (CurrentTime - StartTime >= 100) then
StartTime = CurrentTime
SendData(500)
end
end
function SendData(Data_raw)
local val = Data_raw
local byte0 = math.floor(val) & 0xFF
local byte1 = (math.floor(val) & 0xFF00) >> 8
CAN.Send(CanID,{byte0,byte1,0,0,0,0,0,0})
end
Example 5.2 : CAN send extended message (J1939)
We will do the same as in example 5.1 except we will send an extended message (It will be a J1939 message PH3 - the data order might be different)different in the standard).
ID = 0x18FF8203.
The only thing we need to change is 2 lines, we need to change the CanID and in the CAN.Initialize() functionfunction, the boolean value to true. If we change only the CanID then the message will still be sent out but instead withthe ID 0x18FF8199 it will be 0x199 (the last 3 digits willof be- theID ID)0x18FF8199).
CanID = 0x18FF8199
CAN.Initialize(CAN_TX_ONLY,true,0,0)
Example 6: CAN receive
Example 6.1 : CAN receive
In this example we will turn ON and OFF a LED that is connected on low side 1.1 with a received can message.
The Can ID has to be 0x123 and we will send only one byte of data. If the value of data is 1 then the LED will be turned ON, otherwise it will be turned OFF.
To use the CAN receive function we need to change the CAN.Initialite() and add a function called "CAN.Received(message) end"". With the following code we always go into the CAN.Received() function when a message is received and then we check if the ID is correct.
function Initialize()
CAN.Initialize(CAN_RX_TX,false,0,0)
LoopPeriodMs = 10
end
function Loop()
end
function CAN.Received(message)
if (message.ID == 0x123) then
IO.Print(" Data1: ", message.Data[1])
if(message.Data[1] == 1) then
Digital.Set(LOW_SIDE_1, 1.0)
else
Digital.Set(LOW_SIDE_1, 0.0)
end
end
end
We can also add a filter so we only go into the CAN.Received() when the message has a proper ID. We achieve this with the following code.
function Initialize()
CAN.Initialize(CAN_RX_TX,false,0x123,0x123)
LoopPeriodMs = 10
end
function Loop()
end
function CAN.Received(message)
--if (message.ID == 0x123) then
IO.Print(" Data1: ", message.Data[1])
if(message.Data[1] == 1) then
Digital.Set(LOW_SIDE_1, 1.0)
else
Digital.Set(LOW_SIDE_1, 0.0)
end
--end
end
Example 7: Read & Set CANopen objects
Within a lua script you can read and modify CANopen objects, just like with the configurator tool.
In this exampleexample, we willwe'll use an analog input (HW AIN1AIN1) )as a throttle andto set the motor velocity reference (object 0x3010 0x05
- VelocityRef object.). We will also read the samethis object and print itits value to the luaLua output. WeAdditionally, will alsowe'll limit the maxmaximum RPM insideusing lua withthe math library,library to prevent the motor from running away if there's a problem with the analog reading appears the motor wont run away.reading.
0V = 0RPM
5V = 200RPM
For this example to work you need an inverter that is configured to work with the connected motor. You need to be first able to spin it in velocity mode using the configurator.
When you start the script go to operational and turn on PWMs manually.
If the analog throttle is damaged (either a short circuit or a broken circuit), there is no safety implemented.system in place. Here’s what can happen:
If therethe circuit is a breakbroken, no voltage will be appliedapplied, and the RPM =will be 0. But if
If there is a short thancircuit, the inverter will getreceive the full 5V on the analog inputinput, causing the RPM =to MAX.go Thisto isthe whymaximum.
To prevent these issues, we need to implement someadd safety featuresfeatures. (These will be showndemonstrated in exampleExample 8). 8.
VelocityRef = {0x3010, 0x05}
function Initialize()
LoopPeriodMs = 10
end
function Loop()
local rpm = Analog.Get(ANALOG_IN_1) * 40
rpm = math.min(rpm,200)
CANopen.SetObjectValue(VelocityRef, rpm)
local VelRef_from_CANopen = CANopen.GetObjectValue(VelocityRef)
IO.Print(VelRef_from_CANopen)
end
Example 8: Demo application
Example 8.1 : Demo application using CANopen objects.
WeIn this example, we will demonstrate how to use the HW AIN1 input for a simple throttle inputcontrol -with potenciometera potentiometer (0-5V.5V).
- Throttle
=Input: HW AIN1 (0-5V Potentiometer)- Minimum RPM: 0
=(corresponds to 0.5VMax5V) - Maximum
=RPM: 200=(corresponds to 4.5V
5V) - Motor Stop Conditions:
- If the throttle voltage is
bellowbelow 0.2Vandor above 4.8V we stop8V, the motor(will stop to protect against short and breakprotection).
issues.
- If the throttle voltage is
- PWM Enable Condition:
- If the throttle voltage is
bellowbelow 0.5 we enable5V, thePWMsPWM signals will be enabled
- If the throttle voltage is
RPM - Minimum RPM: 0
function Initialize()
CANopen.SetObjectValue(VelocityRef, 0) -- Set Velocity ref to 0
CANopen.SetObjectValue(ControlMode, 1) -- Set velocity mode
CANopen.SetNMTState(CO_OPERATIONAL) -- Go into operational mode
LoopPeriodMs = 100
end
function Loop()
local throttleVoltage = Analog.Get(ANALOG_IN_1)
-- Decide whether to enable or disable the motor
if Digital.Get(DIGITAL_IN_1) == 1 then
-- only enable if voltage on analog in 1 is < 0.5 V so the motor does not start ang goes to high RPM
if (throttleVoltage < 0.5) then
CANopen.SetObjectValue(PwmControl, 1)
end
else
CANopen.SetObjectValue(PwmControl, 0)
end
if (throttleVoltage > 0.2 and throttleVoltage < 4.8) == true then
-- Map values 0.5 - 4.5 V to 0 - 200 RPM and
local rpm = (throttleVoltage - 0.5) / 4 * 200
rpm = math.min(rpm, 200)
rpm = math.max(rpm, 0)
CANopen.SetObjectValue(VelocityRef, rpm)
else
CANopen.SetObjectValue(VelocityRef, 0)
CANopen.SetObjectValue(PwmControl, 0)
end
end
Example 8.2 : Demo application using dedicated motor library.
MinIn RPMthis = 0 = 0.5VMax RPM = 200 = 4.5VIf throttle voltage is bellow 0.2V and above 4.8Vexample we stopwill have the motorsame (shortfunctionality and break protection).If voltage is bellow 0.5 we enableof the PWMs.
code as in example 8.1. but with the use of motor library
With the use of the motor library the code is easier to read and write than example 8.1.
function Initialize()
Motor.SetReferenceVelocity(0) -- Set Velocity ref to 0
Motor.SetControlMode(VELOCITY_MODE) -- Set velocity mode
CANopen.SetNMTState(CO_OPERATIONAL) -- Go into operational mode
LoopPeriodMs = 100
end
function Loop()
local throttleVoltage = Analog.Get(ANALOG_IN_1)
-- Decide whether to enable or disable the motor
if Digital.Get(DIGITAL_IN_1) == 1 then
-- only enable if voltage on analog in 1 is < 0.5 V so the motor does not start ang goes to high RPM
if (throttleVoltage < 0.5) then
Motor.Enable()
end
else
Motor.Disable()
end
if (throttleVoltage > 0.2 and throttleVoltage < 4.8) == true then
-- Map values 0.5 - 4.5 V to 0 - 200 RPM and
local rpm = (throttleVoltage - 0.5) / 4 * 200
rpm = math.min(rpm, 200)
rpm = math.max(rpm, 0)
Motor.SetReferenceVelocity(rpm)
else
Motor.SetReferenceVelocity(0)
Motor.Disable()
end
end
Example 9: Throttle script (state - machine)
We
Throttle willControl
- Adjusting the
throttleThrottle:usingUseathe potentiometeronconnectedAnalog_IN_1,towhichAnalog_IN_1. - Protection:
haveThe potentiometer is protected against short and breakcircuitcircuits. - Deadband:
implemented. We will also have aA deadband of 0.3V.3VTheisminimumsetspeedforshallthebepotentiometer. - Speed Limits:
- Minimum speed: 0 RPM
- Maximum
the maxspeed: 300 RPM
and(limits).
Motor Operation
- Activation:
- The motor
willruns onlyspin if thewhen digital pin 1 (ON/OFF switch) isconnectedconnected. - Change motor direction
can be changed withusing a switch on digital pin 3.
- we will have a switch ON/OFF.TheTheactual - The motor
- Speed
isSetting:set- Forward max speed: Object 0x3020 0x0D
- Reverse max speed: Object 0x3020 0x0E
- Switching direction at max speed changes to the
objectscorresponding0x3020reverse0x0Dspeed.
through
LED Diagnostics
- LED Indicator (HS1):
- On: System working normally.
- Blinking: System in error mode.
- Error Blink Codes:
- Error 2: Blinks twice, then pauses.
- Error 3: Blinks three times, then pauses.
Voltage and RPM Details
- Working Voltage Range:
- Minimum RPM: at 0.5V
- Maximum RPM: at 4.5V
- Deadband: 0.3V
- Error Conditions:
- ERROR1: Voltage out of bounds
- ERROR2: Analog_IN_1 < (Min_Volt -
Forward_max_speedDeadband) - ERROR3:
0x3020Analog_IN_10x0E>-(Max_VoltReverse_max_speed.+ Deadband)
and
CANopen Error Messages
- Error Message (0x80 + nodeID):
- Sent every 100ms in error state.
- Data length: 8 bytes.
- Byte 0 Mapping:
- Bit 0: Throttle potentiometer error out of bounds
- Bit 1: Throttle potentiometer break circuit error
- Bit 2: Throttle potentiometer short circuit error
- Warning Message (0x180 + nodeID):
- Sent every 100ms in start state.
- Data length: 8 bytes.
- Byte 0: Warning code 0x01 (Motor disabled, potentiometer not in min position)
If the motor spins at Forward max speed and you switch the direction with the switch then it will spin at Reverse max speed
There will be LED light connected to HS1 which will be for diagnostic. When the system is working it will be turned on. If the system is in error mode it will blink.ERRO2 = blink 2 times then pauseERRO3 = blink 3 times then pause
Working voltage rangeMin_RPM = 0, at Min_Volt = 0.5VMax_RPM = 200, as Max_Volt = 4.5V
DeadBand = 0.3V
This means that if voltage goes (bellow Min_RPM - DeadBand)ERROR1 => Out of boundsERROR2 => Analog_In_1 < Min_Volt- DeadBandERROR3 => Max_Volt + DeadBand < Analog_In_2
The application will send a standard CANopen error message on 0x80 + nodeID every 100ms while in error state. There will also be TPDO message on 0x180 + nodeID which will send out the warrning code every 100ms while in start state.0x80 + nodeID message will have a data length of 8 bytes. And the mapping is as follows:byte0:bit0 = Throttle potentiometer errorbit1 = Throttle potentiometer break circuit errorbit2 = Throttle potentiometer short circuit error
0x180 9 nodeID message will have a length of 8 bytes. And the mapping is as follows:byte0: warning code
Warning code:0x01 Motor is disabled, due to potentiometer not in min position
--[[ @file Example_9
@brief User manual examples
@author Matic Jehart
@date 05.07.2024
@version V1.0.0
]]Limits
MAX_RPM = 300
MIN_RPM = -300
-- Objects
FORWARD_MAX_SPEED_ID = {0x3020, 0xD}
REVERSE_MAX_SPEED_ID = {0x3020, 0xE}
LED_ID = {0x30A4, 0x02}
-- Inputs
ENABLE_SW = DIGITAL_IN_1
DIRECTION_SW = DIGITAL_IN_2
THROTTLE_IN = ANALOG_IN_1
DIRECTION_IN = DIGITAL_IN_3
-- Voltage thresholds
THROTTLE_MIN_V = 0.5
THROTTLE_MAX_V = 4.5
DEADBAND = 0.3
ERROR = 0
ERROR_reg = 0
WARNINGS = 0
StartTime1 = nil
StartTIme2 = nil
StartTime3 = nil
StartTime4 = nil
FlagCounter = 0
DIN = {ON = 1, OFF = 0}
LED = {OFF = 0x0, ON = 0x1}
DIRECTION = {FORWARD = 1, REVERSE = 0}
-- Application FSM
Application = {}
function Initialize()
CAN.Initialize(CAN_TX_ONLY,false,0,0)
Application.NextState = "Idle"
Motor.SetReferenceVelocity(0)
Motor.SetControlMode(VELOCITY_MODE)
LoopPeriodMs = 10
end
function Loop()
Application[Application.NextState]()
end
Application.Idle = function ()
local ledValue = CANopen.GetObjectValue(LED_ID)
if ledValue == 1 then
CANopen.SetObjectValue(LED_ID, 0)
endLED.ON)
CANopen.SetNMTState(CO_OPERATIONAL)
Application.NextState = "Start"
end
Application.Start = function ()
-- IF enable switch is off go to stop state
if (Digital.Get(ENABLE_SW) == DIN.OFF ) then
Application.NextState = "Stop"
return
end
-- Get voltage
local throttleVoltage = Analog.Get(THROTTLE_IN)
-- Go into error state if throttle voltage is out of bounds
if OutOfBounds(throttleVoltage, (THROTTLE_MIN_V - DEADBAND), (THROTTLE_MAX_V + DEADBAND)) == true then
ERROR = (ERROR or 0) | 1
Application.NextState = "Error"
return
end
-- Check that voltage is bellow minimal throttle voltage - so that the motor does not start at high speed
if Motor.GetState() == MOTOR_OFF then
if throttleVoltage < (THROTTLE_MIN_V) then
Motor.Enable()
WARNINGS = WARNINGS & Negate_Xbit(1, 8)
return
else
WARNINGS = (WARNINGS or 0) | 1
end
end
-- Only set velocity reference when pwms are enabled
if ((Motor.GetState() == MOTOR_RUN) and (throttleVoltage >= 0.5)) then
-- Calculate Voltage
--local rpm = ((throttleVoltage - THROTTLE_MIN_V) * MAX_RPM) / (THROTTLE_MAX_V - THROTTLE_MIN_V)
local rpm = ((throttleVoltage - THROTTLE_MIN_V) * GetMaxSpeed()) / (THROTTLE_MAX_V - THROTTLE_MIN_V)
-- Limit RPM
rpm = math.min(rpm, MAX_RPM)
rpm = math.max(rpm, MIN_RPM)
IO.Print("REF: ", rpm)
-- Set ref
Motor.SetReferenceVelocity(rpm)
end
-- Send warnings every 100ms
local CurrentTime = Time.GetMs()
if((StartTIme2 == nil) or (CurrentTime-StartTIme2) >= 100) then
SendWarnings()
end
end
Application.Stop = function ()
Motor.Disable()
--When eneble sw is activated go to start state
if (Digital.Get(ENABLE_SW) == DIN.ON) then
Application.NextState = "Start"
end
end
Application.Error = function ()
Motor.Disable()
Motor.SetReferenceVelocity(0)
--local ledValue = CANopen.GetObjectValue(LED_ID)
local throttleVoltage = Analog.Get(THROTTLE_IN)
local ErrorBlinkCounter = 0
local CurrentTime
--If bit 0 = 1 thanthen OutOf BoundsOutOfBounds is detected
if (ERROR & 1) == 1 then
if throttleVoltage < (THROTTLE_MIN_V - DEADBAND) then
ERROR = ERROR | 2
ERROR_reg = ERROR_reg | 1 -- set the generic error register
elseif throttleVoltage > (THROTTLE_MAX_V + DEADBAND) then
ERROR = ERROR | 4
ERROR_reg = ERROR_reg | 1 -- set the generic error register
else
-- Clear ERROR bit 0, 1, 2
ERROR = ERROR & Negate_Xbit(7, 16)
-- Clear the generic error register
ERROR_reg = ERROR_reg | Negate_Xbit(1, 8)
end
end
-- Send Errors every 100ms
CurrentTime = Time.GetMs()
if ((StartTime1 == nil) or (CurrentTime - StartTime1) >= 100) then
StartTime1 = CurrentTime
SendError()
end
-- Determine the how many times we want to blink the led
if ((ERROR & 2) >> 1) == 1 then
ErrorBlinkCounter = 2
elseif ((ERROR & 4) >> 2) == 1 then
ErrorBlinkCounter = 3
end
-- Start the blinking cycle if enough time has passed
CurrentTime = Time.GetMs()
if StartTime3 == nil or (CurrentTime - StartTime3) >= 3000 then
StartTime3 = CurrentTime
FlagCounter = 0
end
-- Handle the blinking logic
if FlagCounter < 2 * ErrorBlinkCounter then
if StartTime4 == nil or (CurrentTime - StartTime4) >= 200300 then
StartTime4 = CurrentTime
FlagCounter = FlagCounter + 1
-- Toggle LED
--if ledValue == 0 then
if FlagCounter % 2 == 1 then
CANopen.SetObjectValue(LED_ID, LED.ON)
else
CANopen.SetObjectValue(LED_ID, LED.OFF)
end
end
else
-- Ensure the LED is off during the pause period
CANopen.SetObjectValue(LED_ID, LED.OFF)
end
if ERROR == 0 then
FlagCounter = 0
CANopen.SetObjectValue(LED_ID, LED.OFF)
Application.NextState = "Idle"
return
end
end
function GetMaxSpeed()
if Digital.Get(DIRECTION_IN) == DIRECTION.FORWARD then
return CANopen.GetObjectValue(FORWARD_MAX_SPEED_ID)
end
return CANopen.GetObjectValue(REVERSE_MAX_SPEED_ID)
end
function SendError()
-- Send on ID + nodeID; we get the NodeId from CANopen off the inverter
local CanID = 0x80 + CANopen.GetObjectValue({0x100B, 0x00})
local byte0 = ERROR & 0xFF
local byte1 = (ERROR & 0xFF00) >> 8
local byte2 = ERROR_reg & 0xFF
CAN.Send(CanID,{byte0, byte1, byte2, 0, 0, 0, 0, 0})
end
function SendWarnings()
-- Send on PDO + nodeID;
local CanID = 0x180 + CANopen.GetObjectValue({0x100B, 0x00})
local byte0 = WARNINGS & 0xFF
CAN.Send(CanID,{byte0, 0, 0, 0, 0, 0, 0, 0})
end
function OutOfBounds(throttleVoltage, minVal, maxVal)
if (throttleVoltage < minVal) or (throttleVoltage > maxVal) then
return true
else
return false
end
end
--- Fuctino to negate bits
---@param value number Value to negate
---@param xBit number Number of bites e.g 16bit => xBit = 16
function Negate_Xbit(value, xBit)
local negated = 0
for i = 0, (xBit - 1) do
if (value & (1 << i)) == 0 then
negated = negated + (1 << i)
end
end
return negated
end
Error 0x06070010 when loading a script may indicate Lua object 0x2040 0x01 isn't set to 1. To resolve, ensure proper access level in Configurator to unlock this feature. If you do not have the proper access level contact us.
Best practices
For further insights and practical tips, I recommend exploring "Programming in Lua," available at: Programming in Lua. Authored by experts in the field, this comprehensive resource offers invaluable guidance for Lua developers. It's worth noting that the entire topic is accessible free of charge as of April 24, 2024. Additionally, some sections of the topic include examples to further illustrate key concepts and techniques.
Minimizing Global Variables
In Lua scripting, it's imperative to exercise caution with global variables to prevent memory exhaustion and script failures, especially on embbeded systems. Emphasizing the utilization of local variables whenever possible is paramount. By minimizing global variable usage, we mitigate the risk of memory overflow and enhance script performance.
Example:
myGlobalVaribale = 10 --Global variable
local myLocalVaribale = 10 --Local variable
Issues
If IO.Print() isn't functioning, it may signal an outdated firmware version. Firmware 0x10C01 lacks support, while 0x10D02 enables the function.