As in the previous tutorial, we see two different ways to use the Innovator Hub for a "practical" purpose - this time, driving a home-made robot: using a Lua script, and also using TI-BASIC.
This robot vehicle is driven by two light servo motors. In order to limit the power demand, we will connect the voltage lead for both of these to the one 3.3V BB connection (I "piggy-backed" one onto the other). Using the more usual 5V BB9 port would require the other wheel to also use 5 volts, and together this is too much, so using 3.3 volts for each gives us a more reliable (if slightly slower) experience.
The best option (if not the simplest) is to run the servos from an external battery. Use either a 9 volt "transistor" (D-type) battery or a 4-pack battery case. Either way, you will have a positive (red) and a negative (ground, black) lead. Using a breadboard (or even part of a breadboard), connect the red (positive) lead on the same line as the red (voltage) leads from your servos. The black ground lead needs to be connected to a ground pin on your board, along with the black/ground leads from the servos, connected to other ground pins (I just used three ground BB pins). Connect the other (signal) leads from the servos to the appropriate pins on your board, and you now have a circuit which runs through your board but with no risk of frying it!
In addition to sending instructions to two attached servo motors to go forward, back, left and right, we also extend the activity to have the robot follow a bright light using the on-board brightness sensor, and auto-drive to avoid obstacles using an ultrasonic ranger (if you have one).
As with LaunchPad lesson 9 (in which we built a BLE robot using a bare MSP432 LaunchPad board and spare LEGO from my grandchildrens' toybox) we will supplement our main chassis - in this case, our TI Innovator™ Hub - with a few extra components:
Two servo motors - I used Feetech FS90R micro continuous rotation servo from eBay and paid around $AUD6 each (total around $USD10) - NOTE: NOT the identical-looking Feetech FS90 which will NOT work for our purposes!
local isASIavailable = false
local hubStrOn, hubStrOff, msgStr, rxValue, timeStr, delay, repeats, drive
local tempo, key, I
local list1, list2, index, dataList = {}, {}, {}, {}
var.store("index", index)
var.store("dataList", dataList)
function addMsg(input)
msgStr = input
print(msgStr)
screen:invalidate()
end
function on.construction()
--the next three lines are required for Hub connection
TI_Innovator.init(TI_InnovatorStateCallback)
TI_Innovator.setReadListener(TI_InnovatorReadCallback)
TI_Innovator.connect()
TI_Innovator.Send(hubStrOn) -- This is an example of sending a SET command to the Hub
TI_Innovator.Read() -- This is an example of sending a READ command to the Hub
screen:invalidate()
function str2list(input, def)
local list = {}
if input:find(def) then
list = input:split(def)
table.foreachi(list,print)
else
list = {input}
end
return list
end
function TI_InnovatorReadCallback(port, error_msg) -- this is the callback function that is defined above and catches the result of a READ command send to the Hub.
rxValue = port:getValue() or '' -- this gets the actual return value from the hub and puts it in the variable rxValue. The user may choose any variable name and also may do any calibration or ranging of the value here. Note that rxValue may be numeric, list or string (hence the utility function list2str)
if tonumber(rxValue) then rxValue = math.floor(100*rxValue+0.5)/100
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
addMsg(hubStrOn)
if tonumber(rxValue) then voltage = (rxValue/2^14)*3.3 end -- an example of converting a raw 14 bit ADC value into voltage.
screen:invalidate()
end
function TI_InnovatorStateCallback(event)
addMsg("TI_InnovatorStateCallback")
if 'ready' == event then
TI_InnovatorConfig()
elseif "disconnected" == event then
-- user may choose to do some clean up or display a msg when the Hub is disconnected.
addMsg("TI_Innovator Hub disconnected")
end
screen:invalidate()
end
function TI_InnovatorConfig() -- this function is called from TI_InnovatorStateCallback() when a ready connection is succesful.
-- place CONNECT and other Hub startup commands here
addMsg("TI_Innovator Hub connected")
on.resize()
TI_Innovator.Send("CONNECT SERVO 1 BB9")
TI_Innovator.Send("CONNECT SERVO 2 BB10")
end
function wait(input)
if input then delay = input end
time_step = 0.1
if delay >= 0 then
timeStr = "Wait Time: "..delay.." seconds"
delay = math.floor((1/time_step)*(delay - time_step)+0.5)*time_step
TI_Innovator.Send(hubStrOn)
if hubStrOn:find("READ") then TI_Innovator.Read() end
if tonumber(rxValue) then -- This allows our wait function to serve as a timed data collection tool
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
oneShotTimer(1000*time_step,wait)
else
TI_Innovator.Send(hubStrOff)
delay = 0
end
screen:invalidate()
end
function blink(rep,waitValue)
local lightState = "OFF"
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "blink("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then lightState = "ON" else lightState = "OFF" end
TI_Innovator.Send("SET LIGHT "..lightState)
oneShotTimer(1000*delay,blink)
repeats = repeats - 0.5
end
screen:invalidate()
end
function alarm(rep,waitValue)
local tone = 220
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "alarm("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then tone = 440 else tone = 220 end
TI_Innovator.Send("SET SOUND "..tone)
oneShotTimer(1000*delay,alarm)
repeats = repeats - 0.5
end
screen:invalidate()
end
function waitTone()
if not list1 then list1 = var.recall("list1") end
if not list2 then list2 = var.recall("list2") end
if list1 and #list1 > 0 and list2 and #list2 > 0 then
if I <= #list1 and list1[I] and list2[I] then -- don't go past the lenght of the array
if list1[I] and list2[I] then
local str = "SET SOUND "..list1[I]*2^(key/12).." TIME "..math.floor(100/(list2[I])+0.5)/100
hubStrOn = str
TI_Innovator.Send(str)
oneShotTimer((tempo/list2[I])+20, waitTone)
I = I + 1
local ct = currentTimer
if currentTimer == nil then
timerStop()
end
if currentTimer.oneShot then
currentTimer = nil
timerStop()
end
ct.listenerHandler(unpack(ct.params))
screen:invalidate()
end
-- oneShotTimer ENDS
-- TI Innovator BEGINS
TI_Innovator = { }
function TI_Innovator.init(theStateCallback)
if not pcall(function() require 'asi' end) then
addMsg('Hub NOT available')
return
end
addMsg('Hub available')
isASIavailable = true
local HANDSHAKE_GREETING = 'ISTI\n'
local HANDSHAKE_ANSWER = 'TISTEM'
local portFoundList = { }
local state
local handshakeState
local notifyEvent
local asiStateListener
local startScanning
local stopScanning
local portFoundListener
local portStateListener
local handshake_SendListener
local handshake_readListener
local handshake_port
local TI_Innovator_port
local baudRate = asi.BAUD_RATE_DEFAULT
local readTimeout = asi.READ_TIMEOUT_DEFAULT
-- User callbacks
local stateCallback = theStateCallback
local SendCallback
local readCallback
function notifyEvent(event)
addMsg('notifying '.. event)
if stateCallback then
-- Configure port for normal use
TI_Innovator_port = reportedPort
TI_Innovator_port:setReadTimeout(readTimeout)
TI_Innovator.setWriteListener(SendCallback)
TI_Innovator.setReadListener(readCallback)
-- Notify launchpad is ready
notifyEvent('ready')
end
local result = TI_Innovator_port:write(data .. '\n')
if result then addMsg(tostring(data)..': '.. tostring(result))
else addMsg(tostring(data)) end
return result
end
function TI_Innovator.Read(bytes_to_read)
if not TI_Innovator_port then
return "No TI Hub attached"
end
return TI_Innovator_port:read(bytes_to_read)
end
function TI_Innovator.request(request)
if not TI_Innovator_port then
return "No TI Hub attached"
end
local result = TI_Innovator_port:write(request .. '\n')
if result then addMsg(tostring(request) .. ': '.. tostring(result))
else addMsg(tostring(request)) end
result = TI_Innovator_port:read()
addMsg('read() '.. tostring(result))
if result then
local isASIavailable = false
local hubStrOn, hubStrOff, msgStr, rxValue, timeStr, delay, repeats, drive
local tempo, key, I
local list1, list2, index, dataList = {}, {}, {}, {}
var.store("index", index)
var.store("dataList", dataList)
function addMsg(input)
msgStr = input
print(msgStr)
screen:invalidate()
end
function on.construction()
--the next three lines are required for Hub connection
TI_Innovator.init(TI_InnovatorStateCallback)
TI_Innovator.setReadListener(TI_InnovatorReadCallback)
TI_Innovator.connect()
TI_Innovator.Send(hubStrOn) -- This is an example of sending a SET command to the Hub
TI_Innovator.Read() -- This is an example of sending a READ command to the Hub
screen:invalidate()
function str2list(input, def)
local list = {}
if input:find(def) then
list = input:split(def)
table.foreachi(list,print)
else
list = {input}
end
return list
end
function TI_InnovatorReadCallback(port, error_msg) -- this is the callback function that is defined above and catches the result of a READ command send to the Hub.
rxValue = port:getValue() or '' -- this gets the actual return value from the hub and puts it in the variable rxValue. The user may choose any variable name and also may do any calibration or ranging of the value here. Note that rxValue may be numeric, list or string (hence the utility function list2str)
if tonumber(rxValue) then rxValue = math.floor(100*rxValue+0.5)/100
dataList[#dataList+1] = tonumber(rxValue)
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
addMsg(hubStrOn)
if tonumber(rxValue) then voltage = (rxValue/2^14)*3.3 end -- an example of converting a raw 14 bit ADC value into voltage.
screen:invalidate()
end
function TI_InnovatorStateCallback(event)
addMsg("TI_InnovatorStateCallback")
if 'ready' == event then
TI_InnovatorConfig()
elseif "disconnected" == event then
-- user may choose to do some clean up or display a msg when the Hub is disconnected.
addMsg("TI_Innovator Hub disconnected")
end
screen:invalidate()
end
function TI_InnovatorConfig() -- this function is called from TI_InnovatorStateCallback() when a ready connection is succesful.
-- place CONNECT and other Hub startup commands here
addMsg("TI_Innovator Hub connected")
on.resize()
end
function wait(input)
if input then delay = input end
time_step = 0.1
if delay >= 0 then
timeStr = "Wait Time: "..delay.." seconds"
delay = math.floor((1/time_step)*(delay - time_step)+0.5)*time_step
TI_Innovator.Send(hubStrOn)
if hubStrOn:find("READ") then TI_Innovator.Read() end
if tonumber(rxValue) then -- This allows our wait function to serve as a timed data collection tool
else rxValue = tostring(rxValue) rxValue = str2list(rxValue,string.char(10))[1] end
oneShotTimer(1000*time_step,wait)
else
TI_Innovator.Send(hubStrOff)
delay = 0
end
screen:invalidate()
end
function blink(rep,waitValue)
local lightState = "OFF"
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "blink("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then lightState = "ON" else lightState = "OFF" end
TI_Innovator.Send("SET LIGHT "..lightState)
oneShotTimer(1000*delay,blink)
repeats = repeats - 0.5
end
screen:invalidate()
end
function alarm(rep,waitValue)
local tone = 220
if waitValue then delay = waitValue end
if rep then repeats = rep end
if repeats > 0 then
timeStr = "alarm("..(math.floor(repeats+0.5))..", "..delay..")"
if math.floor(repeats) == repeats then tone = 440 else tone = 220 end
TI_Innovator.Send("SET SOUND "..tone)
oneShotTimer(1000*delay,alarm)
repeats = repeats - 0.5
end
screen:invalidate()
end
function waitTone()
if not list1 then list1 = var.recall("list1") end
if not list2 then list2 = var.recall("list2") end
if list1 and #list1 > 0 and list2 and #list2 > 0 then
if I <= #list1 and list1[I] and list2[I] then -- don't go past the lenght of the array
if list1[I] and list2[I] then
local str = "SET SOUND "..list1[I]*2^(key/12).." TIME "..math.floor(100/(list2[I])+0.5)/100
hubStrOn = str
TI_Innovator.Send(str)
oneShotTimer((tempo/list2[I])+20, waitTone)
I = I + 1
local ct = currentTimer
if currentTimer == nil then
timerStop()
end
if currentTimer.oneShot then
currentTimer = nil
timerStop()
end
ct.listenerHandler(unpack(ct.params))
screen:invalidate()
end
-- oneShotTimer ENDS
-- TI Innovator BEGINS
TI_Innovator = { }
function TI_Innovator.init(theStateCallback)
if not pcall(function() require 'asi' end) then
addMsg('Hub NOT available')
return
end
addMsg('Hub available')
isASIavailable = true
local HANDSHAKE_GREETING = 'ISTI\n'
local HANDSHAKE_ANSWER = 'TISTEM'
local portFoundList = { }
local state
local handshakeState
local notifyEvent
local asiStateListener
local startScanning
local stopScanning
local portFoundListener
local portStateListener
local handshake_SendListener
local handshake_readListener
local handshake_port
local TI_Innovator_port
local baudRate = asi.BAUD_RATE_DEFAULT
local readTimeout = asi.READ_TIMEOUT_DEFAULT
-- User callbacks
local stateCallback = theStateCallback
local SendCallback
local readCallback
function notifyEvent(event)
addMsg('notifying '.. event)
if stateCallback then
-- Configure port for normal use
TI_Innovator_port = reportedPort
TI_Innovator_port:setReadTimeout(readTimeout)
TI_Innovator.setWriteListener(SendCallback)
TI_Innovator.setReadListener(readCallback)
-- Notify launchpad is ready
notifyEvent('ready')
end
local result = TI_Innovator_port:write(data .. '\n')
if result then addMsg(tostring(data)..': '.. tostring(result))
else addMsg(tostring(data)) end
return result
end
function TI_Innovator.Read(bytes_to_read)
if not TI_Innovator_port then
return "No TI Hub attached"
end
return TI_Innovator_port:read(bytes_to_read)
end
function TI_Innovator.request(request)
if not TI_Innovator_port then
return "No TI Hub attached"
end
local result = TI_Innovator_port:write(request .. '\n')
if result then addMsg(tostring(request) .. ': '.. tostring(result))
else addMsg(tostring(request)) end
result = TI_Innovator_port:read()
addMsg('read() '.. tostring(result))
if result then