Home ← TI-Nspire Scripting HQ ← STEM HQ ← Getting Started with TI LaunchPads ← TI LaunchPad Lesson 7
Lesson 7 - Real world data at your fingertips: Light, Ultrasonic Motion and more...
Use your Chrome browser on Mac, Android or ChromeBook or the LightBlue App for iOS or Android to test this sketch.
Texas Instruments TI-Nspire Scripting Support Page
Download the TI-Nspire LaunchPad document for this lesson
Lesson 7 - Real world data at your fingertips: Light, Ultrasonic Motion and more...
Lesson 8 - Build your own BLE ultrasonic motion detector for under $USD30
Reading Data from Connected Sensors
The last lesson was a good example of writing from a TI Nspire Lua document to the LaunchPad, and having it then perform some response (in that case, sending different tones to a connected speaker).
In this lesson, we see how to read data from various sensors (again using the Grove modules, but the same principles should apply in other examples). We will begin with the Grove Light sensor and the Grove Sound Sensor (both relatively simple examples). We will finish with my favourite, the ultrasonic motion sensor.
Grove Sound Sensor
Grove Ultrasonic Motion Sensor
Hacking the Hub™
Note, in the sketch below the brightness pin used is pin 23 - which is the pin allocated to the Innovator Hub brightness sensor, and pin 30 (used for the ultrasonic ranger) corresponds to the IN1 port (IN2 corresponds to pin 28, and IN3 to pin 26)!
Remember: You can always reflash your Innovator Hub to return it to its original state, using the tools that TI has made available (just follow the Resources tab, and then "Keep your Innovator up to date"). So it is possible to explore coding the Hub in new ways!
TI Innovator™ Hub (with MSP432 LaunchPad)
LaunchPad Sketch: Light (and Sound)
(Copy and paste into Energia)#define LIGHT_SENSOR = 23; /* pin connected to the Light Sensor */
int light = 0;
void setup()
{Serial.begin(9600);
Serial1.begin(9600);
}void loop()
{}
light = analogRead(LIGHT_SENSOR); /* read the value from the sensor */
Serial1.print("L");
Serial1.println(light);Serial.print("Light Intensity: ");
Serial.print(light);
Serial.println(" LUX ");
delay(500);
}Study the LaunchPad sketch carefully and see how simple this actually is!
Values are read from pin number 23 (which is where the light sensor is plugged in) and these are stored in the variable "light". This is printed via BLE with "L" attached to the front, so that it can be recognised at the other end (it is also printed to the Serial monitor in a more readable form).
Now just swap "light" for "sound", plug in your sound sensor, and you can collect sound data just as easily!
Lua Script (copy and paste full script into TI-Nspire Script Editor)
If you are interested in learning more about Lua, then feel free to study this script. It has been written so as to be as generic as possible: whatever is entered into the text box is checked against the string coming in from the BLE/Serial port. The sensor is activated upon pressing the enterKey after the identifying letter ("D", "L" or "S"). The script has been amended to also recognise the text commands "RANGER", "LIGHT" and "SOUND".
platform.apilevel = '2.5'
screen = platform.window
w = screen:width()
h = screen:height()
local date = "160216"pcall(function () require 'bleCentral' end)
require "color"
local nameList = {'HMSoft'}
local bleState = ''
local bleStatus = 'Stand by'
local peripheralName = ''
local myPeripheral = nillocal groveBLE = 'FFE1'
local keyPress = 0
local alert = nil
local myChar = nil
local sensor = nillocal textBox = D2Editor.newRichText()
local boxX, boxY, boxWidth, boxHeight
local fontSize = 12
local button1 = "Scan for BLE"
local button2 = "Reset"-- Layout Functions (resize and paint)
function on.resize()
w = screen:width() or 841end
h = screen:height() or 567pcall(function() ble.addStateListener(listenerCallback) end)
refreshMenu()fontSize = math.floor(h/24 + 0.5)
fontSize = fontSize < 25 and fontSize or 24
fontSize = fontSize > 6 and fontSize or 7
local width, height = 0.4*w, 0.075*h
local spacer = 0.025*w
boxX, boxY, boxWidth, boxHeight = 0.4*w, 0.45*h, 0.2*w, 0.2*htextBox:move(boxX + spacer, boxY + spacer)
textBox:resize(boxWidth - 2*spacer, boxHeight - 2*spacer)
textBox:setFontSize(fontSize)
textBox:setText("")
textBox:setFocus(true)textBox:setTextChangeListener(
function()screen:invalidate()
if textBox:getText() thenend)
local tmp = textBox:getText()else
if tmp and tmp:find(string.char(10)) then
alert = textBox:getText():gsub("RANGER", "D")end
alert = alert:gsub("SOUND", "S")
alert = alert:gsub("LIGHT", "L")
local alert1 = textBox:getText():gsub(string.char(10), '') or ''
textBox:setText(alert1)
alert = nilend
if myChar and alert then myChar:write(alert) end
function on.paint(gc)
w = screen:width() or 841end
h = screen:height() or 567gc:setFont("sansserif", "b", fontSize)
local sw = gc:getStringWidth("send(")
gc:drawString("send (", boxX - 0.01*w - sw, boxY + boxHeight*0.4, "middle")
gc:drawString(")", boxX + boxWidth + 0.025*w, boxY + boxHeight*0.4, "middle")if bleState:find("ON") then gc:setColorRGB(color.blue) else gc:setColorRGB(color.red) end
gc:setFont("sansserif", "b", fontSize-5)
local sw = gc:getStringWidth(bleState.." "..bleStatus)
gc:drawString(bleState.." "..bleStatus, 0.05*w, 0.925*h)if bleStatus:find('Connected') then
gc:setColorRGB(color.blue)else
gc:setColorRGB(color.gray)end
if sensor then
local str = ""else
if textBox:getText() then
if textBox:getText():find("L") or textBox:getText():find("LIGHT") thenend
str = "Light Intensity: "..sensor.." lux"elseif textBox:getText():find("S") or textBox:getText():find("SOUND") then
str = "Sound Intensity: "..sensor.." dB"elseif textBox:getText():find("D") or textBox:getText():find("RANGER") then
str = "Distance: "..sensor.." cm"end
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.4*h, 'middle')
gc:drawString('', 0.5*w - sw/2, 0.4*h, 'middle')end
gc:setColorRGB(color.green)
gc:fillRect(0, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button1)
gc:drawString(button1, 0.25*w - sw/2, 0.05*h, "middle")gc:setColorRGB(color.red)
gc:fillRect(0.5*w, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0.5*w, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button2)
gc:drawString(button2, 0.75*w - sw/2, 0.05*h, "middle")
--Menu, Keyboard and Mouse Functions--------------
function refreshMenu()
Menu={
end{"About",
{" ©2016 Texas Instruments", function() end},},
{" Version "..date, function() end},
{" Contact: steve@compasstech.com.au", function() end},
{"Controls",
{"Scan and Connect", function() peripheralOn() end},},
{"Disconnect", function() peripheralOff() end},
{"Reset", function() reset() end},
{"Select action",
{"Sound Intensity", function() alert = "S" textBox:setText("SOUND") if myChar then myChar:write(alert) end end},}, } toolpalette.register(Menu)
{"Light Intensity", function() alert = "L" textBox:setText("LIGHT") if myChar then myChar:write(alert) end end},
{"Distance", function() alert = "D" textBox:setText("RANGER") if myChar then myChar:write(alert) end end},
{"No reading", function() alert = "0" textBox:setText(alert) if myChar then myChar:write(alert) end end},
{"Button 1", function() alert = "1" textBox:setText(alert) if myChar then myChar:write(alert) end end},
function on.enterKey()
if not bleStatus:find("Connect") thenend
peripheralOn()else
peripheralOff()end
function on.escapeKey()
reset()end
function reset()
endalert = nil
keyPress = 0
sensor = nil
if myChar then myChar:write(0) end
on.resize()
screen:invalidate()function on.mouseUp(x, y)
if x < 0.5*w and y < 0.1*h thenend
on.enterKey()end
if x > 0.5*w and y < 0.1*h then
reset()end
screen:invalidate()
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON thenend
bleState = 'BLE ON'elseif state == ble.OFF then
bleState = 'BLE OFF'elseif state == ble.RESETTING then
bleState = 'BLE RESET'elseif state == ble.UNSUPPORTED then
bleState = 'UNSUPPORTED'end
if scriptError then
print('Error message: BLE not supported')end
screen:invalidate()
function peripheralOn()
bleCentral.startScanning(callbackScan)
bleStatus = 'Scanning'
screen:invalidate()end
function peripheralOff()
bleCentral.stopScanning() if myPeripheral thenend
myPeripheral:disconnect()end
bleStatus = 'Stand by'
peripheralName = ''
reset()
screen:invalidate()
function callbackScan(peripheral)
if peripheral ~= nil thenend
peripheralName = peripheral:getName() or ''end
if peripheralName:find(nameList[1]) then
peripheral:connect(callbackConnect)end
bleStatus = "Connecting to "..nameList[1]
screen:invalidate()
function callbackConnect(peripheral, event)
endif event == bleCentral.CONNECTED then
bleCentral.stopScanning()
bleStatus = "Connected to "..nameList[1]
myPeripheral = peripheral
button1 = "Connected"
peripheral:discoverServices(callbackServices)elseif event == bleCentral.DISCONNECTED then
bleStatus = 'Disconnected'end
peripheralName = ''
button1 = "Scan for BLE"
screen:invalidate()
function callbackServices(peripheral)
endif peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
endlocal services = peripheral:getServices()
for _,service in ipairs(services) do
service:discoverCharacteristics(callbackCharacteristics)
end
screen:invalidate()
-- BLE Specific Functions ---------
endfunction callbackCharacteristics(service)
endlocal characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) doif characteristic:getUUID() == groveBLE thenend
myChar = characteristicend
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)
function callbackCharacteristic(characteristic)
endif characteristic:getUUID() == groveBLE then
endlocal value = characteristic:getValue()
if value then
local groveList = value:split(string.char(10)) or {}end
if groveList and #groveList > 0 then
for g = 1, #groveList doend
groveList[g] = groveList[g]:gsub(" ", "")end
if textBox:getText() then
alert = textBox:getText():gsub('RANGER', 'D')
alert = alert:gsub('LIGHT', 'L')
alert = alert:gsub('SOUND', 'S')
if groveList[g]:find(alert) then
sensor = groveList[g]:gsub(alert, "")else
sensor = sensor:gsub(" ", "")
sensor = tonumber(sensor)
characteristic:write(alert)
screen:invalidate()
break
alert = nilend
sensor = nil
characteristic:write(0)
screen:invalidate()
screen:invalidate()
platform.apilevel = '2.5'
screen = platform.window
w = screen:width()
h = screen:height()
local date = "110216"pcall(function () require 'bleCentral' end)
require "color"
local nameList = {'HMSoft'}
local bleState = ''
local bleStatus = 'Stand by'
local peripheralName = ''
local myPeripheral = nillocal groveBLE = 'FFE1'
local keyPress = 0
local alert = nil
local myChar = nil
local sensor = nillocal textBox = D2Editor.newRichText()
local boxX, boxY, boxWidth, boxHeight
local fontSize = 12
local button1 = "Scan for BLE"
local button2 = "Reset"-- Layout Functions (resize and paint)
function on.resize()
w = screen:width() or 841end
h = screen:height() or 567pcall(function() ble.addStateListener(listenerCallback) end)
refreshMenu()fontSize = math.floor(h/24 + 0.5)
fontSize = fontSize < 25 and fontSize or 24
fontSize = fontSize > 6 and fontSize or 7
local width, height = 0.4*w, 0.075*h
local spacer = 0.025*w
boxX, boxY, boxWidth, boxHeight = 0.4*w, 0.45*h, 0.2*w, 0.2*htextBox:move(boxX + spacer, boxY + spacer)
textBox:resize(boxWidth - 2*spacer, boxHeight - 2*spacer)
textBox:setFontSize(fontSize)
textBox:setText("")
textBox:setFocus(true)textBox:setTextChangeListener(
function()screen:invalidate()
if textBox:getText() thenend)
local tmp = textBox:getText()else
if tmp and tmp:find(string.char(10)) then
alert = textBox:getText():gsub("RANGER", "D")end
alert = alert:gsub("SOUND", "S")
alert = alert:gsub("LIGHT", "L")
local alert1 = textBox:getText():gsub(string.char(10), '') or ''
textBox:setText(alert1)
alert = nilend
if myChar and alert then myChar:write(alert) end
function on.paint(gc)
w = screen:width() or 841end
h = screen:height() or 567gc:setFont("sansserif", "b", fontSize)
local sw = gc:getStringWidth("send(")
gc:drawString("send (", boxX - 0.01*w - sw, boxY + boxHeight*0.35, "middle")
gc:drawString(")", boxX + boxWidth + 0.025*w, boxY + boxHeight*0.35, "middle")if bleState:find("ON") then gc:setColorRGB(color.blue) else gc:setColorRGB(color.red) end
gc:setFont("sansserif", "b", fontSize-5)
local sw = gc:getStringWidth(bleState.." "..bleStatus)
gc:drawString(bleState.." "..bleStatus, 0.05*w, 0.925*h)if bleStatus:find('Connected') then
gc:setColorRGB(color.blue)else
gc:setColorRGB(color.gray)end
if sensor then
local str = ""else
if textBox:getText() then
if textBox:getText():find("L") or textBox:getText():find("LIGHT") thenend
str = "Light Intensity: "..sensor.." lux"elseif textBox:getText():find("S") or textBox:getText():find("SOUND") then
str = "Sound Intensity: "..sensor.." dB"elseif textBox:getText():find("D") or textBox:getText():find("RANGER") then
str = "Distance: "..sensor.." cm"end
local sw = gc:getStringWidth(str)
gc:drawString(str, 0.5*w - sw/2, 0.4*h, 'middle')
gc:drawString('', 0.5*w - sw/2, 0.4*h, 'middle')end
gc:setColorRGB(color.green)
gc:fillRect(0, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button1)
gc:drawString(button1, 0.25*w - sw/2, 0.05*h, "middle")gc:setColorRGB(color.red)
gc:fillRect(0.5*w, 0, 0.5*w, 0.1*h)
gc:setColorRGB(color.black)
gc:drawRect(0.5*w, 0, 0.5*w, 0.1*h)
local sw = gc:getStringWidth(button2)
gc:drawString(button2, 0.75*w - sw/2, 0.05*h, "middle")
--Menu, Keyboard and Mouse Functions--------------
function refreshMenu()
Menu={
end{"About",
{" ©2016 Texas Instruments", function() end},},
{" Version "..date, function() end},
{" Contact: steve@compasstech.com.au", function() end},
{"Controls",
{"Scan and Connect", function() peripheralOn() end},},
{"Disconnect", function() peripheralOff() end},
{"Reset", function() reset() end},
{"Select action",
{"Sound Intensity", function() alert = "S" textBox:setText("SOUND") if myChar then myChar:write(alert) end end},}, } toolpalette.register(Menu)
{"Light Intensity", function() alert = "L" textBox:setText("LIGHT") if myChar then myChar:write(alert) end end},
{"Distance", function() alert = "D" textBox:setText("RANGER") if myChar then myChar:write(alert) end end},
{"No reading", function() alert = "0" textBox:setText(alert) if myChar then myChar:write(alert) end end},
{"Button 1", function() alert = "1" textBox:setText(alert) if myChar then myChar:write(alert) end end},
function on.enterKey()
if not bleStatus:find("Connect") then
peripheralOn()else
peripheralOff()end
end
function on.escapeKey()
reset()
end
function reset()
alert = nil
keyPress = 0
sensor = nil
if myChar then myChar:write(0) end
on.resize()
screen:invalidate()end
function on.mouseUp(x, y)
if x < 0.5*w and y < 0.1*h then
on.enterKey()end
if x > 0.5*w and y < 0.1*h then
reset()end
screen:invalidate()
end
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON then
bleState = 'BLE ON'elseif state == ble.OFF then
bleState = 'BLE OFF'elseif state == ble.RESETTING then
bleState = 'BLE RESET'elseif state == ble.UNSUPPORTED then
bleState = 'UNSUPPORTED'end
if scriptError then
print('Error message: BLE not supported')end
screen:invalidate()
end
function peripheralOn()
bleCentral.startScanning(callbackScan)
bleStatus = 'Scanning'
screen:invalidate()end
function peripheralOff()
bleCentral.stopScanning() if myPeripheral thenend
myPeripheral:disconnect()end
bleStatus = 'Stand by'
peripheralName = ''
reset()
screen:invalidate()
function callbackScan(peripheral)
if peripheral ~= nil thenend
peripheralName = peripheral:getName() or ''end
if peripheralName:find(nameList[1]) then
peripheral:connect(callbackConnect)end
bleStatus = "Connecting to "..nameList[1]
screen:invalidate()
function callbackConnect(peripheral, event)
endif event == bleCentral.CONNECTED then
bleCentral.stopScanning()
bleStatus = "Connected to "..nameList[1]
myPeripheral = peripheral
button1 = "Connected"
peripheral:discoverServices(callbackServices)elseif event == bleCentral.DISCONNECTED then
bleStatus = 'Disconnected'end
peripheralName = ''
button1 = "Scan for BLE"
screen:invalidate()
function callbackServices(peripheral)
if peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
endlocal services = peripheral:getServices()
for _,service in ipairs(services) do
service:discoverCharacteristics(callbackCharacteristics)
end
screen:invalidate()
end
-- BLE Specific Functions ---------
endfunction callbackCharacteristics(service)
endlocal characteristicsList = service:getCharacteristics()
for _,characteristic in ipairs(characteristicsList) doif characteristic:getUUID() == groveBLE thenend
myChar = characteristicend
characteristic:setValueUpdateListener(callbackCharacteristic)
characteristic:setNotify(true)
function callbackCharacteristic(characteristic)
endif characteristic:getUUID() == groveBLE then
endlocal value = characteristic:getValue()
if value then
local groveList = value:split(string.char(10)) or {}end
if groveList and #groveList > 0 then
for g = 1, #groveList doend
groveList[g] = groveList[g]:gsub(" ", "")end
if textBox:getText() then
alert = textBox:getText():gsub('RANGER', 'D')
alert = alert:gsub('LIGHT', 'L')
alert = alert:gsub('SOUND', 'S')
if groveList[g]:find(alert) then
sensor = groveList[g]:gsub(alert, "")else
sensor = sensor:gsub(" ", "")
sensor = tonumber(sensor)
characteristic:write(alert)
screen:invalidate()
break
alert = nilend
sensor = nil
characteristic:write(0)
screen:invalidate()
screen:invalidate()
LaunchPad Sketch: Distance
(Copy and paste into Energia)int ULTRASONIC_PIN = 30; /* pin of the Ultrasonic Ranger */
int distance = 0;
int buttonState1 = 0;
void setup() {
pinMode(PUSH1, INPUT_PULLUP);
Serial.begin(9600);
Serial1.begin(9600);
}void loop() {
}buttonState1 = digitalRead(PUSH1);
char recvChar;if(Serial1.available()){
recvChar = Serial1.read();}
if(Serial.available()){
recvChar = Serial.read();}
switch(recvChar) {
case '1':
Serial1.println("P1");
buttonState1 = LOW;
break;case 'D':
buttonState1 = LOW;
break;case '0':
buttonState1 = HIGH;
break;
}if (buttonState1 == LOW) {
pinMode(ULTRASONIC_PIN, OUTPUT);
digitalWrite(ULTRASONIC_PIN, LOW);
delayMicroseconds(2);
digitalWrite(ULTRASONIC_PIN, HIGH);
delayMicroseconds(5);
digitalWrite(ULTRASONIC_PIN,LOW);
pinMode(ULTRASONIC_PIN,INPUT);
long duration;
duration = pulseIn(ULTRASONIC_PIN,HIGH);
long RangeInCentimeters;
RangeInCentimeters = duration/29/2;distance = RangeInCentimeters; /* read the value from the sensor */
Serial1.print("D");
Serial1.println(distance);
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" cm ");
}
Some interesting differences in this script from the previous approach.
You will notice that the PUSH1 button is used here: instead of constantly collecting data from the connected sensor, this only occurs when that sensor is triggered. On the LaunchPad, pressing the PUSH1 button will activate data collection - open the Serial Monitor to see this.
From the TI-Nspire iPad App, this occurs when "D" is entered in the textbox.
For free options to test and explore our BLE code, we can use the LightBlue App for iOS or Android, or your Chrome browser on Mac, Android or ChromeBooks.
Now look closely at what happens when PUSH1 is pressed. While the light and sound sensors simply take whatever is read from that pin, the ultrasonic motion sensor requires a bit more work to turn the variations in voltage at that pin into a measurement in centimetres (reasonably accurate from 0 to 400 cm).
Note that these distance readings are digital, not analog like the previous sensors. As you might expect, digital tends to be wither ON or OFF, HIGH or LOW, while analog supports the variations that lead directly to values, like those for sound and light.
It is worth noting at this point that, while we used digitalWrite in earlier lessons for switching the various LEDs on and off, we could also use analogWrite and get variations in these colours and their intensities.
So in the case of the ultrasonic sensor, you will see that the sensor is actually turned on and off very quickly, with very short delays between each of these switches. A burst of ultrasonic vibration is sent out, and returns to the sensor, from which the distance is calculated.
If you browse some of the many excellent example sketches provided in Energia, you will find some that refer to the ultrasonic sensor, and you should play with these. You may notice that these sketches begin with a line such as #include "Ultrasonic.h". This calls another sketch (which in turn actually calls other sketches) which is automtically loaded to perform just the calculations that have been made explicit here.
Challenges?
So where to from here?
I would certainly suggest that you try merging the three sketches described here into one, so that you have a nice general sketch for sound, light and distance. I have used PUSH! as the trigger for ultrasonic distance; you choose what you would like to do with the other button.
Then go back and continue building and enhancing your sketch. While there are certainly performance benefits in keeping things simple, there is also much to be gained from having a functional and versatile sketch that can be used with sensors, and LEDs as indicators, and perhaps even varying the tone played as one of your sensors varies - the sound as you walk back and forth would be an interesting experience!
Or (if you are using Chrome browser on Mac, Android or Chromebook, or the WebBLE app on iPad) you might just try this for yourself!
BLE LaunchPad Controls
Helpful Hints...
Make sure your LaunchPad with BLE module and suitable sketch is powered and close by, and tap on the button above to connect. Then try the following either by typing the given commands into the input box below, or scroll down to use the buttons which will automate this process. A log of the session will be stored on the text box opposite.
- LED controls: r (red), g (green), b (blue)
- Numbers such as 110 and 220 will play these tones if a speaker is attached.
- If an HC-SR04 ultrasonic ranger is connected, then h will display the distance. If Grove Rangers are connected, then use d and/or D.
- Use l for Light Intensity and s for Sound Intensity.
- [I2C] Use p for Barometric Pressure ("p") and Temperature ("t"). and use x for 3-axis Accelerometer.
- [Robot] F (Forward), B (Back), L (Left) and R (Right). You might also use A (Auto drive).
- Use n to RESET.
Home ← TI-Nspire Scripting HQ ← STEM HQ ← Getting Started with TI LaunchPads ← TI LaunchPad Lesson 7