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.
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 = nil
local groveBLE = 'FFE1' local keyPress = 0 local alert = nil local myChar = nil local sensor = nil
local 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 841 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*h
textBox: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() endif textBox:getText() thenend)local tmp = textBox:getText() if tmp and tmp:find(string.char(10)) thenelsealert = textBox:getText():gsub("RANGER", "D") alert = alert:gsub("SOUND", "S") alert = alert:gsub("LIGHT", "L") local alert1 = textBox:getText():gsub(string.char(10), '') or '' textBox:setText(alert1)endalert = nilend if myChar and alert then myChar:write(alert) endfunction on.paint(gc)
w = screen:width() or 841 h = screen:height() or 567endgc: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)elsegc:setColorRGB(color.gray)endif sensor then
local str = "" if textBox:getText() thenelseif textBox:getText():find("L") or textBox:getText():find("LIGHT") thenend local sw = gc:getStringWidth(str) gc:drawString(str, 0.5*w - sw/2, 0.4*h, 'middle')str = "Light Intensity: "..sensor.." lux"elseif textBox:getText():find("S") or textBox:getText():find("SOUND") thenstr = "Sound Intensity: "..sensor.." dB"elseif textBox:getText():find("D") or textBox:getText():find("RANGER") thenstr = "Distance: "..sensor.." cm"endgc:drawString('', 0.5*w - sw/2, 0.4*h, 'middle')endgc: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}, {"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},}, } toolpalette.register(Menu)function on.enterKey()
if not bleStatus:find("Connect") thenendperipheralOn()elseperipheralOff()endfunction on.escapeKey()
reset()endfunction 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 thenendon.enterKey()end if x > 0.5*w and y < 0.1*h thenreset()end screen:invalidate()
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON thenendbleState = 'BLE ON'elseif state == ble.OFF thenbleState = 'BLE OFF'elseif state == ble.RESETTING thenbleState = 'BLE RESET'elseif state == ble.UNSUPPORTED thenbleState = 'UNSUPPORTED' if scriptError thenend screen:invalidate()print('Error message: BLE not supported')endfunction peripheralOn()
bleCentral.startScanning(callbackScan) bleStatus = 'Scanning' screen:invalidate()
end
function peripheralOff()
bleCentral.stopScanning() if myPeripheral thenendmyPeripheral:disconnect()end bleStatus = 'Stand by' peripheralName = '' reset() screen:invalidate()function callbackScan(peripheral)
if peripheral ~= nil thenendperipheralName = peripheral:getName() or '' if peripheralName:find(nameList[1]) thenend screen:invalidate()peripheral:connect(callbackConnect) bleStatus = "Connecting to "..nameList[1]endfunction 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' peripheralName = '' button1 = "Scan for BLE"end screen:invalidate()function callbackServices(peripheral)
endif peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
end screen:invalidate()local services = peripheral:getServices() for _,service in ipairs(services) do service:discoverCharacteristics(callbackCharacteristics) end
-- BLE Specific Functions ---------
endfunction callbackCharacteristics(service)
endlocal characteristicsList = service:getCharacteristics() for _,characteristic in ipairs(characteristicsList) do
if characteristic:getUUID() == groveBLE thenendmyChar = characteristic characteristic:setValueUpdateListener(callbackCharacteristic) characteristic:setNotify(true)endfunction callbackCharacteristic(characteristic)
end screen:invalidate()if characteristic:getUUID() == groveBLE then
endlocal value = characteristic:getValue() if value then
local groveList = value:split(string.char(10)) or {} if groveList and #groveList > 0 thenendfor g = 1, #groveList doendgroveList[g] = groveList[g]:gsub(" ", "") 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) thenendsensor = groveList[g]:gsub(alert, "") sensor = sensor:gsub(" ", "") sensor = tonumber(sensor) characteristic:write(alert) screen:invalidate() breakelsealert = nil sensor = nil characteristic:write(0) screen:invalidate()endplatform.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 = nil
local groveBLE = 'FFE1' local keyPress = 0 local alert = nil local myChar = nil local sensor = nil
local textBox = D2Editor.newRichText() local boxX, boxY, boxWidth, boxHeight local fontSize = 12 local button1 = "Scan for BLE" local button2 = "Reset"
-- Layout Functions (resize and paint)endble.addStateListener(listenerCallback) end) refreshMenu()function on.resize()
w = screen:width() or 841 h = screen:height() or 567pcall(function()
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*h
textBox: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() if tmp and tmp:find(string.char(10)) thenelsealert = textBox:getText():gsub("RANGER", "D") alert = alert:gsub("SOUND", "S") alert = alert:gsub("LIGHT", "L") local alert1 = textBox:getText():gsub(string.char(10), '') or '' textBox:setText(alert1)endalert = nilend if myChar and alert then myChar:write(alert) endfunction on.paint(gc)
w = screen:width() or 841 h = screen:height() or 567endgc: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)elsegc:setColorRGB(color.gray)endif sensor then
local str = "" if textBox:getText() thenelseif textBox:getText():find("L") or textBox:getText():find("LIGHT") thenend local sw = gc:getStringWidth(str) gc:drawString(str, 0.5*w - sw/2, 0.4*h, 'middle')str = "Light Intensity: "..sensor.." lux"elseif textBox:getText():find("S") or textBox:getText():find("SOUND") thenstr = "Sound Intensity: "..sensor.." dB"elseif textBox:getText():find("D") or textBox:getText():find("RANGER") thenstr = "Distance: "..sensor.." cm"endgc:drawString('', 0.5*w - sw/2, 0.4*h, 'middle')endgc: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}, {"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},}, } toolpalette.register(Menu)function on.enterKey()
if not bleStatus:find("Connect") then
peripheralOn()elseperipheralOff()endend
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()endif x > 0.5*w and y < 0.1*h then
reset()endscreen:invalidate()
end
-- BLE General Functions -----------
function listenerCallback(state, scriptError)
if state == ble.ON then
bleState = 'BLE ON'elseif state == ble.OFF thenbleState = 'BLE OFF'elseif state == ble.RESETTING thenbleState = 'BLE RESET'elseif state == ble.UNSUPPORTED thenbleState = 'UNSUPPORTED' if scriptError thenendprint('Error message: BLE not supported')endscreen:invalidate()
end
function peripheralOn()
bleCentral.startScanning(callbackScan) bleStatus = 'Scanning' screen:invalidate()
end
function peripheralOff()
bleCentral.stopScanning() if myPeripheral thenendmyPeripheral:disconnect()end bleStatus = 'Stand by' peripheralName = '' reset() screen:invalidate()function callbackScan(peripheral)
if peripheral ~= nil thenendperipheralName = peripheral:getName() or '' if peripheralName:find(nameList[1]) thenend screen:invalidate()peripheral:connect(callbackConnect) bleStatus = "Connecting to "..nameList[1]endfunction 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' peripheralName = '' button1 = "Scan for BLE"end screen:invalidate()function callbackServices(peripheral)
if peripheral ~= nil and peripheral:getState() and peripheral:getState() == bleCentral.CONNECTED then
end screen:invalidate()local services = peripheral:getServices() for _,service in ipairs(services) do service:discoverCharacteristics(callbackCharacteristics) end
end
end end end-- BLE Specific Functions ---------
characteristic:setValueUpdateListener(callbackCharacteristic) characteristic:setNotify(true)function callbackCharacteristics(service)
local characteristicsList = service:getCharacteristics() for _,characteristic in ipairs(characteristicsList) do
if characteristic:getUUID() == groveBLE thenmyChar = characteristicfunction callbackCharacteristic(characteristic)
end screen:invalidate() endif characteristic:getUUID() == groveBLE then
endlocal value = characteristic:getValue() if value then
local groveList = value:split(string.char(10)) or {} if groveList and #groveList > 0 thenendfor g = 1, #groveList doendgroveList[g] = groveList[g]:gsub(" ", "") 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) thenendsensor = groveList[g]:gsub(alert, "") sensor = sensor:gsub(" ", "") sensor = tonumber(sensor) characteristic:write(alert) screen:invalidate() breakelsealert = nil sensor = nil characteristic:write(0) screen:invalidate()end
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