From 0d258335e987c1b32a16e6ffa6a63b18d8272c67 Mon Sep 17 00:00:00 2001 From: 7HAL32 Date: Mon, 29 Aug 2016 15:35:47 +0200 Subject: [PATCH] added pbLua --- examples/tutorial/ConvertXPM.sh | 1 + examples/tutorial/pbLuaAdvancedMotors.lua | 56 +++ examples/tutorial/pbLuaBasicMotors.lua | 248 +++++++++++++ examples/tutorial/pbLuaBlueToothRemote.lua | 285 +++++++++++++++ examples/tutorial/pbLuaBtConsole.lua | 233 ++++++++++++ examples/tutorial/pbLuaCDCConnect.png | Bin 0 -> 1073 bytes examples/tutorial/pbLuaCDCConnect.xpm | 114 ++++++ examples/tutorial/pbLuaCDCInit.png | Bin 0 -> 985 bytes examples/tutorial/pbLuaCDCInit.xpm | 114 ++++++ examples/tutorial/pbLuaConsoleChooser.png | Bin 0 -> 1363 bytes examples/tutorial/pbLuaConsoleChooser.xpm | 114 ++++++ examples/tutorial/pbLuaCoroutines.lua | 154 ++++++++ examples/tutorial/pbLuaDatalog.lua | 222 ++++++++++++ examples/tutorial/pbLuaFileSystem.lua | 248 +++++++++++++ examples/tutorial/pbLuaFirstSteps.lua | 131 +++++++ examples/tutorial/pbLuaFloatMath.lua | 192 ++++++++++ examples/tutorial/pbLuaGPS.lua | 377 ++++++++++++++++++++ examples/tutorial/pbLuaGyro.lua | 329 +++++++++++++++++ examples/tutorial/pbLuaHiTechnicCompass.lua | 216 +++++++++++ examples/tutorial/pbLuaHiTechnicProto.lua | 21 ++ examples/tutorial/pbLuaHiTechnicTrike.lua | 199 +++++++++++ examples/tutorial/pbLuaI2C.lua | 123 +++++++ examples/tutorial/pbLuaI2CCommon.lua | 86 +++++ examples/tutorial/pbLuaIRLink.lua | 131 +++++++ examples/tutorial/pbLuaLCDControl.lua | 191 ++++++++++ examples/tutorial/pbLuaLinearActuators.lua | 55 +++ examples/tutorial/pbLuaMemory.lua | 167 +++++++++ examples/tutorial/pbLuaRFIDSensor.lua | 290 +++++++++++++++ examples/tutorial/pbLuaRequire.lua | 223 ++++++++++++ examples/tutorial/pbLuaSensors.lua | 303 ++++++++++++++++ examples/tutorial/pbLuaSerialFlash.lua | 68 ++++ examples/tutorial/pbLuaStartupMain.png | Bin 0 -> 1189 bytes examples/tutorial/pbLuaStartupMain.xpm | 114 ++++++ examples/tutorial/pbLuaVersion.png | Bin 0 -> 852 bytes examples/tutorial/pbLuaVersion.xpm | 114 ++++++ examples/tutorial/pbLuaXmodem.lua | 146 ++++++++ pbLua.rfw | Bin 0 -> 136136 bytes usbDriver/pbLanguages.inf | 32 ++ 38 files changed, 5297 insertions(+) create mode 100644 examples/tutorial/ConvertXPM.sh create mode 100644 examples/tutorial/pbLuaAdvancedMotors.lua create mode 100644 examples/tutorial/pbLuaBasicMotors.lua create mode 100644 examples/tutorial/pbLuaBlueToothRemote.lua create mode 100644 examples/tutorial/pbLuaBtConsole.lua create mode 100644 examples/tutorial/pbLuaCDCConnect.png create mode 100644 examples/tutorial/pbLuaCDCConnect.xpm create mode 100644 examples/tutorial/pbLuaCDCInit.png create mode 100644 examples/tutorial/pbLuaCDCInit.xpm create mode 100644 examples/tutorial/pbLuaConsoleChooser.png create mode 100644 examples/tutorial/pbLuaConsoleChooser.xpm create mode 100644 examples/tutorial/pbLuaCoroutines.lua create mode 100644 examples/tutorial/pbLuaDatalog.lua create mode 100644 examples/tutorial/pbLuaFileSystem.lua create mode 100644 examples/tutorial/pbLuaFirstSteps.lua create mode 100644 examples/tutorial/pbLuaFloatMath.lua create mode 100644 examples/tutorial/pbLuaGPS.lua create mode 100644 examples/tutorial/pbLuaGyro.lua create mode 100644 examples/tutorial/pbLuaHiTechnicCompass.lua create mode 100644 examples/tutorial/pbLuaHiTechnicProto.lua create mode 100644 examples/tutorial/pbLuaHiTechnicTrike.lua create mode 100644 examples/tutorial/pbLuaI2C.lua create mode 100644 examples/tutorial/pbLuaI2CCommon.lua create mode 100644 examples/tutorial/pbLuaIRLink.lua create mode 100644 examples/tutorial/pbLuaLCDControl.lua create mode 100644 examples/tutorial/pbLuaLinearActuators.lua create mode 100644 examples/tutorial/pbLuaMemory.lua create mode 100644 examples/tutorial/pbLuaRFIDSensor.lua create mode 100644 examples/tutorial/pbLuaRequire.lua create mode 100644 examples/tutorial/pbLuaSensors.lua create mode 100644 examples/tutorial/pbLuaSerialFlash.lua create mode 100644 examples/tutorial/pbLuaStartupMain.png create mode 100644 examples/tutorial/pbLuaStartupMain.xpm create mode 100644 examples/tutorial/pbLuaVersion.png create mode 100644 examples/tutorial/pbLuaVersion.xpm create mode 100644 examples/tutorial/pbLuaXmodem.lua create mode 100755 pbLua.rfw create mode 100644 usbDriver/pbLanguages.inf diff --git a/examples/tutorial/ConvertXPM.sh b/examples/tutorial/ConvertXPM.sh new file mode 100644 index 0000000..b65416c --- /dev/null +++ b/examples/tutorial/ConvertXPM.sh @@ -0,0 +1 @@ +mogrify -format png -rotate 270 -scale 200% *.xpm diff --git a/examples/tutorial/pbLuaAdvancedMotors.lua b/examples/tutorial/pbLuaAdvancedMotors.lua new file mode 100644 index 0000000..ae4d896 --- /dev/null +++ b/examples/tutorial/pbLuaAdvancedMotors.lua @@ -0,0 +1,56 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaAdvancedMotors.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Read tachos until the orange button is pressed +function TachoRead(port) + + repeat + if 4 == nxt.ButtonRead() then + nxt.OutputResetTacho(port,1,0,0) + elseif 2 == nxt.ButtonRead() then + nxt.OutputResetTacho(port,0,0,1) + elseif 1 == nxt.ButtonRead() then + nxt.OutputResetTacho(port,0,1,0) + end + + print( nxt.TimerRead(), nxt.OutputGetStatus(port) ) + + t = nxt.TimerRead() + while t+100 > nxt.TimerRead() do + -- nothing + end + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function with a motor plugged into port A... +TachoRead(1) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Turn Motor 1 exactly 180 degrees at half speed +port = 1 +nxt.OutputSetRegulation(port,1,1) +nxt.OutputSetSpeed(port,0x20,50,180) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Turn Motor 1 exactly 180 degrees - and wait until done +port = 1 +function move(degrees) + nxt.OutputSetRegulation(port,1,1) + + _,tacho = nxt.OutputGetStatus(port) + nxt.OutputSetSpeed(port,0x20,nxt.sign(degrees)*50,nxt.abs(degrees)) + + repeat + _,curtacho = nxt.OutputGetStatus(port) + until 4 > nxt.abs( curtacho - (tacho + degrees) ) +end +--codeExampleEnd 3 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaBasicMotors.lua b/examples/tutorial/pbLuaBasicMotors.lua new file mode 100644 index 0000000..44b1053 --- /dev/null +++ b/examples/tutorial/pbLuaBasicMotors.lua @@ -0,0 +1,248 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaBasicMotors.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Read motor port until the orange button is pressed +function MotorRead(port) + + repeat + print( nxt.TimerRead(), nxt.OutputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function with a motor plugged into port A... +MotorRead(1) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Read motor port until the orange button is pressed +-- Left arrow decreases speed +-- Right arrow increases speed +function MotorSpeed(port) + local speed = 0 + + repeat + if 4 == nxt.ButtonRead() then + if speed >= -95 then + speed = speed - 5 + nxt.OutputSetSpeed( port, 32, speed ) + end + end + + if 2 == nxt.ButtonRead() then + if speed <= 95 then + speed = speed + 5 + nxt.OutputSetSpeed( port, 32, speed ) + end + end + + print( nxt.TimerRead(), nxt.OutputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) + + -- Remember to turn the motor off! + nxt.OutputSetSpeed( port, 0, 0 ) +end + +-- And using the function with a motor plugged into port A... +MotorSpeed(1) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Read motor port until the orange button is pressed +-- Left arrow decreases speed +-- Right arrow increases speed +function MotorSpeed(port) + local speed = 0 + local oldButton = 0 + local newButton = 0 + + repeat + newButton = nxt.ButtonRead() + + if 0 == oldButton then + -- Only check buttons if no buttons are pressed! + + if 4 == newButton then + if speed >= -95 then + speed = speed - 5 + nxt.OutputSetSpeed( port, 32, speed ) + end + end + + if 2 == newButton then + if speed <= 95 then + speed = speed + 5 + nxt.OutputSetSpeed( port, 32, speed ) + end + end + end + + oldButton = newButton + + print( nxt.TimerRead(), nxt.OutputGetStatus(port) ) + + until( 8 == newButton ) + + -- Remember to turn the motor off! + nxt.OutputSetSpeed( port, 0, 0 ) +end +-- And using the function with a motor plugged into port A... +MotorSpeed(1) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Read motor port until the orange button is pressed +-- Left arrow decreases speed +-- Right arrow increases speed + +function MotorSpeed(port, state, mode ) + local speed = 0 + local oldButton = 0 + local newButton = 0 + + nxt.OutputSetRegulation( port, state, mode ) + + repeat + newButton = nxt.ButtonRead() + + if 0 == oldButton then + if 4 == newButton then + if speed >= -95 then + speed = speed - 5 + nxt.OutputSetSpeed( port, 32, speed ) + end + end + + if 2 == newButton then + if speed <= 95 then + speed = speed + 5 + nxt.OutputSetSpeed( port, 32, speed ) + end + end + end + + oldButton = newButton + + print( nxt.TimerRead(), nxt.OutputGetStatus(port), nxt.HeapInfo() ) + until( 8 == newButton ) + + -- Remember to turn the motor off! + nxt.OutputSetSpeed( port, 0, 0 ) +end + +-- Now try it out with regulation in float mode... +MotorSpeed( 1, 1, 0 ) + +-- Now try it out with regulation in brake mode... +MotorSpeed( 1, 1, 1 ) + +-- And with no regulation at all... +MotorSpeed( 1, 0, 0 ) +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- Line Follower! +function LineFollow(target,delay,n) + local port = 1 + + nxt.InputSetType(port,5) + + nxt.OutputSetRegulation(1,1,1) + nxt.OutputSetRegulation(3,1,1) + + local idx = 1 + local speed = 0; + + -- initialize the raw array to "grey" + local raw = {} + for i=1,n do + raw[i] = target + end + + repeat + t = nxt.TimerRead() + while t+delay > nxt.TimerRead() do + -- nothing + end + + -- get a new raw reading + raw[(idx%n)+1] = nxt.InputGetStatus(port) + + -- calculate the average + local sum = 0 + for i=1,n do + sum = sum + raw[n] + end + + local avg = sum/n + + print( avg ) + + if avg > target then + speed = 20 + ((avg - target)/2) + if speed > 50 then speed = 50 end + + nxt.OutputSetSpeed(1,0x20,20) + nxt.OutputSetSpeed(3,0x20,speed) + else + speed = 20 + ((target - avg)/2) + if speed > 50 then speed = 50 end + + nxt.OutputSetSpeed(1,0x20,speed) + nxt.OutputSetSpeed(3,0x20,20) + end + + idx = idx + 1 + until( 8 == nxt.ButtonRead() ) + + nxt.OutputSetSpeed(1,0,0) + nxt.OutputSetSpeed(3,0,0) + nxt.InputSetState(port,0,0) +end + +-- And using the function - press the orange button on the NXT to stop it +LineFollow(760,780) +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Sync Motors B (I) and C (II) - the speed is s and the difference is t + +function MotorSync(s,t) + + nxt.OutputResetTacho(2,1,1,1) + nxt.OutputResetTacho(3,1,1,1) + + nxt.OutputSetRegulation(2,2,1) + nxt.OutputSetRegulation(3,2,1) + + nxt.DisableNXT( 1 ); + nxt.OutputSetSpeed(2,0x20,s, 0, t ) + nxt.OutputSetSpeed(3,0x20,s, 0, t ) + nxt.DisableNXT( 0 ); + + repeat + until( 8 == nxt.ButtonRead() ) + + nxt.OutputSetSpeed(2) + nxt.OutputSetSpeed(3) +end +-- And using the function - press the orange button on the NXT to stop it + +-- Motor I and II try to stay sunchronized +MotorSync(75,0) + +-- Motor I turns a bit slower than Motor I +MotorSync(75,20) + +-- Motor I stops - all power goes to Motor I +MotorSync(75,50) + +-- Motor I turns a bit slower than Motor 1 - in the opposite direction +MotorSync(75,60) +--codeExampleEnd 6 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaBlueToothRemote.lua b/examples/tutorial/pbLuaBlueToothRemote.lua new file mode 100644 index 0000000..59b37a7 --- /dev/null +++ b/examples/tutorial/pbLuaBlueToothRemote.lua @@ -0,0 +1,285 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaBlueToothRemote.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Turn the Bluetooth radio on: +nxt.BtPower(1) +--codeExampleEnd 1 + +--codeExampleStart 1a ----------------------------------------------------------- +-- make the NXT visible for Bluetooth searches: +nxt.BtVisible(1) +--codeExampleEnd 1a +-- +--codeExampleStart 2 ----------------------------------------------------------- +-- Reset the Bluetooth subsystem to factory defaults. Note theat this does +-- not reset the firmware, just the device tables. +nxt.BtFactoryReset() +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Change the name of the NXT +nxt.BtSetName("LEFTY") +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Enter the PIN code for your NXT +nxt.BtSetPIN("5551212") +--codeExampleEnd 4 + + +--codeExampleStart 12 ----------------------------------------------------------- +-- Dump the first 4 entries in the device table +btDevice(4) + +-- Results are below, do not paste the following text to the console! +Name: DELLD610 Addr:00:10:c6:62:f6:ba Class:00:02:01:04 Status:2 +Name: BT GPS V10 Addr:00:0a:3a:24:33:97 Class:00:00:1f:00 Status:130 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +--codeExampleEnd 12 + +--codeExampleStart 13 ----------------------------------------------------------- +-- Search for the NXT +nxt.BtSearch(1) -- This takes about 20 seconds! +--codeExampleEnd 13 + +--codeExampleStart 14 ----------------------------------------------------------- +-- Dump the first 4 entries in the device table +btDevice(4) + +-- Results are below, do not paste the following text to the console! +Name: DELLD610 Addr:00:10:c6:62:f6:ba Class:00:02:01:04 Status:66 +Name: BT GPS V10 Addr:00:0a:3a:24:33:97 Class:00:00:1f:00 Status:130 +Name: LEFTY Addr:00:16:53:09:f7:59 Class:00:00:08:04 Status:130 +Name: RIGHTY Addr:00:16:53:09:f9:26 Class:00:00:08:04 Status:65 +--codeExampleEnd 14 + +--codeExampleStart 15 ----------------------------------------------------------- +-- Start the connection on CONTROL between channel 1 and device 3 (RIGHTY) +nxt.BtConnect(1,3) + +-- Wait 5 seconds before entering the PIN on CONTROL +nxt.BtSetPIN("5551212") + +-- Now enter the PIN on RIGHTY +nxt.BtSetPIN("5551212") +--codeExampleEnd 15 + +--codeExampleStart 16 ----------------------------------------------------------- +-- Check the connection table on CONTROL +btConnect() + +-- Results are below, do not paste the following text to the console! +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +Name: RIGHTY Addr:00:16:53:09:f9:26 Class:00:00:08:04 PIN:5551212 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +--codeExampleEnd 16 + +--codeExampleStart 17 ----------------------------------------------------------- +-- Check the device table on CONTROL +btDevice(4) + +-- Results are below, do not paste the following text to the console! +Name: DELLD610 Addr:00:10:c6:62:f6:ba Class:00:02:01:04 Status:2 +Name: BT GPS V10 Addr:00:0a:3a:24:33:97 Class:00:00:1f:00 Status:2 +Name: LEFTY Addr:00:16:53:09:f7:59 Class:00:00:08:04 Status:2 +Name: RIGHTY Addr:00:16:53:09:f9:26 Class:00:00:08:04 Status:2 +--codeExampleEnd 17 + +--codeExampleStart 18 ----------------------------------------------------------- +-- Sit in a loop and spit out anything from the Bluetooth radio to the +-- console...copy this to LEFTY and RIGHT + +function BtListen() + -- Make sure we're reading the raw stream, not NXT messages + nxt.BtStreamMode(1) + + -- Spin in a loop and echo any data we get until the big + -- orange button is pressed. + repeat + s = nxt.BtStreamRecv() + if s then + print( s ) + end + until( 8 == nxt.ButtonRead() ) +end +--codeExampleEnd 18 + +--codeExampleStart 19 ----------------------------------------------------------- +-- A little piece of code to wait until the BT system is idle... +function BtIdleWait() + local active + repeat + _,active = nxt.BtGetStatus() + until 17 == active +end + +-- Set up the connection to LEFTY on channel 1 and device 2 (your device +-- number may be different! +nxt.BtConnect(1,2) + +-- Wait until idle +BtIdleWait() + +-- Set up the connection to RIGHTY on channel 2 and device 3 (your device +-- number may be different! +nxt.BtConnect(2,3) +--codeExampleEnd 19 + +--codeExampleStart 20 ----------------------------------------------------------- +function BtStream() + -- Make sure we're reading the raw stream, not NXT messages + nxt.BtStreamMode(1) + + local i = 0 + + repeat + nxt.BtStreamSend(1,"LEFTY" .. i) + nxt.BtStreamSend(2,"RIGHTY" .. i) + i = i+1 + until( 8 == nxt.ButtonRead() ) +end +--codeExampleEnd 20 + +--codeExampleStart 21 ----------------------------------------------------------- +-- Start listening on LEFTY by typing this in the LEFTY console: +BtListen() + +-- Start listening on RIGHTY by typing this in the RIGHTY console: +BtListen() + +-- Start streaming by typing this in the CONTROL console: +BtStream() +--codeExampleEnd 21 + +--codeExampleStart 22 ----------------------------------------------------------- +-- Copy the BtListen() function into FLASH on LEFTY and RIGHTY and run +-- it at startup + +-- Create the program as an extended string... +s = [[ +function BtListen() + -- Make sure we're reading the raw stream, not NXT messages + nxt.BtStreamMode(1) + + -- Spin in a loop and echo any data we get until the big + -- orange button is pressed. + repeat + s = nxt.BtStreamRecv() + if s then + nxt.DisplayScroll() + nxt.DisplayText(s) + end + until( 8 == nxt.ButtonRead() ) +end + +repeat + BtListen() + + -- Wait for button to be released + repeat + until( 0 == nxt.ButtonRead() ) + + -- Wait for button to be pressed + repeat + until( 8 == nxt.ButtonRead() ) + + -- Wait for button to be released + repeat + until( 0 == nxt.ButtonRead() ) +until false +]] + +-- Create the file, save the handle so we can use it later... +h = nxt.FileCreate( "pbLuaStartup", string.len(s) ) + +-- Now write the string... +nxt.FileWrite( h, s ) + +-- And close the file... +nxt.FileClose( h ) + +-- The next time you boot LEFTY or RIGHTY, this program will run, even +-- if you're not connected to the console +--codeExampleEnd 22 + + +--codeExampleStart 23 ----------------------------------------------------------- +-- Copy the BtStream() function into FLASH on CONTROL and run +-- it at startup + +-- Create the program as an extended string... +s = [[ +function BtIdleWait() + local active + repeat + _,active = nxt.BtGetStatus() + until 17 == active +end + + +function BtStream() + nxt.BtDisconnectAll() + + -- Wait until idle + BtIdleWait() + + -- Set up the connection to LEFTY on channel 1 and device 2 (your device + -- number may be different! + nxt.BtConnect(1,1) + + -- Wait until idle + BtIdleWait() + + -- Set up the connection to RIGHTY on channel 2 and device 3 (your device + -- number may be different! + nxt.BtConnect(2,2) + + -- Wait until idle + BtIdleWait() + + --Make sure we're reading the raw stream, not NXT messages + nxt.BtStreamMode(1) + + local i = 0 + + repeat + nxt.BtStreamSend(1,"LEFTY" .. i) + nxt.BtStreamSend(2,"RIGHTY" .. i) + i = i+1 + until( 8 == nxt.ButtonRead() ) +end + +repeat + BtStream() + + -- Wait for button to be released + repeat + until( 0 == nxt.ButtonRead() ) + + -- Wait for button to be pressed + repeat + until( 8 == nxt.ButtonRead() ) + + -- Wait for button to be released + repeat + until( 0 == nxt.ButtonRead() ) +until false +]] + +-- Create the file, save the handle so we can use it later... +h = nxt.FileCreate( "pbLuaStartup", string.len(s) ) + +-- Now write the string... +nxt.FileWrite( h, s ) + +-- And close the file... +nxt.FileClose( h ) + +-- The next time you boot CONTROL, this program will run, even +-- if you're not connected to the console +--codeExampleEnd 23 + diff --git a/examples/tutorial/pbLuaBtConsole.lua b/examples/tutorial/pbLuaBtConsole.lua new file mode 100644 index 0000000..dc3e2fa --- /dev/null +++ b/examples/tutorial/pbLuaBtConsole.lua @@ -0,0 +1,233 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaBtConsole.html + + +--codeExampleStart BtConsole_1 ----------------------------------------------------------- +> nxt.BtFactoryReset() +--codeExampleEnd BtConsole_1 + +--codeExampleStart BtConsole_2 ----------------------------------------------------------- +> nxt.BtPower(1) +--codeExampleEnd BtConsole_2 + +--codeExampleStart BtConsole_3 ----------------------------------------------------------- +> nxt.BtVisible(1) +--codeExampleEnd BtConsole_3 + +--codeExampleStart BtConsole_4 ----------------------------------------------------------- +> nxt.BtSetName("OGEL") +--codeExampleEnd BtConsole_4 + + +--codeExampleStart Linux_1 ----------------------------------------------------------- +rhempel@debian:~$ dmesg + +[ 3337.783560] usb 1-1: USB disconnect, address 4 +[ 3533.515988] Clocksource tsc unstable (delta = 4686977665 ns) +[ 3668.161395] usb 1-1: new full speed USB device using uhci_hcd and address 6 +[ 3668.403670] usb 1-1: configuration #1 chosen from 1 choice +[ 3668.467956] cdc_acm: This device cannot do calls on its own. It is no modem. +[ 3668.469863] cdc_acm 1-1:1.0: ttyACM0: USB ACM device +--codeExampleEnd Linux_1 + +--codeExampleStart Linux_2 ----------------------------------------------------------- +rhempel@debian:~$ ls /dev/ttyACM* +/dev/ttyACM0 +--codeExampleEnd Linux_2 + +--codeExampleStart Linux_3 ----------------------------------------------------------- +# Machine-generated file - use setup menu in minicom to change parameters. +pu port /dev/ttyACM0 +pu minit +pu mreset +--codeExampleEnd Linux_3 + +--codeExampleStart Linux_4 ----------------------------------------------------------- +rhempel@debian:~$ minicom acm0 +Welcome to minicom 2.3 + +OPTIONS: I18n +Compiled on Oct 24 2008, 06:37:44. +Port /dev/ttyACM0 + + Press CTRL-A Z for help on special keys +> +--codeExampleEnd Linux_4 + +--codeExampleStart Linux_5 ----------------------------------------------------------- +rhempel@debian:~$ sudo apt-get install bluez bluez-hcidump + +Unpacking bluez (from .../archives/bluez_4.57-1_i386.deb) ... +--codeExampleEnd Linux_5 + +--codeExampleStart Linux_6 ----------------------------------------------------------- +-- /etc/default/bluetooth + +HID2HCI_ENABLED=1 +HID2HCI_UNDO=1 +--codeExampleEnd Linux_6 + +----codeExampleStart Linux_7 ----------------------------------------------------------- +-- Only run this if your Bluetooth interface is connected to the USB bus +rhempel@debian:~$ lsusb +Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub +Bus 001 Device 002: ID 050d:016a Belkin Components +Bus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub + +-- Check if your Bluetooth interface is recognized +rhempel@debian:~$ hcitool dev +Devices: + hci0 aa:bb:cc:dd:ee:ff +--codeExampleEnd Linux_7 + +--codeExampleStart Linux_8 ----------------------------------------------------------- +rhempel@debian:~$ sudo hcidump +HCI sniffer - Bluetooth packet analyzer ver 1.42 +device: hci0 snap_len: 1028 filter: 0xffffffff +< HCI Command: Create Connection (0x01|0x0005) plen 13 +> HCI Event: Command Status (0x0f) plen 4 +> HCI Event: Connect Complete (0x03) plen 11 +... +> HCI Event: Link Key Request (0x17) plen 6 +< HCI Command: Link Key Request Reply (0x01|0x000b) plen 22 +> HCI Event: Command Complete (0x0e) plen 10 +> HCI Event: Connect Complete (0x03) plen 11 +< HCI Command: Read Remote Supported Features (0x01|0x001b) plen 2 +< ACL data: handle 11 flags 0x02 dlen 10 + L2CAP(s): Info req: type 2 +> ACL data: handle 11 flags 0x02 dlen 16 + L2CAP(s): Info rsp: type 2 result 0 + Extended feature mask 0x0000 +... +... You get the idea +--codeExampleEnd Linux_8 +-- +--codeExampleStart Linux_9 ----------------------------------------------------------- +rhempel@debian:~$ /usr/sbin/hciconfig +hci0: Type: USB + BD Address: aa:bb:cc:dd:ee:ff ACL MTU: 1021:8 SCO MTU: 64:1 + UP RUNNING PSCAN + RX bytes:2145 acl:0 sco:0 events:82 errors:0 + TX bytes:2040 acl:0 sco:0 commands:82 errors:0 +--codeExampleEnd Linux_9 + +--codeExampleStart Linux_10 ----------------------------------------------------------- +rhempel@debian:~$ hcitool scan +Scanning ... + nn:xx:tt:mm:aa:cc OGEL +--codeExampleEnd Linux_10 + +--codeExampleStart Linux_11 ----------------------------------------------------------- +rhempel@debian:~$ ls /var/lib/bluetooth/aa:bb:cc:dd:ee:ff/ +classes config lastseen names +--codeExampleEnd Linux_11 + +--codeExampleStart Linux_12 ----------------------------------------------------------- +-- /var/lib/bluetooth/aa:bb:cc:dd:ee:ff/pincodes +-- Add a line that looks like this (put your desired pincode instead of 1234): + +nn:xx:tt:mm:aa:cc 1234 +--codeExampleEnd Linux_12 +-- +--codeExampleStart Linux_13 ----------------------------------------------------------- +-- /var/lib/bluetooth/aa:bb:cc:dd:ee:ff/trusts +-- Add a line that looks like this: + +nn:xx:tt:mm:aa:cc [all] +--codeExampleEnd Linux_13 + +--codeExampleStart Linux_14 ----------------------------------------------------------- +/etc/bluetooth/rfcomm.conf + +-- Edit the rfcomm0 section like so: +rfcomm0 { + # Automatically bind the device at startup + bind yes; + + # Bluetooth address of the device + device nn:xx:tt:mm:aa:cc; + + # RFCOMM channel for the connection + channel 1; + + # Description of the connection + comment "OGEL"; +} +--codeExampleEnd Linux_14 + +--codeExampleStart Linux_15 ----------------------------------------------------------- +rhempel@debian:~$ sudo /etc/init.d/bluetooth restart +--codeExampleEnd Linux_15 + +--codeExampleStart Linux_16 ----------------------------------------------------------- +> nxt.BtSetPIN("1234") + +-- Optionally, you can run this little snippet of code that will send the +-- PIN every time you press the orange button... + +function sendPIN() + local oldButton = 0 + local newButton = 0 + + while( 1 ) do + newButton = nxt.ButtonRead() + + if 0 == oldButton then + -- Only check buttons if no buttons were pressed! + + if 8 == newButton then + nxt.BtSetPIN("1234") + end + end + + oldButton = newButton + end +end + +-- And run the function (it's an endless loop - power off the NXT to stop it) +sendPIN() +--codeExampleEnd Linux_16 + +--codeExampleStart Linux_17 ----------------------------------------------------------- +rhempel@debian:~$ sudo rfcomm bind 0 +rhempel@debian:~$ sudo rfcomm connect 0 +Can't create RFCOMM TTY: Address already in use +--codeExampleEnd Linux_17 + +--codeExampleStart Linux_18 ----------------------------------------------------------- +rhempel@debian:~$ ls /var/lib/bluetooth/aa:bb:cc:dd:ee:ff/ +classes features lastused manufacturers pincodes +config lastseen linkkeys names trusts +--codeExampleEnd Linux_18 + +--codeExampleStart Linux_19 ----------------------------------------------------------- +rhempel@debian:~$ sudo /etc/init.d/bluetooth restart + +-- Verify that rfcomm0 is assigned to OGEL +rhempel@debian:~$ rfcomm +rfcomm0: 00:16:53:04:E8:A3 channel 1 clean +--codeExampleEnd Linux_19 + +--codeExampleStart Linux_20 ----------------------------------------------------------- +-- One last step, create a config for minicom in your home directory +-- called .minirc.OGEL with this as the contents: +# Machine-generated file - use setup menu in minicom to change parameters. +pu port /dev/rfcomm0 +pu minit +pu mreset +--codeExampleEnd Linux_20 + +--codeExampleStart Linux_21 ----------------------------------------------------------- +rhempel@debian:~$ minicom OGEL +Welcome to minicom 2.3 + +OPTIONS: I18n +Compiled on Oct 24 2008, 06:37:44. +Port /dev/rfcomm0 + + Press CTRL-A Z for help on special keys +> +--codeExampleEnd Linux_21 + + diff --git a/examples/tutorial/pbLuaCDCConnect.png b/examples/tutorial/pbLuaCDCConnect.png new file mode 100644 index 0000000000000000000000000000000000000000..0167bff40101d2a657b1ad1312db929d5a3aecc4 GIT binary patch literal 1073 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 literal 0 HcmV?d00001 diff --git a/examples/tutorial/pbLuaConsoleChooser.xpm b/examples/tutorial/pbLuaConsoleChooser.xpm new file mode 100644 index 0000000..f2f16a7 --- /dev/null +++ b/examples/tutorial/pbLuaConsoleChooser.xpm @@ -0,0 +1,114 @@ +/* XPM */ +static char * _xpm[] = { +"72 108 2 1", +" c #77cc88", +"* c #000044", +" ", +" ", +" ", +" ", +" ** ***** * * ********* ", +" * * * * * * * * ** ****** ", +" * * * * * * * ** ****** ", +" * * * * * * * * ** ****** ", +" **** ***** * * ** ** ** ", +" ******** ", +" ** ***** * * ** *** * ", +" * * * * * * ** ** ** ", +" * * * * * ******** ** ** ", +" * * * * * * * ** ** ** ", +" **** ** * * * *** *** ", +" ******** ", +" ** ******* * * * ", +" * * * * * * * * ** ** ", +" * * * * * * * ** ** ", +" * * * * * * * * ** ** ", +" **** * * * * ** * * ", +" ******** ", +" ** ******* * * ***** ******** ", +" * * * * * * ********* ", +" * * * * * ********* ", +" * * * * * * ********* ", +" **** * * * * * ******** ", +" ******** ", +" ** *** ** * ", +" * * * * * ***** ", +" * * * * * ***** ", +" * * * * * ***** ", +" **** *** ** *** * ", +" ******** ", +" ***** ***** ** *** ", +" * * * *** ** ", +" ** * * *** ** ", +" * * * *** ** ", +" **** **** ** *** ", +" ******** ", +" ******* ***** * * * ** ", +" * * * * * * * **** *** ", +" * * * * * * * ***** ** ", +" * * * * * * * ***** ** ", +" ** ** ***** * * *** ", +" ******** ", +" **** *** * ** *** ", +" * * * * * * ** ", +" * * * * * * ** ", +" * * * * * * ** ", +" ***** *** ** ***** ", +" ******** ", +" * ***** ** *** ", +" ****** * * * ** *** ** ", +" * * * * ******** *** ** ", +" * * * * * *** ** ", +" * * * ** *** ", +" ******** ", +" * *** *** ******** ", +" ****** * * * * * * * ***** ", +" * * * * * * * * * ", +" * * * * * * * * ****** ", +" * ** ** ******** ", +" ******** ", +" *** ** *** ", +" * * * * * * * ** ", +" * * ******* * * * ** ", +" * * * * * * ** ", +" *** *** *** ", +" ******** ", +" ***** ", +" * * * ", +" * ******* ", +" * * ", +" **** ", +" ", +" * * ", +" * * * ", +" * * * ", +" * * * ", +" * ", +" ", +" ***** ", +" * * * ", +" * * * ", +" * * * ", +" ***** ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +}; diff --git a/examples/tutorial/pbLuaCoroutines.lua b/examples/tutorial/pbLuaCoroutines.lua new file mode 100644 index 0000000..f54cba8 --- /dev/null +++ b/examples/tutorial/pbLuaCoroutines.lua @@ -0,0 +1,154 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaCoroutines.html + +--codeExampleStart 1 ----------------------------------------------------------- +co = coroutine.create( +function () + for i=1,10 do + print("co",i) + coroutine.yield() + end +end +) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- + +co1 = coroutine.create( +function ( s ) + local t = nxt.TimerRead() + while s do + if t+1000 < nxt.TimerRead() then + t = t+1000 + print("co1", t) + end + + s = coroutine.yield() + end +end +) + +co2 = coroutine.create( +function ( s ) + local t = nxt.TimerRead() + while s do + if t+1500 < nxt.TimerRead() then + t = t+1500 + print("bo2", t) + end + + s = coroutine.yield() + end +end +) + + +repeat + local s = nxt.ButtonRead() +until false == (coroutine.resume(co1, s==0) and coroutine.resume(co2, s==0)) + +--codeExampleEnd 2 + +--codeExampleStart n ----------------------------------------------------------- +-- Using a coroutine to drive a motor back and forth until +-- a button is pressed + +SeeSaw1 = coroutine.create( +function ( port, speed, rot ) + local s = true + + nxt.OutputSetRegulation(port,1,1) + nxt.OutputResetTacho(port,1,1,1) + + while s do + nxt.OutputSetSpeed(port,0x20,speed,rot) + repeat + s = coroutine.yield() + _,_,_,_,_,_,d = nxt.OutputGetStatus( port ) + until d < 3 + speed = -speed + end +end +) + +coroutine.resume(SeeSaw1,1,75,360) +repeat + local s = nxt.ButtonRead() +until false == coroutine.resume(SeeSaw1,s==0) +--codeExampleEnd n + +--codeExampleStart n ----------------------------------------------------------- + + +function TurnWait( port, co ) + local s,v = coroutine.resume( co ) + local _,_,_,_,_,_,d = nxt.OutputGetStatus( port ) + return s, v, (d < 3) +end + +function Flipper( port, speed, rot, co ) + return coroutine.create( function () + nxt.OutputSetRegulation(port,1,1) + nxt.OutputResetTacho(port,1,1,1) + + local s,t,v,d = true,0,0,false + + repeat + while t == 0 do + s,v = coroutine.resume( co ) + t = t + v + end + + t = 0 + nxt.OutputSetSpeed(port,0x20, speed,rot) + repeat + s,v,d = TurnWait( port, co ) + t = t + v + until d + + while t == 0 do + s,v = coroutine.resume( co ) + t = t + v + end + + t = 0 + coroutine.yield(1) + + nxt.OutputSetSpeed(port,0x20,-speed,rot) + repeat + s,v,d = TurnWait( port, co ) + t = t + v + until d + + until s == false + end ) +end + + +function Ticker( n ) + return coroutine.create( function () + local t = nxt.TimerRead() + repeat + t = t + n + repeat + coroutine.yield( 0 ) + until t <= nxt.TimerRead() + print( "Tick" ) + coroutine.yield( 1 ) + until 0~=nxt.ButtonRead() + end ) +end + + +function Clock( co ) + repeat + until false == coroutine.resume( co ) +end + +-- Clock( Ticker( 1000 ) ) +-- Clock( Flipper( 1,75,90, Ticker( 1000 ) ) ) +-- Clock( Flipper( 2,75,90, Flipper( 1,75,90, Ticker( 1000 ) ) ) ) +Clock( Flipper( 3,75,180, Flipper( 2,75,180, Flipper( 1,75,180, Ticker( 1000 ) ) ) ) ) + +--codeExampleEnd n diff --git a/examples/tutorial/pbLuaDatalog.lua b/examples/tutorial/pbLuaDatalog.lua new file mode 100644 index 0000000..b15295b --- /dev/null +++ b/examples/tutorial/pbLuaDatalog.lua @@ -0,0 +1,222 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaDatalog.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Read gyro sensor until the orange button is pressed +function GyroRead(port) + + -- Set up the gyro sensor with low sensitivity + nxt.InputSetType(port,0) + nxt.InputSetState(port,0,0) + nxt.InputSetDir(port,1,1) + + -- Now start reading the sensor and putting out data + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +GyroRead(1) +--codeExampleEnd 1 + +--codeExampleStart 1r ----------------------------------------------------------- +52017 617 0 0 +52019 615 0 0 +52021 617 0 0 +52023 616 0 0 +52025 616 0 0 +52027 617 0 0 +52029 616 0 0 +--codeExampleEnd 1r + +--codeExampleStart 2 ----------------------------------------------------------- +-- Lots of tricks in this function, note the concatenation operator ".." and +-- that the last two results from nxt.InputGetStatus() are simply discarded! + +function sampleString(port) + return nxt.istr(nxt.TimerRead()) .. nxt.istr(nxt.InputGetStatus(port)) +end +--codeExampleEnd 2 + +--codeExampleStart 2s ----------------------------------------------------------- +-- Let's do a few examples first... +=string.byte(nxt.istr(0),1,4) +=string.byte(nxt.istr(255),1,4) +=string.byte(nxt.istr(65535),1,4) +=string.byte(nxt.istr(-1),1,4) + +-- And now use the function to generate the string... +port = 1 +s = sampleString(port) + +-- And dump the results. Can you figure out what the values were? +=string.byte(s,1,8) + +-- It's easy if you do this... + +-- The timestamp is the first 4 bytes (1 to 4) +=nxt.stri(string.sub(s,1,4)) + +-- The value is the second 4 bytes (5 to 8) +=nxt.stri(string.sub(s,5,8)) +--codeExampleEnd 2s + +--codeExampleStart 2r ----------------------------------------------------------- +-- Let's do a few examples first... +> =string.byte(nxt.istr(0),1,4) +0 0 0 0 + +> =string.byte(nxt.istr(255),1,4) +255 0 0 0 + +> =string.byte(nxt.istr(65535),1,4) +255 255 0 0 + +> =string.byte(nxt.istr(-1),1,4) +255 255 255 255 + +-- And now use the function to generate the string... +> port = 1 +> s = sampleString(port) + +-- And dump the results. Can you figure out what the values were? +> =string.byte(s,1,8) +148 26 10 0 106 2 0 0 + +-- It's easy if you do this... + +> -- The timestamp is the first 4 bytes (1 to 4) +> =nxt.stri(string.sub(s,1,4)) +662164 + +-- The value is the second 4 bytes (5 to 8) +> =nxt.stri(string.sub(s,5,8)) +618 +--codeExampleEnd 2r + +--codeExampleStart 3 ----------------------------------------------------------- +-- Function to save 100 timestamped samples to a file as quickly as +-- possible + +function save100 (port) + -- Set up the gyro sensor with low sensitivity + nxt.InputSetType(port,0) + nxt.InputSetState(port,0,0) + nxt.InputSetDir(port,1,1) + + -- Create an 800 byte file, save the handle + local file = nxt.FileCreate("sampleFile", 800) + + -- And now read and save 100 individual samples and timestamps + for i=1,100 do + nxt.FileWrite( file, nxt.istr(nxt.TimerRead()) .. nxt.istr(nxt.InputGetStatus(port)) ) + end + + -- And close the file + nxt.FileClose(file) +end + +-- And run the function on port 1 to try it out... +save100(1) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Function to dump timestamped samples from a file + +function DataDumper() + -- Open the file + file = nxt.FileOpen( "sampleFile" ) + + -- And now read individual samples and timestamps 8 bytes + -- at a time until there are no more + repeat + local s = nxt.FileRead( file, 8 ) + + if s then + local t = nxt.stri(string.sub(s,1,4)) + local v = nxt.stri(string.sub(s,5,8)) + print( string.format( "T:%08i V:%04i", t, v ) ) + end + until nil == s + + -- And close the file + nxt.FileClose(file) +end + +-- And run the function to see how fast the writing went... +DataDumper() + +T:00950217 V:0617 +T:00950221 V:0616 +T:00950224 V:0619 + ... +T:00950574 V:0616 +T:00950578 V:0615 +T:00950581 V:0618 + +-- Yep, that's 100 samples written in 364 msec! +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- Complete data logger example +s = [[ +function DataLogger () + -- Set up the gyro sensor on port 1 with low sensitivity + nxt.InputSetType(1,0) + nxt.InputSetState(1,0,0) + nxt.InputSetDir(1,1,1) + + -- Check to see if the file exists, and if so, erase it... + if nxt.FileExists("sampleFile") then + nxt.FileDelete("sampleFile") + end + + -- Create an 8000 byte file, save the handle + local file = nxt.FileCreate("sampleFile", 8000) + + -- Clear the display + nxt.DisplayClear() + + -- And now read and save up to 100 individual samples and timestamps + for i=1,1000 do + nxt.FileWrite( file, nxt.istr(nxt.TimerRead()) .. nxt.istr(nxt.InputGetStatus(1)) ) + + -- Update the LCD so we can see what's going on... + + nxt.DisplayText( i ) + + -- break out of the loop if the user hits the orange button + if 8 == nxt.ButtonRead() then + break + end + end + + -- And close the file + nxt.FileClose(file) + + -- And turn off the NXT + nxt.PowerDown() +end + +-- Don't forget tot execute it :-) +DataLogger() +]] +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +> =string.len(s) +970 +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +> f=nxt.FileCreate("pbLuaStartup", 1024) +> nxt.FileWrite(f,s) +> nxt.FileClose(f) +--codeExampleEnd 7 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaFileSystem.lua b/examples/tutorial/pbLuaFileSystem.lua new file mode 100644 index 0000000..3485d89 --- /dev/null +++ b/examples/tutorial/pbLuaFileSystem.lua @@ -0,0 +1,248 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaFileSystem.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Format the FLASH file system, erasing all files: +nxt.FileFormat(1) + +-- Format the FLASH file system, moves the file descriptor block to a new +-- location, makes erased descriptors usable, leaves exiting files untouched: +nxt.FileFormat(0) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Return information on the pbLua filesystem +function FileSysInfo() + files, blocks, blockSize = nxt.FileSysInfo() + + print( "Total File Descriptors -> " .. files ) + print( "Total FLASH Blocks -> " .. blocks ) + print( "FLASH Block Size -> " .. blockSize .. " bytes" ) +end +--codeExampleEnd 2 + +--codeExampleStart r2 ----------------------------------------------------------- +-- Print out the FLASH File System Information +FileSysInfo() + +-- Results are below, do not paste the following text to the console! +Total File Descriptors -> 31 +Total FLASH Blocks -> 374 +FLASH Block Size -> 256 bytes +--codeExampleEnd r2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Return information on the pbLua filesystem +function DumpFileDesc(from,to) + + print( "Type Name Block MaxBytes CurBytes CurPtr" ) + + for h=from,to do + print( string.format( "%4i %12s %5i %5i %5i %8i", nxt.FileHandleInfo(h) ) ) + end +end +--codeExampleEnd 3 + +--codeExampleStart r3 ----------------------------------------------------------- +-- Dump all of the file descriptors +DumpFileDesc(0,31) + +-- Results are below, do not paste the following text to the console! +Type Name Block MaxBytes CurBytes CurPtr + 3 pbLuaFileSys 0 512 0 1214976 + 1 ÿÿÿÿÿÿÿÿÿÿÿÿ 65535 65535 0 17991936 + 1 ÿÿÿÿÿÿÿÿÿÿÿÿ 65535 65535 0 17991936 + 1 ÿÿÿÿÿÿÿÿÿÿÿÿ 65535 65535 0 17991936 + ... +--codeExampleEnd r3 + +--codeExampleStart 4 ----------------------------------------------------------- +function Hello() + nxt.DisplayClear() + nxt.DisplayText( "Hello, World!" ) +end +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +programString = [[ +nxt.DisplayClear() +nxt.DisplayText( "Hello, World!" ) +]] +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Print the number of bytes in the string: +=string.len( programString ) + +-- Or do it a bit fancier: +print( "programString has " .. string.len( programString ) .. " bytes" ) + +-- Compile the string into a function: +test = loadstring( programString ) + +-- And run it, but clear the display first: +nxt.DisplayClear() +test() +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +programString = [[ +nxt.DisplayClear() +nxt.DisplayText( "Hello, World!" ) +]] + +-- Create the file, save the handle so we can use it later... +h = nxt.FileCreate( "hello", 128 ) + +-- Now write the string... +nxt.FileWrite( h, programString ) + +-- And close the file... +nxt.FileClose( h ) +--codeExampleEnd 7 + +--codeExampleStart r7 ----------------------------------------------------------- +-- Now let's dump the first few file descriptors to see what we get: +DumpFileDesc(0,3) + +-- Results are below, do not paste the following text to the console! +Type Name Block MaxBytes CurBytes CurPtr + 3 pbLuaFileSys 0 512 0 1214976 + 5 hello 2 54 0 1215488 + 1 ÿÿÿÿÿÿÿÿÿÿÿÿ 65535 65535 0 17991936 + 1 ÿÿÿÿÿÿÿÿÿÿÿÿ 65535 65535 0 17991936 + ... +--codeExampleEnd r7 + +--codeExampleStart 8 ----------------------------------------------------------- +-- Read the whole file as a string +h = nxt.FileOpen("hello") +s = nxt.FileRead(h,"*a") +nxt.FileClose(h) + +print( s ) + +-- Results are below, do not paste the following text to the console! +nxt.DisplayClear() +nxt.DisplayText( "Hello, World!" ) +--codeExampleEnd 8 + + +--codeExampleStart 9 ----------------------------------------------------------- +-- Read the whole file a line at a time +h = nxt.FileOpen("hello") +repeat + s = nxt.FileRead(h,"*l") + print( string.len(s or ""), s ) +until nil == s +nxt.FileClose(h) + +-- Results are below, do not paste the following text to the console! +-- +-- Note that the value returned for the last line is nil, we've +-- arranged to have it printed here. Normally you would not print it. +18 nxt.DisplayClear() +34 nxt.DisplayText( "Hello, World!" ) +0 nil:0x0 +--codeExampleEnd 9 + +--codeExampleStart 10 ----------------------------------------------------------- +-- Read the whole file 7 bytes at a time +h = nxt.FileOpen("hello") +repeat + s = nxt.FileRead(h,7) + print( string.len(s or ""), s ) +until nil == s +nxt.FileClose(h) + +-- Results are below, do not paste the following text to the console! +7 nxt.Dis +7 playCle +7 ar() +nx +7 t.Displ +7 ayText( +7 "Hello +7 , World +5 !" ) + +0 nil:0x0 +--codeExampleEnd 10 + +--codeExampleStart 11 ----------------------------------------------------------- +-- Read a file into a function that can be executed +f = nxt.loadfile("hello") + +-- And now run the funtion to see the result +f() +--codeExampleEnd 11 + +--codeExampleStart 12 ----------------------------------------------------------- +-- Read the file and execute it all at once +nxt.dofile("hello") +--codeExampleEnd 12 + +--codeExampleStart 13 ----------------------------------------------------------- +-- Run the file chooser and return the name of the file chosen: +print( nxt.FileChooser("name") ) + +-- Or more simply... +print( nxt.FileChooser() ) +--codeExampleEnd 13 + +--codeExampleStart 13a ----------------------------------------------------------- +-- Run the file chooser and return the contents of the file chosen: +print( nxt.FileChooser("file") ) +--codeExampleEnd 13a + +--codeExampleStart 14 ----------------------------------------------------------- +-- Run the file chooser and load the contents of the file into a function: +=nxt.FileChooser("loadfile") + +-- Whoops, let's put it into a functioon and execute it... +f=nxt.FileChooser("loadfile") +f() +--codeExampleEnd 14 + +--codeExampleStart 15 ----------------------------------------------------------- +-- Run the file chooser and execute the contents of the file +> =nxt.FileChooser("dofile") +--codeExampleEnd 15 + +--codeExampleStart 16 ----------------------------------------------------------- +-- Run the file chooser and execute the contents of the file, but only give the +-- user 5 seconds to make up their mind +> =nxt.FileChooser("dofile",5) +--codeExampleEnd 16 + +--codeExampleStart 17 ----------------------------------------------------------- +programString = [[ +nxt.FileChooser("dofile") + +melody = string.char(0x02, 0x00, 0x04, 0x00, + 0x01, 0x00, 0x04, 0x00, + 0x02, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x01, 0x00) + +while 0 == nxt.SoundMelody( melody, 1 ) do +-- do nothing +end +]] + +h = nxt.FileCreate( "pbLuaStartup", 512 ) + +-- Now write the string... +nxt.FileWrite( h, programString ) + +-- And close the file... +nxt.FileClose( h ) +--codeExampleEnd 17 + + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaFirstSteps.lua b/examples/tutorial/pbLuaFirstSteps.lua new file mode 100644 index 0000000..44cf2be --- /dev/null +++ b/examples/tutorial/pbLuaFirstSteps.lua @@ -0,0 +1,131 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaFirstSteps.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Hello World! for pbLua + +print( "Hello World!" ) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Reading the NXT millisecond timer + +print( nxt.TimerRead() ) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Reading the NXT millisecond timer using the short form of print + +=nxt.TimerRead() +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Reading the NXT millisecond timer and storing it in a variable + +cur = nxt.TimerRead() + +-- And now print the saved result... + +print( cur ) + +-- or equivalently at the console... + +=cur +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- Implementing a simple busy-wait loop + +function BusyWait( n ) + local start, stop + start = nxt.TimerRead() + + repeat + stop = nxt.TimerRead() + until start+n < stop +end + +-- And use it like this: + +function testBusyWait( n ) + print( nxt.TimerRead() ) + BusyWait( n ) + print( nxt.TimerRead() ) +end +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Reading the NXT front panel buttons + +print( nxt.ButtonRead() ) +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +-- Detecting a button press... + +function WaitButtonPress() + repeat + b = nxt.ButtonRead() + until b ~= 0 + + return b, nxt.TimerRead() +end + +-- and using the function + +b,t = WaitButtonPress() + +print( "Button -> " .. b .. " Time -> " .. t ) +--codeExampleEnd 7 + +--codeExampleStart 8 ----------------------------------------------------------- +-- Detecting a button release... + +function WaitButtonRelease() + local b + + repeat + b = nxt.ButtonRead() + until b == 0 + + return b, nxt.TimerRead() +end + +-- A function that combines these operations: + +function TimeButtonPress() + local b,s = WaitButtonPress() + _,e = WaitButtonRelease() + + print( "Button -> " .. b .. " Time -> " .. s ) + print( "Released at Time -> " .. e ) + print( "Total Time Pressed -> " .. e-s ) +end +--codeExampleEnd 8 + +--codeExampleStart 9 ----------------------------------------------------------- +-- Detecting button state... + +function WaitButtonState( s ) + repeat + b = nxt.ButtonRead() + until b == s + + return b, nxt.TimerRead() +end + +-- A function that combines these operations: + +function TimeButtonPress() + b,s = WaitButtonState( 8 ) + _,e = WaitButtonState( 0 ) + + print( "Button -> " .. b .. "Time -> " .. s ) + print( "Released at Time -> " .. e ) + print( "Total Time Pressed -> " .. e-s ) +end +--codeExampleEnd 9 + +--codeExampleStart n +--codeExampleEnd n diff --git a/examples/tutorial/pbLuaFloatMath.lua b/examples/tutorial/pbLuaFloatMath.lua new file mode 100644 index 0000000..9ba0e36 --- /dev/null +++ b/examples/tutorial/pbLuaFloatMath.lua @@ -0,0 +1,192 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaFloatMath.html + +--codeExampleStart 1 ----------------------------------------------------------- +> f=123.456 +tty: stdin:1: malformed number near '123.456' +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +> =nxt.float(123, 456) +123.000457 +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +> =nxt.float(123, 456000) +123.456001 +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +> =nxt.float(-123, 456000) +-122.543998 +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +> =nxt.float(-123, -456000) +-123.456001 +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +> =-nxt.float(123, 456000) +-123.456001 +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +> =nxt.float(123) +123.000000 +--codeExampleEnd 7 + +--codeExampleStart 8 ----------------------------------------------------------- +> =nxt.float() +0.000000 +--codeExampleEnd 8 + +--codeExampleStart 9 ----------------------------------------------------------- +> =nxt.float(32456,9) +32456.000000 +--codeExampleEnd 9 + +--codeExampleStart 10 ----------------------------------------------------------- +> =nxt.float("08056.2984") +8056.298339 +--codeExampleEnd 10 + +--codeExampleStart 10a ----------------------------------------------------------- +> f=nxt.float("08056.2984") +> =nxt.int(f) +8056 298339 +--codeExampleEnd 10a + +--codeExampleStart 11 ----------------------------------------------------------- +> =nxt.float("123.45E17") +1.234496E19 +> =nxt.float("123.45e-10") +1.234500E-8 +--codeExampleEnd 11 + +--codeExampleStart 12 ----------------------------------------------------------- +> =1+2 +3 +--codeExampleEnd 12 + +--codeExampleStart 13 ----------------------------------------------------------- +> ="11"+2 +13 +--codeExampleEnd 13 + +--codeExampleStart 14 ----------------------------------------------------------- +> =1+4.567 +tty: stdin:1: malformed number near '4.567' +> ="123.45"+98 +tty: stdin:1: attempt to perform arithmetic on a string value +--codeExampleEnd 14 + +--codeExampleStart 15 ----------------------------------------------------------- +> f=nxt.float(1) +> =f +1.000000 +> =9+f +10.000000 +--codeExampleEnd 15 + +--codeExampleStart 16 ----------------------------------------------------------- +f=nxt.float(3) +g=nxt.float(4) +=f+g +7.000000 + +=f-g +-1.000000 + +=f*g +12.000000 + +=f/g +0.750000 +--codeExampleEnd 16 + +--codeExampleStart 17 ----------------------------------------------------------- +f=nxt.float(45,0) +=f +45.000000 + +f=nxt.float("45.0") +=f +45.000000 + +=nxt.sin(f) +0.707106 + +=nxt.sin(45) +0.707106 + +=nxt.sin("45") +0.707106 +--codeExampleEnd 17 + +--codeExampleStart 18 ----------------------------------------------------------- +f = nxt.float(49) +g = nxt.float(234) +=nxt.max(f,g) +234.000000 + +=nxt.min(f,g) +49.000000 + +f=49 +g=234 +=nxt.max(f,g) +234 + +=nxt.min(f,g) +49 +--codeExampleEnd 18 + +--codeExampleStart 19 ----------------------------------------------------------- +d = 56 +=nxt.pi()*d +175.929199 +--codeExampleEnd 19 + +--codeExampleStart 20 ----------------------------------------------------------- +d = 56 +t = nxt.TimerRead() +for i=1,10000 do + c = nxt.pi()*d +end +print( "10,000 iterations in ", nxt.TimerRead()-t, " milliseconds " ) +print( "c is ", c ) + +10,000 iterations in 3426 milliseconds +c is 175.929199 +--codeExampleEnd 20 + +--codeExampleStart 21 ----------------------------------------------------------- +=nxt.float(355)/nxt.float(113) +3.141592 + +=nxt.pi() +3.141592 + +=355/113 +3 +--codeExampleEnd 21 + +--codeExampleStart 22 ----------------------------------------------------------- +d = 56 +t = nxt.TimerRead() +for i=1,10000 do + c = (d*355)/113 +end +print( "10,000 iterations in ", nxt.TimerRead()-t, " milliseconds " ) +print( "c is ", c ) + +10,000 iterations in 304 milliseconds +c is 175 +--codeExampleEnd 22 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaGPS.lua b/examples/tutorial/pbLuaGPS.lua new file mode 100644 index 0000000..ba5c043 --- /dev/null +++ b/examples/tutorial/pbLuaGPS.lua @@ -0,0 +1,377 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaGPS.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Turn the Bluetooth radio off: +nxt.BtPower(0) + +-- Turn the Bluetooth radio on: +nxt.BtPower() + +-- or +nxt.BtPower(1) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Reset the Bluetooth subsystem to factory defaults. Note theat this does +-- not reset the firmware, just the device tables. You'll need to do a fresh +-- search of the Bluetooth devices +nxt.BtFactoryReset() + +-- Turn the visibility of the NXT on: +nxt.BtVisible() + +-- Turn the visibility of the NXT off: +nxt.BtVisible(0) + +-- Start searching for other visible Bluetooth devices: +nxt.BtSearch() + +-- Abort the search: +nxt.BtSearch(0) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Turn on the Bluetoorh radio, make the NXT visible and search for +-- other devices +function btIdleWait() + local active + repeat + _,active = nxt.BtGetStatus() + until 17 == active +end + +nxt.BtPower() +btIdleWait() + +nxt.BtVisible() +btIdleWait() + +nxt.BtSearch() -- This takes about 20 seconds! +btIdleWait() + +-- Now you're ready to use the Bluetooth system! +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Turn on the Bluetooth system but make the NXT invisible to discovery +nxt.BtPower() +nxt.BtVisible(0) +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- Make the NXT visible to discovery +nxt.BtVisible() +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Change the name of the NXT +nxt.BtSetName("Frodo") +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +-- Search for the NXT +nxt.BtSearch() -- This takes about 20 seconds! +btIdleWait() +--codeExampleEnd 7 + +--codeExampleStart 8 ----------------------------------------------------------- +-- btMonitor(n) monitors the Bluetooth system for n seconds and +-- prints a message any time there's a change + +function btMonitor( n ) + local t = nxt.TimerRead() + local oldState, oldActive, oldUpdate + + repeat + state, active, update = nxt.BtGetStatus() + if (state ~= oldState) or (active ~= oldActive) or (update ~= oldUpdate) then + print( nxt.TimerRead() - t, state, active, update ) + oldState = state + oldActive = active + oldUpdate = update + end + until t+n < nxt.TimerRead() +end +--codeExampleEnd 8 + +--codeExampleStart 9 ----------------------------------------------------------- +btMonitor(30000) +--codeExampleEnd 9 + +--codeExampleStart 10 ----------------------------------------------------------- +-- Set the PIN in your NXT +nxt.BtSetPIN("yourPIN") -- Put your own PIN in the quotes! +--codeExampleEnd 10 + +--codeExampleStart 11 ----------------------------------------------------------- +-- btDevice(n) dumps the first n entries in the Bluetooth device table + +function btDevice( n ) + for idx=0,n-1 do + name, class, addr, status = nxt.BtGetDeviceEntry( idx ) + + -- Format the BT device address + addr = string.format("%02x:%02x:%02x:%02x:%02x:%02x", string.byte( addr, 1, 6 ) ) + + -- Format the BT class + class = string.format("%02x:%02x:%02x:%02x", string.byte( class, 1, 4 )) + + -- Print the device info + print(string.format("Name:%16s Addr:%s Class:%s Status:%i",name,addr,class,status)) + end +end +--codeExampleEnd 11 + +--codeExampleStart 12 ----------------------------------------------------------- +-- Dump the first 4 entries in the device table +btDevice(4) + +-- Results are below, do not paste the following text to the console! +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +--codeExampleEnd 12 + +--codeExampleStart 13 ----------------------------------------------------------- +-- Initiate a new search for Bluetooth devices +nxt.BtSearch() +btMonitor(30000) + +-- Results are below, do not paste the following text to the console! +0 65 10 3 +13822 65 10 4 +13823 65 10 5 +15630 65 10 4 +15631 65 17 0 +--codeExampleEnd 13 + +--codeExampleStart 14 ----------------------------------------------------------- +-- Dump the first 4 entries in the device table +btDevice(4) + +-- Results are below, do not paste the following text to the console! +Name: Ralph DellD610 Addr:00:10:c6:62:f6:ba Class:00:1c:01:0c Status:65 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +--codeExampleEnd 14 + +--codeExampleStart 15 ----------------------------------------------------------- +-- btConnect() dumps all the connection entries in the Bluetooth connection +-- table +function btConnect() + for idx=0,3 do + name, class, pin, addr, handle, status, linkq = nxt.BtGetConnectEntry( idx ) + + -- Format the BT device address + addr = string.format("%02x:%02x:%02x:%02x:%02x:%02x", string.byte( addr, 1, 6 ) ) + + -- Format the BT class + class = string.format("%02x:%02x:%02x:%02x", string.byte( class, 1, 4 )) + + -- Print the connection info + print(string.format("Name:%16s Addr:%s Class:%s PIN:%s Status:%i",name,addr,class,pin,status)) + end +end +--codeExampleEnd 15 + +--codeExampleStart 16 ----------------------------------------------------------- +-- Summarize the Bluetooth connection table +btConnect() + +-- Results are below, do not paste the following text to the console! +Name: Addr:00:10:c6:62:f6:ba Class:00:00:00:00 PIN:xyzzy Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +--codeExampleEnd 16 + +--codeExampleStart 17 ----------------------------------------------------------- +-- Connect device 0 to channel 0 +nxt.BtConnect(0,0) +--codeExampleEnd 17 + +----codeExampleStart 18 ----------------------------------------------------------- +-- Dump the connection table again +btConnect() + +-- Results are below, do not paste the following text to the console! +Name: Ralph DellD610 Addr:00:10:c6:62:f6:ba Class:f8:7a:01:0c PIN: Status:1 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0 +--codeExampleEnd 18 + +----codeExampleStart 19 ----------------------------------------------------------- +-- Put the NXT Bluetooth into raw streaming mode, and send some text +nxt.BtStreamMode(1) +nxt.BtStreamSend(0,"Hello world!") + +-- And disconnect if we're done... +nxt.BtDisconnect(0) +--codeExampleEnd 19 + +----codeExampleStart 20 ----------------------------------------------------------- +-- Search for the Navibe +nxt.BtSearch() + +-- Wait 20 seconds, then dump the device table to see if we can find it +btDevice(4) + +-- Results are below, do not paste the following text to the console! +Name: Ralph DellD610 Addr:00:10:c6:62:f6:ba Class:f8:7a:01:0c Status:66 +Name: BT GPS V10 Addr:00:0a:3a:24:33:97 Class:00:00:1f:00 Status:65 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +Name: Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0 +--codeExampleEnd 20 + +----codeExampleStart 21 ----------------------------------------------------------- +nxt.BtConnect(1,1) -- Device 1, connection 1 + +-- You may have to also set the PIN for successful connection... +nxt.BtSetPIN("0000") +--codeExampleEnd 21 + +----codeExampleStart 22 ----------------------------------------------------------- +$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06 +$GPRMC,140817.000,V,4433.2983,N,08056.3970,W,1.42,77.42,020707,,,E*51 +$GPGGA,140818.000,4433.2984,N,08056.3964,W,6,00,50.0,168.7,M,-36.0,M,,0000*5C +$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06 +$GPRMC,140818.000,V,4433.2984,N,08056.3964,W,1.42,77.42,020707,,,E*5C +$GPGGA,140819.000,4433.2984,N,08056.3959,W,6,00,50.0,168.7,M,-36.0,M,,0000*53 +$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06 +$GPGSV,3,1,10,18,71,320,30,21,62,184,32,09,46,130,,22,36,290,20*79 +$GPGSV,3,2,10,24,29,154,,26,28,050,22,29,18,052,,03,13,300,*78 +$GPGSV,3,3,10,14,12,233,,19,07,327,*75 +$GPRMC,140819.000,V,4433.2984,N,08056.3959,W,1.42,77.42,020707,,,E*53 +$GPGGA,140820.000,4433.2985,N,08056.3954,W,6,00,50.0,168.7,M,-36.0,M,,0000*55 +$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06 +$GPRMC,140820.000,V,4433.2985,N,08056.3954,W,1.42,77.42,020707,,,E*55 +$GPGGA,140821.000,4433.2985,N,08056.3948,W,6,00,50.0,168.7,M,-36.0,M,,0000*59 +$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06 +--codeExampleEnd 22 + +----codeExampleStart 23 ----------------------------------------------------------- +-- Connect, set to stream mode, and then send a dummy character to +-- get communications running. Remember to have btIdleWait() loaded ! +nxt.BtConnect(1,1) +btIdleWait() +nxt.BtStreamMode(1) +nxt.BtStreamSend(1,"") + +-- Wait a few seconds for BT data to accumulate, then enter: +print(nxt.BtStreamRecv()) + +-- Results are below, do not paste the following text to the console! +$GPRMC,170837.063,A,4433.3037,N,08056.4227,W,3.28,309.27,161108,,,D*70 +$GPGGA,170838.063,4433.3023,N,08056.4205,W,2,03,2 + +-- Now disconnect +nxt.BtDisconnect(1) +--codeExampleEnd 23 + +----codeExampleStart 24 ----------------------------------------------------------- +-- Code to parse out a GPGGA string +function parseGPGGA (s) + print( s ) + _,_,time,lat,ns,long,ew,_,_,_,alt = string.find(s, + "([^,]+),([^,]+),([NS]),([^,]+),([EW]),([^,]+),([^,]+),([^,]+),([^,]+)") + + -- If we have a valid string, then update the NXT display + + if( nil ~= alt ) then + print( time,lat,ns,long,ew,alt ) + nxt.DisplayText( string.format( "Time %s", time), 0, 0 ) + nxt.DisplayText( string.format( "Lat %s%s", lat, ns), 0, 8 ) + nxt.DisplayText( string.format( "Long %s%s", long, ew), 0, 16 ) + nxt.DisplayText( string.format( "Alt %sm", alt), 0, 24 ) + end +end + +-- Main routine that looks for strings from the +function btGPS( timeout ) + local t=nxt.TimerRead() + local s + local gps = "" + + nxt.DisplayClear() + + -- And now loop until we get fully formed GPS messages + while( t+timeout > nxt.TimerRead() ) do + + -- Get and available GPS data from the BT device + s = nxt.BtStreamRecv() + + if s then +-- print( "--" .. s ) + + gps = gps .. s + + start = string.find( gps, "\$GP", 2 ) + + while nil ~= start do + + if start > 1 then + -- Here's where we pull out an entire $GP string + + data = string.sub( gps, 1, start-2 ) + _,_,sentence,data = string.find( data, "\$GP(%u+),(.+)" ) + + -- And check to see if it's one we're interested in + + if "GGA" == sentence then + parseGPGGA( data ) + end + + -- Here's where you can parse other strings... + + -- And now skip over the string we just parsed to get ready for + -- the next one + + gps = string.sub( gps, start, -1 ) + + end + start = string.find( gps, "\$GP", 2 ) + end + end + end +end +--codeExampleEnd 24 + +----codeExampleStart 25 ----------------------------------------------------------- +-- Set up the connection to the Navibe GPS +nxt.BtConnect(1,1) -- Device 1, connection 1 +btIdleWait() + +-- Put the strean into binary mode +nxt.BtStreamMode(1) + +-- Do a dummy send to get the NXT to accept streaming data +nxt.BtStreamSend(1,"") + +-- And now start reading for 5 seconds +btGPS(5000) + +-- Results are below, do not paste the following text to the console! +143115.000 4433.3073 N 08056.3969 W 172.7 +143116.000,433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*65 +143116.000 433.3073 N 08056.3969 W 172.7 +143117.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*64 +143117.000 4433.3073 N 08056.3969 W 172.7 +143118.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*6B +143118.000 4433.3073 N 08056.3969 W 172.7 +143119.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*6A +143119.000 4433.3073 N 08056.3969 W 172.7 +143120.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*60 +143120.000 4433.3073 N 08056.3969 W 172.7 +--codeExampleEnd 25 + +----codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + +----codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + diff --git a/examples/tutorial/pbLuaGyro.lua b/examples/tutorial/pbLuaGyro.lua new file mode 100644 index 0000000..c904339 --- /dev/null +++ b/examples/tutorial/pbLuaGyro.lua @@ -0,0 +1,329 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaGyro.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Initialize the Gyro sensor on a port + +function GyroInit(port,active) + nxt.InputSetType(port,0) + + if 0 == (active or 0) then nxt.InputSetState(port,0,0) + else nxt.InputSetState(port,1,0) + end + + nxt.InputSetDir(port,1,1) +end + +-- Read the Gyro sensor no faster than every 4 msec and +-- print the timestamp and result. Stop when we press the +-- orange button + +function GyroTest(port, timeout) + timeout = timeout or 4 + + local sampletick = 0 + local tick, new + + repeat + tick = nxt.TimerRead() + if tick-sampletick >= timeout then + new = nxt.InputGetStatus(port) + print( tick .. " | " .. new ) + sampletick = tick + end + until( 8 == nxt.ButtonRead() ) + + repeat + -- spin here until the button is released! + until( 0 == nxt.ButtonRead() ) + +end + +-- Inititialize the gyro on port 1 to less sensitive mode and print +-- the samples + +GyroInit(1,0) +GyroTest(1) +--codeExampleEnd 1 + +--codeExampleStart 1r ----------------------------------------------------------- +2279974 617 +2279978 617 +2279983 617 +2279994 617 +2279999 617 +2280003 617 +2280007 620 +2280011 614 +2280017 616 +2280030 617 +2280034 617 +2280038 617 +2280042 615 +2280048 617 +2280053 616 +2280067 617 +2280071 617 +2280075 617 +2280079 617 +2280083 617 +--codeExampleEnd 1r + +--codeExampleStart 2 ----------------------------------------------------------- +-- Read the Gyro sensor no faster than every 4 msec and calculate the +-- running average. Each sample is scaled by 64 and the average is +-- weighted by a factor of 128. + +function GyroZero(port, scale, weight) + local sampletick = 0 + local printtick = 0 + local avg = 0 + local fweight = nxt.float( scale*weight ) + + local tick, new + + repeat + local tick = nxt.TimerRead() + + -- update the average no faster than every 4 msec + if tick-sampletick >= 4 then + new = nxt.InputGetStatus(port)*scale + avg = avg - (avg/weight) + new + sampletick = tick + end + + -- print the average no faster than every 256 msec + if tick-printtick >= 256 then + print( tick, avg, avg/fweight ) + printtick = tick + end + + until( 8 == nxt.ButtonRead() ) + + repeat + -- spin here until the button is released! + until( 0 == nxt.ButtonRead() ) + + return( avg ) +end + +-- Inititialize the gyro in port 1 to less sensitive mode and print +-- the running average until the orange button is pressed + +GyroInit(1,0) +=GyroZero(1,64,128) +--codeExampleEnd 2 + +--codeExampleStart 2r ----------------------------------------------------------- +> =GyroZero(1,64,128) +130430 39488 4.820312 +130686 1845131 225.235717 +130942 3031650 370.074462 +131198 3758822 458.840576 +131454 4230305 516.394653 +131710 4532917 553.334594 +131966 4718822 576.028076 +132222 4837786 590.550048 +132478 4917269 600.252563 +132734 4964312 605.995117 +132990 4995417 609.792114 +133246 5015514 612.245361 +133502 5027208 613.672851 +133758 5034958 614.618896 +134014 5039344 615.154296 +134270 5042426 615.530517 +134526 5044153 615.741333 +134782 5045634 615.922119 +135038 5046739 616.057006 +135294 5046977 616.086059 +135550 5047888 616.197265 +135806 5048128 616.226562 +136062 5047896 616.198242 +136318 5048005 616.211547 +136574 5048054 616.217529 +136830 5048699 616.296264 +137086 5048668 616.292480 +--codeExampleEnd 2r + +--codeExampleStart 3 ----------------------------------------------------------- +-- Read the Gyro sensor no faster than every 32 msec and subtract the scaled +-- zero point returned by a previous call to GyroZero(). + +function GyroRead(port,scale,weight,zero) + local sampletick = 0 + local fweight = nxt.float( scale*weight ) + + local tick, new + + repeat + tick = nxt.TimerRead() + + -- take a new sample no faster than every 32 msec + if tick-sampletick >= 32 then + new = nxt.InputGetStatus(port)*scale*weight - zero + print( tick, new, new/fweight ) + sampletick = tick + end + + until( 8 == nxt.ButtonRead() ) + + repeat + -- spin here until the button is released! + until( 0 == nxt.ButtonRead() ) + +end + +-- Inititialize the gyro in port 1 to less sensitive mode and print +-- the running average until the orange button is pressed, then print +-- the zero compensated output until the orange button is pressed +-- +-- Notice how the output of one function is part of the input to +-- the next one... + +GyroInit(1,0) +GyroRead(1,64,128, GyroZero(1,64,128) ) +--codeExampleEnd 3 + +--codeExampleStart 3r ----------------------------------------------------------- +--codeExampleEnd 3r + +--codeExampleStart 4 ----------------------------------------------------------- +-- Read the Gyro sensor no faster than every 16 msec and subtract the scaled +-- zero point returned by a previous call to GyroZero(). Keep a running total +-- of the sum which is the current angular displacement + +function GyroSum(port,scale,weight,zero,period) + local tick = nxt.TimerRead() + local printtick,sampletick = tick,tick + local old, new, sum = 0,0,0 + local fweight = nxt.float( scale*weight*1000 ) + + repeat + tick = nxt.TimerRead() + + -- take a new sample no faster than every period msec + if tick-sampletick >= period then + new = nxt.InputGetStatus(port)*scale*weight - zero + sum = sum + ((new+old)/2)*(tick-sampletick) + + old = new + sampletick = tick + -- print( tick, new, diff, sum, sum/(scale*weight*1000), speed ) + end + + -- print the sum no faster than every 1000 msec + if tick-printtick >= 1000 then + print( sum, sum/fweight ) + printtick = tick + end + + if 1 == nxt.ButtonRead() then + sum = 0 + end + + until( 8 == nxt.ButtonRead() ) + + repeat + -- spin here until the button is released! + until( 0 == nxt.ButtonRead() ) +end + +-- Inititialize the gyro in port 1 to less sensitive mode and print +-- the running average until the orange button is pressed, then print +-- the zero compensated output and total angular displacement until +-- the orange button is pressed +-- +-- Notice how the output of one function is part of the input to +-- the next one... + +GyroInit(1,0) +GyroSum(1,64,128, GyroZero(1,64,128), 32 ) +--codeExampleEnd 4 + +--codeExampleStart 4r ----------------------------------------------------------- +-- Here's the output of the program after the zero point stabilizes. I'm turning +-- the sensor 90 degrees clockwise in a fixture and the indicated angle is -91 +-- degrees. When I return the sensor to the original position the indicated +-- angle is back to the original -1 degrees + +4981144 0.608049 +3260429 0.398001 +1979037 0.241581 +3651252 0.445709 +101220116 12.355970 +652550725 79.657073 +764644166 93.340354 +707941286 86.418617 +15886016 1.939210 +-894571 -0.109200 +510008 0.062256 +1992112 0.243177 +--codeExampleEnd 4r + +--codeExampleStart 5 ----------------------------------------------------------- +-- Balance the robot + +function GyroBalance(port,scale,weight,zero,period,pid) + local tick = nxt.TimerRead() + local printtick,sampletick = tick,tick + local old, new, sum, diff, speed = 0,0,0,0,0 + local fweight = nxt.float( scale*weight ) + + -- Set the motors on port 1 and port 3 to non-regulated + -- brake mode + nxt.OutputSetRegulation(1,0,1) + nxt.OutputSetRegulation(3,0,1) + + repeat + tick = nxt.TimerRead() + + -- take a new sample no faster than every period msec + if tick-sampletick >= period then + new = nxt.InputGetStatus(port)*scale*weight - zero + diff = new - old + sum = sum + ((new+old)/2)*(tick-sampletick) + + speed = (pid.p*new+pid.d*diff+pid.i*(sum/1000))/(scale*weight*256) + speed = nxt.min(100,speed) + speed = nxt.max(speed,-100) + nxt.OutputSetSpeed(1,0x20,speed) + nxt.OutputSetSpeed(3,0x20,speed) + old = new + sampletick = tick + -- print( tick, new, diff, sum, sum/(scale*weight*1000), speed ) + end + + if 1 == nxt.ButtonRead() then + sum = 0 + end + + until( 8 == nxt.ButtonRead() ) + + -- Don't forget to turn off the motors! + + nxt.OutputSetSpeed(1,0,0) + nxt.OutputSetSpeed(3,0,0) + + repeat + -- spin here until the button is released! + until( 0 == nxt.ButtonRead() ) + +end + +-- Inititialize the gyro in port 1 to less sensitive mode and print +-- the running average until the orange button is pressed, then balance +-- the robot +-- +-- Notice how the output of one function is part of the input to +-- the next one... + +pid = {p=180,i=6000,d=100} +GyroInit(1,0) +GyroBalance(1,16,128, GyroZero(1,16,128),32, pid ) +--codeExampleEnd 5 + +--codeExampleStart nr ----------------------------------------------------------- +--codeExampleEnd nr + + diff --git a/examples/tutorial/pbLuaHiTechnicCompass.lua b/examples/tutorial/pbLuaHiTechnicCompass.lua new file mode 100644 index 0000000..7410806 --- /dev/null +++ b/examples/tutorial/pbLuaHiTechnicCompass.lua @@ -0,0 +1,216 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaHiTechnicCompass.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Read compass sensor in raw mode until the orange button is pressed + +function CompassRead(port) + -- Set up the port and report on the sensor type (I2C address 2) + checkI2C(port,2) + + -- Read and print the results until the orange button is pressed + repeat + -- Read 8 bytes of data from the sensor + nxt.I2CSendData( port, nxt.I2Cdata(2), 4 ) + waitI2C( port ) + s = nxt.I2CRecvData( port, 4 ) + + -- Break the resulting string into 8 bytes and print the results + c1,c2,c3,c4 = string.byte(s,1,8) + print( string.format( "Result: %3i %3i %3i %3i", + c1, c2, c3, c4 ) ) + until( 8 == nxt.ButtonRead() ) +end + +-- Run the test code... +CompassRead(1) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +function Spin(speed) + nxt.OutputSetSpeed(2,32,-speed) + nxt.OutputSetSpeed(3,32, speed) +end + +function Move(speed) + nxt.OutputSetSpeed(2,32, speed) + nxt.OutputSetSpeed(3,32, speed) +end + +function Stop() + nxt.OutputSetSpeed(2) + nxt.OutputSetSpeed(3) +end +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +function RunCompass(port) + -- Set up the port and report on the sensor type + checkI2C(port,2) + + nxt.OutputSetRegulation(2,1,1) + nxt.OutputSetRegulation(3,1,1) + + -- Read and print the results until the orange button is pressed + repeat + -- Read 8 bytes of data from the sensor + nxt.I2CSendData( port, nxt.I2Cdata(2), 4 ) + waitI2C( port ) + s = nxt.I2CRecvData( port, 4 ) + + -- Break the resulting string into 8 bytes and print the results + c1,c2,c3,c4 = string.byte(s,1,8) + print( string.format( "Result: %3i %3i %3i %3i", + c1, c2, c3, c4 ) ) + + angle = c1 + + if angle < 3 then + Spin(0) + elseif angle < 90 then + Spin(-60) + elseif angle < 177 then + Spin( 60) + else + Spin(0) + end + + until( 8 == nxt.ButtonRead() ) + + Stop() +end + +-- And run the test code... +RunCompass(1) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +function RunCompass(port,desired) + -- Initialize the desired direction - remember to divide by two + desired = desired/2 or 0 + + -- Set up the port and report on the sensor type + checkI2C(port,2) + + nxt.OutputSetRegulation(2,1,1) + nxt.OutputSetRegulation(3,1,1) + + -- Read and print the results until the orange button is pressed + repeat + -- Read 8 bytes of data from the sensor + nxt.I2CSendData( port, nxt.I2Cdata(2), 4 ) + waitI2C( port ) + s = nxt.I2CRecvData( port, 4 ) + + -- Break the resulting string into 8 bytes and print the results + c1,c2,c3,c4 = string.byte(s,1,8) + print( string.format( "Result: %3i %3i %3i %3i", + c1, c2, c3, c4 ) ) + + -- Figure out the difference and adjust to a positive value + angle = (c1 - desired + 180) % 180 + + -- angle = c1 + + if angle < 3 then + Spin(0) + elseif angle < 90 then + Spin(-60) + elseif angle < 177 then + Spin( 60) + else + Spin(0) + end + + until( 8 == nxt.ButtonRead() ) + + Stop() +end + +-- And run the test code... +RunCompass(1,90) +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +function RunCompass(port,desired,tolerance) + -- Initialize the desired direction - remember to divide by two + desired = (desired or 0) / 2 + tolerance = (tolerance or 2) / 2 + + -- Set up the port and report on the sensor type + checkI2C(port,2) + + nxt.OutputSetRegulation(2,1,1) + nxt.OutputSetRegulation(3,1,1) + + -- Read and print the results until the orange button is pressed + repeat + -- Read 8 bytes of data from the sensor + nxt.I2CSendData( port, nxt.I2Cdata(2), 4 ) + waitI2C( port ) + s = nxt.I2CRecvData( port, 4 ) + + -- Break the resulting string into 8 bytes and print the results + c1,c2,c3,c4 = string.byte(s,1,8) + print( string.format( "Result: %3i %3i %3i %3i", + c1, c2, c3, c4 ) ) + + -- Figure out the difference and adjust to a positive value + angle = (c1 - desired + 180) % 180 + + if angle < tolerance then + Spin(0) + elseif angle < 90 then + Spin( -(10 + angle) ) + elseif angle < 180-tolerance then + Spin( 10 + 180 - angle ) + else + Spin(0) + end + + until( 8 == nxt.ButtonRead() ) + + Stop() +end + +-- And run the test code... +RunCompass(1,90,2) +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Put the compass in calibrate mode and spin very slowly for 5 seconds + +function CompassCalibrate( port ) + -- Set up the port and report on the sensor type + checkI2C(port,2) + + -- Force the compass into calibrate mode + nxt.I2CSendData( port, string.char( 0x41, 0x43 ), 1 ) + waitI2C( port ) + print( "Calibration Result->" .. string.byte( nxt.I2CRecvData( port, 1 ) ) ) + + -- Force regulation mode so we can get the lowest possible steady speed + nxt.OutputSetRegulation(2,1,1) + nxt.OutputSetRegulation(3,1,1) + + --Now spin slowly for 20 seconds - then stop + Spin( 15 ) + + local t=nxt.TimerRead() + repeat + -- do nothing + until( t+20000 < nxt.TimerRead() ) + + Stop() + + -- Go back to normal mode and check calibration results + nxt.I2CSendData( port, string.char( 0x41, 0x00 ), 1 ) + waitI2C( port ) + print( "Calibration Result->" .. string.byte( nxt.I2CRecvData( port, 1 ) ) ) +end + +-- Running the calibration routine +CompassCalibrate(1) +--codeExampleEnd 6 + diff --git a/examples/tutorial/pbLuaHiTechnicProto.lua b/examples/tutorial/pbLuaHiTechnicProto.lua new file mode 100644 index 0000000..2256117 --- /dev/null +++ b/examples/tutorial/pbLuaHiTechnicProto.lua @@ -0,0 +1,21 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaI2C.html + +--codeExampleStart 1 ----------------------------------------------------------- +function I2CReadProto(port) + -- Read 14 bytes of data from the sensor + nxt.I2CSendData( port, nxt.I2Cdata[2], 14 ) + waitI2C( port ) + s = nxt.I2CRecvData( port, 14 ) + + -- Break the resulting string into 8 bytes and print the results + c = {string.byte(s,1,14)} + + print( string.format( "Result: %3i %3i %3i %3i %3i %3i %3i %3i", + c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8] ) ) +end + +-- And using the function - press the orange button on the NXT to stop it +I2CRead(4) + diff --git a/examples/tutorial/pbLuaHiTechnicTrike.lua b/examples/tutorial/pbLuaHiTechnicTrike.lua new file mode 100644 index 0000000..188fbd1 --- /dev/null +++ b/examples/tutorial/pbLuaHiTechnicTrike.lua @@ -0,0 +1,199 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaHiTechnicTrike.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Simple test code for reading orange switch + +function TestButton() + + print( "Waiting for keypress" ) + + -- Now spin here until someone presses the orange key + repeat + until( 8 == nxt.ButtonRead() ) + + -- And spin here until someone presses the ornage key again + repeat + print( "Running main routine" ) + until( 8 == nxt.ButtonRead() ) + + print( "Done" ) +end + +-- Running the test +TestButton() +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- A better framework for reading the orange switch + +function TestButton() + + print( "Waiting for keypress" ) + + -- Now spin here until someone presses and releases the orange key + local oldKey = 0; + local newKey = 0; + + repeat + oldKey = newKey + newKey = nxt.ButtonRead() + until( oldKey == 8 and newKey == 0 ) + + -- And spin here until someone presses the orange key again + repeat + print( "Running main routine" ) + until( 8 == nxt.ButtonRead() ) + + print( "Done" ) +end + +-- Running the test +TestButton() +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Move the Trike forward for one second...then stop. Note that we're using +-- our orange switch framework more sensibly. You can pass a function to the +-- TestButton() function now! + +function TestTrike( doThis ) + + print( "Waiting for keypress" ) + + -- Now spin here until someone presses and releases the orange key + local oldKey = 0; + local newKey = 0; + + repeat + oldKey = newKey + newKey = nxt.ButtonRead() + until( oldKey == 8 and newKey == 0 ) + + doThis() + + print( "Done" ) +end + +-- Here's the actual test function + +function MoveForward( ) + local ticks = nxt.TimerRead() + + -- Turn the motors on + -- + nxt.OutputSetSpeed(2,32,80) + nxt.OutputSetSpeed(3,32,80) + + -- Stay here for 1 second (1000 ticks) + -- + repeat + -- this space intentionally left blank + until( ticks+1000 < nxt.TimerRead() ) + + -- Turn the motors off + -- + nxt.OutputSetSpeed(2,0,0) + nxt.OutputSetSpeed(3,0,0) +end + +-- Running the test +TestTrike( MoveForward ) +--codeExampleEnd 3 + + +--codeExampleStart 4 ----------------------------------------------------------- +-- A first pass at functions to move the Trike +-- +function TrikeForward( speed ) + nxt.OutputSetSpeed(2,32, speed) + nxt.OutputSetSpeed(3,32, speed) +end + +function TrikeReverse( speed ) + nxt.OutputSetSpeed(2,32,-speed) + nxt.OutputSetSpeed(3,32,-speed) +end + +function TrikeClockwise( speed ) + nxt.OutputSetSpeed(2,32,-speed) + nxt.OutputSetSpeed(3,32, speed) +end + +function TrikeCounterClockwise( speed ) + nxt.OutputSetSpeed(2,32, speed) + nxt.OutputSetSpeed(3,32,-speed) +end + +function TrikeStop() + nxt.OutputSetSpeed(2,0,0) + nxt.OutputSetSpeed(3,0,0) +end +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- Make is so we only have to change one function if the motor port changes + +function MoveTrike( leftSpeed, rightSpeed ) + nxt.OutputSetSpeed(2,32,rightSpeed) + nxt.OutputSetSpeed(3,32,leftSpeed) +end + +function TrikeForward( speed ) + MoveTrike( speed, speed ) +end + +function TrikeReverse( speed ) + MoveTrike(-speed,-speed ) +end + +function TrikeClockwise( speed ) + MoveTrike(-speed, speed ) +end + +function TrikeCounterClockwise( speed ) + MoveTrike( speed,-speed ) +end + +function TrikeStop() + MoveTrike( 0, 0 ) +end +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- New test function, use the framework from example 3 + +function TimedMoves( ) + local ticks = nxt.TimerRead() + + TrikeForward( 80 ) + + repeat + -- this space intentionally left blank + until( ticks+1000 < nxt.TimerRead() ) + + TrikeReverse( 80 ) + + repeat + -- this space intentionally left blank + until( ticks+2000 < nxt.TimerRead() ) + + TrikeClockwise( 80 ) + + repeat + -- this space intentionally left blank + until( ticks+3000 < nxt.TimerRead() ) + + TrikeCounterClockwise( 80 ) + + repeat + -- this space intentionally left blank + until( ticks+4000 < nxt.TimerRead() ) + + TrikeStop( 80 ) +end + +-- Running the test +TestTrike( TimedMoves ) +--codeExampleEnd 6 diff --git a/examples/tutorial/pbLuaI2C.lua b/examples/tutorial/pbLuaI2C.lua new file mode 100644 index 0000000..5bf8a89 --- /dev/null +++ b/examples/tutorial/pbLuaI2C.lua @@ -0,0 +1,123 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaI2C.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Initialize port 4 for I2C communication + + nxt.I2CInitPins(4) + +-- Use a variable to hold the port number + + port = 4 + + nxt.I2CInitPins(port) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- setupI2C() - sets up the specified port to handle an I2C sensor + +function setupI2C(port) + nxt.InputSetType(port,2) + nxt.InputSetDir(port,1,1) + nxt.InputSetState(port,1,1) + + nxt.I2CInitPins(port) +end + +-- Now put the standard I2C messages into the nxt table +nxt.I2Cversion = string.char( 0x02, 0x00 ) +nxt.I2Cproduct = string.char( 0x02, 0x08 ) +nxt.I2Ctype = string.char( 0x02, 0x10 ) +nxt.I2Ccontinuous = string.char( 0x02, 0x41, 0x02 ) +nxt.I2Cdata = string.char( 0x02, 0x42 ) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Use the ultrasound sensor in port 4, make sure the +-- previous sample has been loaded + +setupI2C(4) + +-- Now send the product string, up to 8 bytes can be sent by the sensor + +nxt.I2CSendData( 4, nxt.I2Cproduct, 8 ) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Reading the data is simple. Make sure the previous example has been +-- loaded. The = notation is a short form for "print" + +=nxt.I2CRecvData(4) +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- waitI2C() - sits in a tight loop until the I2C system goes idle + +function waitI2C( port ) + while( 0 ~= nxt.I2CGetStatus( port ) ) do + end +end +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Put it all together in a function that prints out a report of which +-- sensor is connected to a port + +function checkI2C(port) + setupI2C(port) + + nxt.I2CSendData( port, nxt.I2Cversion, 8 ) + waitI2C( port ) + print( "Version -> " .. nxt.I2CRecvData( port, 8 ) ) + + nxt.I2CSendData( port, nxt.I2Cproduct, 8 ) + waitI2C( port ) + print( "Product ID -> " .. nxt.I2CRecvData( port, 8 ) ) + + nxt.I2CSendData( port, nxt.I2Ctype, 8 ) + waitI2C( port ) + print( "SensorType -> " .. nxt.I2CRecvData( port, 8 ) ) +end + +-- And now run the function to see if it recognizes the sensor +checkI2C(4) + +-- Results are below, do not paste the following text to the console! +Version -> V1.0 +Product ID -> LEGO +SensorType -> Sonar +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +function I2CRead(port) + -- Set up the port and report on the sensor type + checkI2C(port) + + -- Put it into continuous sample mode + nxt.I2CSendData( port, nxt.I2Ccontinuous, 0 ) + waitI2C( port ) + + -- Read and print the results until the orange button is pressed + + repeat + -- Read 8 bytes of data from the sensor + nxt.I2CSendData( port, nxt.I2Cdata, 8 ) + waitI2C( port ) + s = nxt.I2CRecvData( port, 8 ) + + -- Break the resulting string into 8 bytes and print the results + c1,c2,c3,c4,c5,c6,c7,c8 = string.byte(s,1,8) + print( string.format( "Result: %3i %3i %3i %3i %3i %3i %3i %3i", + c1, c2, c3, c4, c5, c6, c7, c8 ) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +I2CRead(4) +--codeExampleEnd 7 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaI2CCommon.lua b/examples/tutorial/pbLuaI2CCommon.lua new file mode 100644 index 0000000..c670f25 --- /dev/null +++ b/examples/tutorial/pbLuaI2CCommon.lua @@ -0,0 +1,86 @@ +-- This is the example code file for the tutorials at: +-- +-- www.hempeldesigngroup.com/lego/pblua/tutorial/ +-- +-- These routines are basic helper functions to make dealing with +-- the I2C sensor ports a bit easier. + +--codeExampleStart 1 ----------------------------------------------------------- +-- setupI2C() - sets up the specified port to handle an I2C sensor + +function setupI2C(port) + nxt.InputSetType(port,11) + nxt.I2CInitPins(port) +end + +-- These are functions that return strings that can be used to +-- access any register in any I2C device. All you need to pass +-- in as parameters are the device address and the register you +-- want to start reading from. +-- +function nxt.I2CReadString( devaddr, regaddr ) + return string.char( devaddr, regaddr ) +end + +-- Now we'll add 4 generic helpers to get strings from the standard +-- registers for use with the LEGO MINDSTORMS NXT sensors +-- +function nxt.I2Cversion( devaddr ) + return nxt.I2CReadString( devaddr, 0x00 ) +end + +function nxt.I2Cproduct( devaddr ) + return nxt.I2CReadString( devaddr, 0x08 ) +end + +function nxt.I2Ctype( devaddr ) + return nxt.I2CReadString( devaddr, 0x10 ) +end + +function nxt.I2Cdata( devaddr ) + return nxt.I2CReadString( devaddr, 0x42 ) +end + +-- waitI2C() - sits in a tight loop until the I2C system goes idle, there +-- are better ways to do this, but this is good enough for +-- now + +function waitI2C( port ) + while( 0 ~= nxt.I2CGetStatus( port ) ) do + end +end + +-- Put it all together in a function that prints out a report of which +-- sensor is connected to a port +-- +-- Recall that nxt.I2CSendData() is capable of write/read operation. In +-- other words, if you want to read 8 bytes of data starting at the +-- ProductId field, just send the correct string and specify 8 bytes in +-- the receive data parameter. All nxt.I2CRecvData() does is read the +-- bytes out of the buffer that the nxt.I2CSendData() filled up! + +function checkI2C( port, devaddr ) + setupI2C(port) + + nxt.I2CSendData( port, nxt.I2Cversion( devaddr ), 8 ) + waitI2C( port ) + print( "Version -> " .. nxt.I2CRecvData( port, 8 ) ) + + nxt.I2CSendData( port, nxt.I2Cproduct( devaddr ), 8 ) + waitI2C( port ) + print( "Product ID -> " .. nxt.I2CRecvData( port, 8 ) ) + + nxt.I2CSendData( port, nxt.I2Ctype( devaddr ), 8 ) + waitI2C( port ) + print( "SensorType -> " .. nxt.I2CRecvData( port, 8 ) ) +end + +-- For an I2C device on port 1, all you need to do is: + +checkI2C(1,2) +--codeExampleEnd 1 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaIRLink.lua b/examples/tutorial/pbLuaIRLink.lua new file mode 100644 index 0000000..7bb38ad --- /dev/null +++ b/examples/tutorial/pbLuaIRLink.lua @@ -0,0 +1,131 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbluaIRLink.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- And use it - make sure you specify the port you have plugged the +-- IR Link in to: + +checkI2C(1,2) + +-- Gives these results: + +-- Version -> ýV1.2 +-- Product ID -> HiTechnc +-- SensorType -> IRLink +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Set up the two strings that we'll use alternately. The only difference +-- is the toggle bit. It will send the combo-direct command on channel 0 to +-- turn OutputA onfwd and OutputB float. The mode byte is 2 for PF motors. +-- If the IR signal is lost, the motors will turn off. +-- +-- Note: Output A is the RED connector, and make sure the IR receiver is +-- set to channel 0! + +s0 = nxt.EncodeIR( 0x011, 2 ) +s1 = nxt.EncodeIR( 0x811, 2 ) + +-- And this is the test function + +function IRTest(port) + -- Set up the port and report on the sensor type + checkI2C(port,2) + + repeat + nxt.I2CSendData( port, s0, 0 ) + waitI2C( port ) + nxt.I2CSendData( port, s1, 0 ) + waitI2C( port ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +IRTest(1) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Set up strings for brake, fwd, rev for motor A + +b0 = nxt.EncodeIR( 0x013, 2 ) +b1 = nxt.EncodeIR( 0x813, 2 ) + +f0 = nxt.EncodeIR( 0x011, 2 ) +f1 = nxt.EncodeIR( 0x811, 2 ) + +r0 = nxt.EncodeIR( 0x012, 2 ) +r1 = nxt.EncodeIR( 0x812, 2 ) + +-- And this is the test function + +function IRTest(port) + -- Set up the port and report on the sensor type + checkI2C(port,2) + + repeat + if nxt.ButtonRead() == 2 then -- Set up for FWD + s0, s1 = f0, f1 -- multi-assignment, cool! + elseif nxt.ButtonRead() == 4 then -- Set up for REV + s0, s1 = r0, r1 + else -- Set up for BRAKE + s0, s1 = b0, b1 + end + + nxt.I2CSendData( port, s0, 0 ) + waitI2C( port ) + nxt.I2CSendData( port, s1, 0 ) + waitI2C( port ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +IRTest(1) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Set up strings with toggles for StepFWD, StepRev, Stop for channel 1 + +StepFwd = { nxt.EncodeIR( 0x100, 1 ), nxt.EncodeIR( 0x101, 1 ) } +StepRev = { nxt.EncodeIR( 0x110, 1 ), nxt.EncodeIR( 0x111, 1 ) } +Stop = { nxt.EncodeIR( 0x120, 1 ), nxt.EncodeIR( 0x121, 1 ) } + +-- And this is the test function + +function TrainTest(port) + -- Set up the port and report on the sensor type + checkI2C(port,2) + + local toggle = true + local s = Stop[1] + + repeat + if nxt.ButtonRead() == 2 then -- Set up for FWD + if toggle then s = StepFwd[1] else s = StepFwd[2] end + toggle = not toggle + elseif nxt.ButtonRead() == 4 then -- Set up for REV + if toggle then s = StepRev[1] else s = StepRev[2] end + toggle = not toggle + elseif nxt.ButtonRead() == 1 then -- Set up for Stop + if toggle then s = Stop[1] else s = Stop[2] end + toggle = not toggle + end + + repeat + -- spin here until the button is released! + until( 0 == nxt.ButtonRead() ) + + nxt.I2CSendData( port, s, 0 ) + waitI2C( port ) + + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +TrainTest(1) +--codeExampleEnd 4 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaLCDControl.lua b/examples/tutorial/pbLuaLCDControl.lua new file mode 100644 index 0000000..9060371 --- /dev/null +++ b/examples/tutorial/pbLuaLCDControl.lua @@ -0,0 +1,191 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaLCDControl.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Hello World! for the LCD Display + +nxt.DisplayClear() +nxt.DisplayText( "Hello World!" ) +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Writing text to the LCD + +nxt.DisplayClear() + +nxt.DisplayText( "Line 1", 0, 0 ) +nxt.DisplayText( "Line 2", 4, 8 ) +nxt.DisplayText( "Line 3", 8, 16 ) +nxt.DisplayText( "Line 4", 12, 24 ) +nxt.DisplayText( "Line 5", 16, 32 ) +nxt.DisplayText( "Line 6", 20, 40 ) +nxt.DisplayText( "Line 7", 24, 48 ) +nxt.DisplayText( "Line 8", 28, 56 ) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Writing alternating light and dark text to the LCD + +nxt.DisplayClear() + +nxt.DisplayText( "Line 1", 0, 0, 0 ) +nxt.DisplayText( "Line 2", 4, 8, 1 ) +nxt.DisplayText( "Line 3", 8, 16 ) +nxt.DisplayText( "Line 4", 12, 24, 999 ) +nxt.DisplayText( "Line 5", 16, 32, 0 ) +nxt.DisplayText( "Line 6", 20, 40, -8 ) +nxt.DisplayText( "Line 7", 24, 48, 0 ) +nxt.DisplayText( "Line 8", 28, 56, 1 ) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Scrolling the LCD + +nxt.DisplayClear() +nxt.DisplayText( "Line 1" ) +nxt.DisplayScroll() +nxt.DisplayText( "Line 2" ) +nxt.DisplayScroll() +nxt.DisplayText( "Line 3" ) +nxt.DisplayScroll() +nxt.DisplayText( "Line 4" ) +nxt.DisplayScroll() +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- Demonstration of setting random display pixels + +function RandomDisplayPixel() + local x,y + nxt.DisplayClear() + + for i=1,10000 do + x = nxt.random(100) + y = nxt.random(64) + nxt.DisplayPixel(x,y) + end + +end + +-- Another way of writing this without local variables is + +function RandomDisplayPixel() + nxt.DisplayClear() + + for i=1,10000 do + nxt.DisplayPixel(nxt.random(100),nxt.random(64)) + end + +end + +-- And an even faster way is + +function RandomDisplayPixel() + nxt.DisplayClear() + + local random = nxt.random + local DisplayPixel = nxt.DisplayPixel + + for i=1,10000 do + DisplayPixel(random(100),random(64)) + end + +end +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Reading the Display and Writing it to the Console + +function DumpDisplay() + + -- Set up the header for the XPM file + -- + s = [[ +/* XPM */ +static char * _xpm[] = { +"72 108 2 1", +" c #77cc88", +"* c #000044", ]] + + -- Print the header + -- + print( s ) + + -- Now go ahead and loop through the pixels to print the body of the XPM file + -- + for x=-4,103 do + s = "" + for y=67,-4,-1 do + if nxt.DisplayGetPixel(x,y) then + s = s .. "*" + else + s = s .. " " + end + end + + print( "\"" .. s .. "\"," ) + end + + -- Close off the XPM file + -- + print( "};" ) +end +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +-- main() startup display +nxt.DisplayClear() + +nxt.DisplayText( "Wait for BT", 0, 0, 0 ) +nxt.DisplayText( "Act 11 Upd 01", 0, 48, 0 ) +nxt.DisplayText( "yyyyy Button 0", 0, 56, 0 ) + +DumpDisplay() +-- +--codeExampleEnd 7 + +--codeExampleStart 8 ----------------------------------------------------------- +-- Console Chooser startup display +nxt.DisplayClear() + +nxt.DisplayText( "USB Console", 0, 0, 1 ) +nxt.DisplayText( "BT Console", 0, 8, 0 ) +nxt.DisplayText( "xxxx mV Cells", 0, 24, 0 ) +nxt.DisplayText( "OGEL", 0, 40, 0 ) +nxt.DisplayText( "yyyyy Button 0", 0, 56, 0 ) + +DumpDisplay() +--codeExampleEnd 8 + +--codeExampleStart 9 ----------------------------------------------------------- +-- CDC Init Display +nxt.DisplayClear() + +nxt.DisplayText( "CDC Init", 0, 0, 0 ) +nxt.DisplayText( "yyyyy Button 0", 0, 56, 0 ) + +DumpDisplay() +--codeExampleEnd 9 + +--codeExampleStart 10 ----------------------------------------------------------- +-- CDC Connect Display +nxt.DisplayClear() + +nxt.DisplayText( "CDC Connect", 0, 0, 0 ) +nxt.DisplayText( "00199 Button 0", 0, 56, 0 ) + +DumpDisplay() +--codeExampleEnd 10 + +--codeExampleStart 11 ----------------------------------------------------------- +-- pbLua Version Display +nxt.DisplayClear() + +nxt.DisplayText( "pbLua xx.yy.zz", 0, 0, 0 ) + +DumpDisplay() +--codeExampleEnd 11 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n diff --git a/examples/tutorial/pbLuaLinearActuators.lua b/examples/tutorial/pbLuaLinearActuators.lua new file mode 100644 index 0000000..ebeaaca --- /dev/null +++ b/examples/tutorial/pbLuaLinearActuators.lua @@ -0,0 +1,55 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaLinearActuators.html + +--codeExampleStart 2 ----------------------------------------------------------- +-- Save current Motor position, turn forward while grey button is pressed, +-- then reverse to original position... + +port = 1 +nxt.OutputResetTacho(port,1,1,1) +nxt.OutputSetRegulation(port,1,1) + +function CycleProbe( port, fwd, rev ) + -- Save the starting tacho position + local _,startPos = nxt.OutputGetStatus(port) + + while 0 == nxt.ButtonRead() do + -- Wait for button press + end + + -- Start moving toward the target at half speed + nxt.OutputSetSpeed(port,0x20,fwd) + + while 0 ~= nxt.ButtonRead() do + -- Wait for button release + end + + -- Brake motor now + nxt.OutputSetSpeed(port,0x20,0) + + t = nxt.TimerRead() + while t+400 > nxt.TimerRead() do + -- Let the system settle down + end + + -- Save the current tacho position + local _,endPos = nxt.OutputGetStatus(port) + + print( startPos, endPos, nxt.abs(endPos-startPos) ) + + -- Reverse the motor back to the starting position at full speed + nxt.OutputSetSpeed(port,0x20,-rev,nxt.abs(endPos-startPos)) + + t = nxt.TimerRead() + while t+5000 > nxt.TimerRead() do + -- Let the system settle down + end +end +--codeExampleEnd 2 + + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaMemory.lua b/examples/tutorial/pbLuaMemory.lua new file mode 100644 index 0000000..bf733c4 --- /dev/null +++ b/examples/tutorial/pbLuaMemory.lua @@ -0,0 +1,167 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaMemory.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- gcinfo() returns the approximate memory use in K (1024 byte) multiples +>=gcinfo() +16 +-- This tells us we are using about 16*1024 = 16,384 bytes +-- +-- For a more detailed estimate, use collectgarbage(), like this +-- The first value is the approximate memory use in K (1024 byte) multiples +-- and the second value is the fractional K values (mod 1024) +>=collectgarbage("count") +16 403 +-- This tells us we are using about 16*1024 + 403 = 16,787 bytes +-- +--Use the second form when you need a more accurate estimate of RAM usage +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Basic heap information available from pbLua +>=nxt.HeapInfo() +454 443 11 6728 2434 4294 + +-- This tells us two important things: +-- +-- We have 454 "chunks" of memory in our heap, 443 are in use, and 11 are free +-- +-- We have 6728 "blocks" of memory (each 8 bytes), 2434 are in use, 4294 are free +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +>nxt.HeapInfo(1) + +Dumping the umm_heap... +|0x00201fb8|B 0|NB 1|PB 0|Z 1|NF 3096|PF 0| +|0x00201fc0|B 1|NB 43|PB 0|Z 42| +|0x00202110|B 43|NB 68|PB 1|Z 25| +... +|0x00208118|B 3116|NB 3120|PB 3111|Z 4| +|0x00208138|B 3120|NB 3124|PB 3116|Z 4| +|0x00208158|B 3124|NB 3130|PB 3120|Z 6|NF 3148|PF 3096| +|0x00208188|B 3130|NB 3133|PB 3124|Z 3| +|0x002081a0|B 3133|NB 3144|PB 3130|Z 11| +|0x002081f8|B 3144|NB 3148|PB 3133|Z 4| +|0x00208218|B 3148|NB 3149|PB 3144|Z 1|NF 3070|PF 3124| +|0x00208220|B 3149|NB 0|PB 3148|Z 3580|NF 0|PF 136| +Total Entries 607 Used Entries 563 Free Entries 44 +Total Blocks 6728 Used Blocks 3072 Free Blocks 3656 + +-- This somewhat truncated wiew of the heap dump lets you figure out exactly +-- how fragmented the heap is - if you are so inclined +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Dump the contents of the "debug" table using the pairs() iterator, the +-- following output is slightly altered for readability... +>for k,v in pairs(debug) do print(k,v) end +getupvalue function:0x204214 +debug function:0x2040ec +sethook function:0x204264 +getmetatable function:0x2041fc +gethook function:0x20411c +setmetatable function:0x2042d4 +setlocal function:0x20429c +traceback function:0x204324 +setfenv function:0x20424c +getinfo function:0x204154 +setupvalue function:0x2042ec +getlocal function:0x20418c +getregistry function:0x2041c4 +getfenv function:0x204104 +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- This example shows the RAM savings we get by eliminating the debug table +-- +-- First, do a round of garbage collection... +=collectgarbage() + +-- Now print out the current memory usage... +=collectgarbage("count") +16 776 + +-- That's (16*1024)+776 = 17160 bytes in use +-- +-- Now let's delete the debug table, do a round of garbage collection, and +-- print the new memory usage... + +debug=nil +=collectgarbage() + +-- Now print out the current memory usage... +=collectgarbage("count") +15 938 + +-- That's (15*1024)+939 = 16299 bytes in use +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Now delete the table, string, and nxt libraries and see how much RAM is +-- in use... +table=nil +string=nil +nxt=nil +=collectgarbage() + +-- Now print out the current memory usage... +=collectgarbage("count") +7 698 + +-- That's (7*1024)+698 = 7866 bytes in use - a pretty decent savings! +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +-- What does require() return? +>=require() +table:0x204c74 + +-- It's a table, so let's put the table through an iterator to what's in it... +>for k,v in pairs(require()) do print(k,v) end +1 all +2 table +3 string +4 debug +5 nxt_misc +6 nxt_bt +7 nxt_math +8 nxt_file +9 nxt_output +10 nxt_display +11 nxt_i2c +12 nxt_rs485 +13 nxt_input +14 nxt_sound +--codeExampleEnd 7 + +--codeExampleStart 8 ----------------------------------------------------------- +-- First, let's dump debug - it should give an error since it's gone... +for k,v in pairs(debug) do print(k,v) end +tty: stdin:1: bad argument #1 to 'pairs' (table expected, got nil) + +-- Now let's load up the debug library and dump it. I've reformatted the +-- output slightly... +require("debug") +for k,v in pairs(debug) do print(k,v) end +getupvalue function:0x204ccc +debug function:0x203274 +sethook function:0x204c7c +getmetatable function:0x204ce4 +gethook function:0x203734 +setmetatable function:0x20315c +setlocal function:0x204c44 +traceback function:0x20310c +setfenv function:0x204c94 +getinfo function:0x2036fc +setupvalue function:0x203144 +getlocal function:0x204d54 +getregistry function:0x204d1c +getfenv function:0x20374c +--codeExampleEnd 8 + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaRFIDSensor.lua b/examples/tutorial/pbLuaRFIDSensor.lua new file mode 100644 index 0000000..168dd62 --- /dev/null +++ b/examples/tutorial/pbLuaRFIDSensor.lua @@ -0,0 +1,290 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbluaIRLink.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- And use it - make sure you specify the port you have plugged the +-- IR Link in to port 1 + +nxt.RFIDdummy = string.char( 0x04, 0x00, 0x00 ) +nxt.RFIDboot = string.char( 0x04, 0x41, 0x81 ) +nxt.RFIDstart = string.char( 0x04, 0x41, 0x83 ) +nxt.RFIDstatus = string.char( 0x04, 32 ) + +setupI2C(1) + +nxt.I2CSendData( 1, nxt.RFIDboot, 0 ) +nxt.I2CSendData( 1, nxt.RFIDstart, 0 ) + +nxt.I2CSendData( 1, nxt.RFIDdummy, 0 ) +waitI2C( 1 ) +checkI2C(1, 4) + +-- Gives these results: + +-- Version -> V1.0 +-- Product ID -> CODATEX +-- SensorType -> RFID +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +-- Turn on the RFID sensor and take a single reading, then print the result + +nxt.RFIDsingle = string.char( 0x04, 0x41, 0x01 ) + +-- And this is the test function + +function singleRFID(port,delay) + -- Do a single reading + nxt.I2CSendData( port, nxt.RFIDdummy, 0 ) + waitI2C( port ) + + nxt.I2CSendData( port, nxt.RFIDsingle, 0 ) + waitI2C( port ) + + -- wait delay msec ... + + local t = nxt.TimerRead() + while t+delay > nxt.TimerRead() do + -- This space intentionally left blank! + end + + nxt.I2CSendData( port, nxt.I2Cdata[4], 5 ) + waitI2C( port ) + local result = nxt.I2CRecvData( port, 5 ) + + -- Break the resulting string into 5 bytes and print them + + local c1,c2,c3,c4,c5 = string.byte(result,1,5) + + print( string.format( "Result: %02x %02x %02x %02x %02x", + c1, c2, c3, c4, c5 ) ) +end + +-- And using the function +singleRFID(1,200) +singleRFID(1,200) +singleRFID(1,200) + +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Turn on the RFID sensor and take continuous readings, then print results +-- if consecutive readings are different and non-null... + +nxt.RFIDcontinuous = string.char( 0x04, 0x41, 0x02 ) +nxt.RFIDstatus = string.char( 0x04, 0x32 ) + +-- These strings represent bad RFID results + +nullRFID = string.char( 0x00, 0x00, 0x00, 0x00, 0x00 ) + +-- And this is the test function + +function contRFID(port,delay) + -- wake up the sensor + nxt.I2CSendData( port, nxt.RFIDdummy, 0 ) + waitI2C( port ) + + local oldResult, result = "", "" + local status = 0 + + repeat + oldResult = result + + -- Do continuous reading + nxt.I2CSendData( port, nxt.RFIDcontinuous, 0 ) + waitI2C( port ) + + -- Check status, and wait if it's 0 + nxt.I2CSendData( port, nxt.RFIDstatus, 1 ) + waitI2C( port ) + status = string.byte( nxt.I2CRecvData( port, 1 ) ) + if 0 == status then + -- wait 250 msec + local t = nxt.TimerRead() + while t+250 > nxt.TimerRead() do + -- This space intentionally left blank! + end + end + + -- wait delay msec before reading sensor + local t = nxt.TimerRead() + while t+delay > nxt.TimerRead() do + -- This space intentionally left blank! + end + + nxt.I2CSendData( port, nxt.I2Cdata[4], 5 ) + waitI2C( port ) + result = nxt.I2CRecvData( port, 5 ) + + -- print results if valid and different from previous + + if ((result ~= nullRFID) and (result ~= oldResult)) then + -- Break the resulting string into 5 bytes and print them + + local c1,c2,c3,c4,c5 = string.byte(result,1,5) + + print( string.format( "Result: %02x %02x %02x %02x %02x", + c1, c2, c3, c4, c5 ) ) + end + + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function +contRFID(1,200) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +> contRFID(1,100) +Result: 50 00 2a 18 b6 +Result: 50 00 29 d8 3b +Result: 50 00 2a 18 b6 +Result: 04 16 1d 5f de + +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +-- Here are statements that you can run one at a time to sound tones: +nxt.SoundTone(440) +nxt.SoundTone(880) +nxt.SoundTone(110) + +-- Here is a way to assign the tones to specific badges from the +-- previous examples. First start with a blank table: + +badgeAction = {} + +-- Next, index the table with a string and store compiled tone function: + +badgeAction[ "low" ] = + loadstring( "nxt.SoundTone(440)" ) + + +badgeAction[ "high" ] = + loadstring( "nxt.SoundTone(880)" ) + +-- And test the array one element at a time as a function: + +badgeAction["low"]() -- should sound a low tone + +badgeAction["high"]() -- should sound a high tone +--codeExampleEnd 5 + +--codeExampleStart 5r ----------------------------------------------------------- +-- What happens with a non-existent index? + +badgeAction["dummy"]() + +tty: stdin:1: attempt to call field 'dummy' (a nil value) +--codeExampleEnd 5r + +--codeExampleStart 6 ----------------------------------------------------------- +-- Set up a metatable, it can have any name we want, but this is a consistent +-- style if the metatable is for one table only... + +badgeAction.mt = {} + +-- Now specify what to do when we index a non=existent value... + +badgeAction.mt.f = loadstring( "nxt.SoundTone(110)" ) +badgeAction.mt.__index = function() return badgeAction.mt.f end +--codeExampleEnd 6 + +--codeExampleStart 6r ----------------------------------------------------------- +-- And test it out... + +badgeAction["dummy"]() + +tty: stdin:1: attempt to call field 'dummy' (a nil value) +--codeExampleEnd 6r + +--codeExampleStart 7 ----------------------------------------------------------- +-- Assign the new metatable to the original table + +setmetatable(badgeAction, badgeAction.mt) + +-- And test it out... + +badgeAction["dummy"]() +--codeExampleEnd 7 + +--codeExampleStart 8 ----------------------------------------------------------- +-- Example code to sound tones when badges are recognized +-- +-- First, enter table values for the badges we want associated with tones + +badgeAction[ string.char( 0x50, 0x00, 0x2a, 0x18, 0xb6 ) ] = + loadstring( "nxt.SoundTone(440)" ) + +badgeAction[ string.char( 0x50, 0x00, 0x29, 0xd8, 0x3b ) ] = + loadstring( "nxt.SoundTone(880)" ) + +-- Make sure that you've set up the metatable from the previous example... +-- +-- Then load up this new function. + +function badgeTone(port,delay) + -- wake up the sensor + nxt.I2CSendData( port, nxt.RFIDdummy, 0 ) + waitI2C( port ) + + local oldResult, result = "", "" + local status = 0 + + repeat + oldResult = result + + -- Do continuous reading + nxt.I2CSendData( port, nxt.RFIDcontinuous, 0 ) + waitI2C( port ) + + -- Check status, and wait if it's 0 + nxt.I2CSendData( port, nxt.RFIDstatus, 1 ) + waitI2C( port ) + status = string.byte( nxt.I2CRecvData( port, 1 ) ) + if 0 == status then + -- wait 250 msec + local t = nxt.TimerRead() + while t+250 > nxt.TimerRead() do + -- This space intentionally left blank! + end + end + + -- wait delay msec before reading sensor + local t = nxt.TimerRead() + while t+delay > nxt.TimerRead() do + -- This space intentionally left blank! + end + + nxt.I2CSendData( port, nxt.I2Cdata[4], 5 ) + waitI2C( port ) + result = nxt.I2CRecvData( port, 5 ) + + -- print results if valid and different from previous + + if ((result ~= nullRFID) and (result ~= oldResult)) then + -- Break the resulting string into 5 bytes and print them + + local c1,c2,c3,c4,c5 = string.byte(result,1,5) + + print( string.format( "Result: %02x %02x %02x %02x %02x", + c1, c2, c3, c4, c5 ) ) + + -- And don't forget to sound the tone! + badgeAction[ result ]() + + end + + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function +badgeTone(1,200) +--codeExampleEnd 8 +-- +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaRequire.lua b/examples/tutorial/pbLuaRequire.lua new file mode 100644 index 0000000..88fcba0 --- /dev/null +++ b/examples/tutorial/pbLuaRequire.lua @@ -0,0 +1,223 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaRequire.html + +--codeExampleStart 1 ----------------------------------------------------------- + +-- "all" +-- +nxt_names = { + "nxt_misc" + , "nxt_bt" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_sound" } + +-- , "nxt_math" + +for _,s in pairs(nxt_names) do print(s) end + +for k,v in pairs(list()) do print(k,v) end +for k,v in pairs(list(nil)) do print(k,v) end +for k,v in pairs(list("foo")) do print(k,v) end + +for _,s in pairs(nxt_names) do + print( "----" .. s .. "----" ) + for k,v in pairs(list(s)) do print(k,v) end +end +--codeExampleEnd 1 + +require() +require(nil) +require("foo") + +for _,s in pairs(nxt_names) do + print( "----" .. s .. "----" ) + require(s) +end + +unload() +unload(nil) +unload("foo") + +for _,s in pairs(nxt_names) do + print( "----" .. s .. "----" ) + unload(s) +end + +for k,v in pairs(_G) do print(k,v) end + +for k,v in pairs(nxt) do print(k,v) end + +for _,s in pairs(nxt_names) do + print( "----" .. s .. "----" ) + require(s) +end + + , "nxt_bt" + , "nxt_sound" } + +require("all") + +require("nxt_misc") +require("nxt_bt") +require("nxt_file") +require("nxt_output") +require("nxt_display") +require("nxt_i2c") +require("nxt_rs485") +require("nxt_input") +require("nxt_sound") + + +for k,v in pairs(string) do nxt[k]=nil end +for k,v in pairs(table) do nxt[k]=nil end +for k,v in pairs(debug) do nxt[k]=nil end + +string=nil +table=nil +debug=nil + + +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") + + +for k,v in pairs(nxt) do nxt[k]=nil end +for k,v in pairs(foo) do foo[k]=nil end + + +foo = {} +for k,v in pairs(nxt) do foo[k]=nxt[k] end + +nxt=nil + + +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") + + +for k,v in pairs(_G) do print(k,v) end + +nxt_names = { + "nxt_misc" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + , "nxt_file" + , "nxt_output" + , "nxt_display" + , "nxt_i2c" + , "nxt_rs485" + , "nxt_input" + } + + +for _,s in pairs(nxt_names) do + print( "----" .. s .. "----" ) + require(s) +end + +for k,v in pairs(nxt) do nxt[k]=nil end +nxt=nil + +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") +=collectgarbage() +=collectgarbage("count") + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + diff --git a/examples/tutorial/pbLuaSensors.lua b/examples/tutorial/pbLuaSensors.lua new file mode 100644 index 0000000..0c8b613 --- /dev/null +++ b/examples/tutorial/pbLuaSensors.lua @@ -0,0 +1,303 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaSensors.html + +--codeExampleStart 1 ----------------------------------------------------------- +-- Read touch sensor until the orange button is pressed +function TouchRead(port) + + nxt.InputSetType(port,1) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +TouchRead(1) +--codeExampleEnd 1 + +--codeExampleStart 1a ----------------------------------------------------------- +-- Read touch sensor in boolean mode until the orange button is pressed +function TouchRead(port) + + nxt.InputSetType(port,1,0x20) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +TouchRead(1) +--codeExampleEnd 1a + +--codeExampleStart 1b ----------------------------------------------------------- +-- Read touch sensor in transition count mode until the orange button is pressed +function TouchRead(port) + + nxt.InputSetType(port,1,0x40) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +TouchRead(1) +--codeExampleEnd 1b + +--codeExampleStart 1c ----------------------------------------------------------- +-- Read touch sensor in period count mode until the orange button is pressed +function TouchRead(port) + + nxt.InputSetType(port,1,0x60) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +TouchRead(1) +--codeExampleEnd 1c + + +--codeExampleStart 2 ----------------------------------------------------------- +-- Read touch sensor until the orange button is pressed - note that it's a +-- lot easier to simply read the sensor in boolean mode... + +function TouchChange(port, thresh) + + nxt.InputSetType(port,0) + + local oldState,newState + + repeat + if( thresh < nxt.InputGetStatus( port ) ) then + newState = 0 + else + newState = 1 + end + + if newState ~= oldState then + print( nxt.TimerRead(), newState ) + oldState = newState + end + + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +TouchChange(1,500) +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +-- Read sound sensor until the orange button is pressed +function SoundRead(port) + + nxt.InputSetType(port,0,7) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +SoundRead(2) +--codeExampleEnd 3 + +--codeExampleStart 4 ----------------------------------------------------------- +-- Read sound sensor and scale the results the hard way +function SoundScale(port,sensitive) + + sensitive = sensitive or 0 + + if 0 == sensitive then nxt.InputSetType(port,8) + else nxt.InputSetType(port,7) + end + + repeat + local raw = nxt.InputGetStatus(port) + + -- Check if raw value is less than minimum, adjust if needed + if raw < 162 then raw = 0 + else raw = raw - 162 + end + + -- Scale the result + raw = (raw*100)/83 + + -- Check if raw value is greater than maximum, adjust if needed + if raw > 1023 then raw = 1023 + end + + -- Invert the raw value + raw = 1023 - raw + + -- Make a string that represents the volume... + + print( nxt.TimerRead(), string.rep("*", raw/16 ) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +SoundScale(2,0) +--codeExampleEnd 4 + +--codeExampleStart 4a ----------------------------------------------------------- +-- Read sound sensor and scale the results the easy way +function SoundScale(port,sensitive) + + sensitive = sensitive or 0 + + if 0 == sensitive then nxt.InputSetType(port,8,0x80) + else nxt.InputSetType(port,7,0x80) + end + + repeat + local _,_,_,raw = nxt.InputGetStatus(port) + + print( nxt.TimerRead(), string.rep("*", raw/2) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +SoundScale(2,0) +--codeExampleEnd 4a + +--codeExampleStart 5 ----------------------------------------------------------- +-- Read light sensor until the orange button is pressed +function LightRead(port,active) + + active = active or 0 + + if 0 == active then nxt.InputSetType(port,6,0x80) + else nxt.InputSetType(port,5,0x80) + end + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + until( 8 == nxt.ButtonRead() ) +end + +-- And using the function - press the orange button on the NXT to stop it +LightRead(3,0) +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Read light sensor until the orange button is pressed +function Barcode(port,active,threshold,guard) + + nxt.InputSetType(port,0) + + active = active or 0 + + if 0 == active then nxt.InputSetState(port,0,0) + else nxt.InputSetState(port,1,0) + end + + nxt.InputSetDir(port,1,1) + + local oldState = 1 + local newState = 1 + local light + + repeat + light = nxt.InputGetStatus(port) + + if light > (threshold + guard) then newState = 0 end + if light < (threshold + guard) then newState = 1 end + + if newState ~= oldState then + print( nxt.TimerRead(), newState ) + oldState = newState + end + + until( 8 == nxt.ButtonRead() ) +end + +-- And here's how to call the function. You may need to try this +-- with different values for the threshold and guard +Barcode(3,1,700,20) +--codeExampleEnd 6 + +--codeExampleStart 7a ----------------------------------------------------------- +-- The color sensor turns on steady red... +function RedSensor(port) + + nxt.InputSetType(port,14) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + + until( 8 == nxt.ButtonRead() ) +end +-- And using the function - press the orange button on the NXT to stop it +RedSensor(3) +--codeExampleEnd 7a + +--codeExampleStart 7b ----------------------------------------------------------- +-- The color sensor turns on steady green... +function GreenSensor(port) + + nxt.InputSetType(port,15) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + + until( 8 == nxt.ButtonRead() ) +end +-- And using the function - press the orange button on the NXT to stop it +GreenSensor(3) +--codeExampleEnd 7b + +--codeExampleStart 7c ----------------------------------------------------------- +-- The color sensor turns on steady blue... +function BlueSensor(port) + + nxt.InputSetType(port,16) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + + until( 8 == nxt.ButtonRead() ) +end +-- And using the function - press the orange button on the NXT to stop it +BlueSensor(3) +--codeExampleEnd 7c + +--codeExampleStart 7d ----------------------------------------------------------- +-- The color sensor is an active full color device - must use pct mode +function FullSensor(port) + + nxt.InputSetType(port,13,0x80) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + + until( 8 == nxt.ButtonRead() ) +end +-- And using the function - press the orange button on the NXT to stop it +FullSensor(3) +--codeExampleEnd 7d +-- +--codeExampleStart 7e ----------------------------------------------------------- +-- The color sensor is a passive light sensor - must use pct mode +function NoneSensor(port) + + nxt.InputSetType(port,17,0x80) + + repeat + print( nxt.TimerRead(), nxt.InputGetStatus(port) ) + + until( 8 == nxt.ButtonRead() ) +end +-- And using the function - press the orange button on the NXT to stop it +NoneSensor(3) +--codeExampleEnd 7e +-- +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + + diff --git a/examples/tutorial/pbLuaSerialFlash.lua b/examples/tutorial/pbLuaSerialFlash.lua new file mode 100644 index 0000000..4894ee9 --- /dev/null +++ b/examples/tutorial/pbLuaSerialFlash.lua @@ -0,0 +1,68 @@ +-- Daniel, here's a full example that works on stock pbLua +-- as currently distributed on my website + +-- Define a function to set up an I2C port + +function setupI2C(port) + nxt.InputSetType(port,2) + nxt.InputSetDir(port,1,1) + nxt.InputSetState(port,1,1) + + nxt.I2CInitPins(port) +end + +-- And a function to wait for the I2C bus to go idle + +function waitI2C( port ) + while( 0 ~= nxt.I2CGetStatus( port ) ) do + end +end + +-- Set up the I2C FLASH memory to be on port 1 + +port = 1 + +setupI2C(port) + +-- Here's where we write data to the I2C FLASH. The bytes +-- are ordered like this: +-- +-- A0 is the device code and address +-- 0x45 0x98 are the address bytes in big endian format +-- 11 12 13 14 are the bytes to write +-- +-- Since the total write string can be only 16 bytes long, you +-- can only write 13 bytes of data, so round it down to 8 or 12 +-- +-- What makes this a write? The 3rd parameter to the I2CSendData +-- function is 0, which means no restart! + +memstr = string.char( 0xA0, 0x45, 0x98, 21, 22, 23, 24 ) +nxt.I2CSendData( port, memstr, 0 ) +waitI2C( port ) + +-- Here's where we read data from the I2C FLASH. The bytes +-- are ordered like this: +-- +-- A0 is the device code and address +-- 0x45 0x98 are the address bytes in big endian format +-- 11 12 13 14 are the bytes to write +-- +-- Since the total read string can be only 16 bytes long, you +-- can read up to 16 bytes at a time (I think) + +memstr = string.char( 0xA0, 0x45, 0x98 ) +nxt.I2CSendData( port, memstr, 4 ) +waitI2C( port ) + +-- Here's where we read the I2C buffer and print the results +-- byte by byte... + +print( string.byte(nxt.I2CRecvData(port),1,4) ) + +for i=1,1024 do + memstr = string.char( 0xA0, 0x45, 0x98 ) + nxt.I2CSendData( port, memstr, 16 ) + waitI2C( port ) +-- print( string.byte(nxt.I2CRecvData(port),1,16) ) +end diff --git a/examples/tutorial/pbLuaStartupMain.png b/examples/tutorial/pbLuaStartupMain.png new file mode 100644 index 0000000000000000000000000000000000000000..2fefda0638b087fb73bfdb8df6942d1081f3604e GIT binary patch literal 1189 zcmeAS@N?(olHy`uVBq!ia0vp^H-LD8027ehdh^{6AjMhW5m^kRJ;2!QWVRhhu&lr_ z9Y}*!GtBz8GLV6R`GKd4V@SoVw|5=$78@|IUMRbw{FGy-aJa|2Dar*aBERS>g@)|d zcJM&;>WdsQ%&*S#3w*Nl{rsn*h9TzO{=Mo923(CC0!)GmEJ_X>E)4=542lAbk5-qo zKL1=+y?5V#Ti?$$`?tsLn>y?I-RyHqi>u9V*?hmn?>+6C|4WPPV^4ONxHM?wb+{KU zD}Qg=TlysIcJx_w(chEHrIZ|aE(%S~7jKnBEbP6|vd4lEp)o*u>$KUPKl{JH0D-1_^|XD+MUcYpiM8Q*{A{M;3N zE_Z#dPVL=`eMhH#e)+N7@YX*YZD5FYNk6(c`_Qy&Cat>17tWWf7w`KG3dt2#?Kiy6 zT25bD>YlOe{rc$7dH1vC2u?Y-e@pJvb=6aH?SB>8a{;xNWp}KVx^rpwi;TAo^Rv!v zm3=C6XVy<+KA_aD`|TXwRrinY3RnK@`4p6RR%CUUhBJ7}{+%uNr{vn-`fG1@F@en3 zeJZA^F8%E8ce4z?|N8g-(*NsQ-ss-W>wmoUX-(DZjrDF8@4^==G97tta`yA%jo(ce zzAXpx1n#FUn=f}?_MM@5Io}uKLzed!Ul3q)a^53we*GT4^SXbfL1tGcZ7ck@?N40Q zoB3gnx8It-cHU{rTYop0-mW$UCNYcl3Pt6Z+?=m}PK7=G@=tfltkkLxHOKB}b}$6q z5pe%Kcf#hr%ZF#4%k0b9^7q+{bzpnXJDZ$4uKDGL*!{GX z!Z~4C&w{J+V_#-py|X^t_*};M%ABWbZeN|<#vw4TokIX^c9928ip;E)9EhI_&L_ zKYm*DduMwogAZ7YL&;&^vww%~>@I!&`D>MxZhZYaEAMZnPxmg{Q+I0TFYVt^kN@1b z{`u?ksx9kPU+<9?R&q%5@A!Ug#*%4!((=C<6#C3;TPAaOMQKIe71Pb_+3)p@6q!;k zwphH9yIKFYFegE~nfzNM-{FuWjsIdCJ^B@0ZJ_6qJqVg*L zWZPuQZvCz*{QA!KsN}hUzl}um&bM$(a20-}5nWehA60y6?~=gvH!n@|pAcV~UwHcU z^U&&<)j5l=Rhp~0G|2q9ZrcEI8;hU2OC7#SEE=^9w*8kmL{8d@0}SQ#1W8kkra7&sp;Sc{?|H$NpatrE8e Uqj@I;ff^V*UHx3vIVCg!03YQDO8@`> literal 0 HcmV?d00001 diff --git a/examples/tutorial/pbLuaVersion.xpm b/examples/tutorial/pbLuaVersion.xpm new file mode 100644 index 0000000..4197c7a --- /dev/null +++ b/examples/tutorial/pbLuaVersion.xpm @@ -0,0 +1,114 @@ +/* XPM */ +static char * _xpm[] = { +"72 108 2 1", +" c #77cc88", +"* c #000044", +" ", +" ", +" ", +" ", +" ***** ", +" * * ", +" * * ", +" * * ", +" * ", +" ", +" ******* ", +" * * ", +" * * ", +" * * ", +" *** ", +" ", +" ******* ", +" * ", +" * ", +" * ", +" * ", +" ", +" **** ", +" * ", +" * ", +" * ", +" ***** ", +" ", +" * ", +" * * * ", +" * * * ", +" * * * ", +" **** ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" * * ", +" * * ", +" * ", +" * * ", +" * * ", +" ", +" * * ", +" * * ", +" * ", +" * * ", +" * * ", +" ", +" ", +" ** ", +" ** ", +" ", +" ", +" ", +" ** ", +" * * ", +" * * ", +" * * ", +" **** ", +" ", +" ** ", +" * * ", +" * * ", +" * * ", +" **** ", +" ", +" ", +" ** ", +" ** ", +" ", +" ", +" ", +" * * ", +" ** * ", +" * * * ", +" * ** ", +" * * ", +" ", +" * * ", +" ** * ", +" * * * ", +" * ** ", +" * * ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +}; diff --git a/examples/tutorial/pbLuaXmodem.lua b/examples/tutorial/pbLuaXmodem.lua new file mode 100644 index 0000000..c62dd0c --- /dev/null +++ b/examples/tutorial/pbLuaXmodem.lua @@ -0,0 +1,146 @@ +-- This is the example code file for the webpage at: +-- +-- www.hempeldesigngroup.com/lego/pbLua/tutorial/pbLuaXmodem.html + +--codeExampleStart 1 ----------------------------------------------------------- +nxt.DisplayClear() +i = 0 + +repeat + s=nxt.XMODEMRecv() + if s then + nxt.DisplayText( string.format( "Record %i", i ) ) + i = i + 1 + end +until s == nil +--codeExampleEnd 1 + +--codeExampleStart 2 ----------------------------------------------------------- +nxt.DisplayClear() +a={} +repeat + s=nxt.XMODEMRecv() + if s then + table.insert(a,s) + end +until s == nil +--codeExampleEnd 2 + +--codeExampleStart 3 ----------------------------------------------------------- +for _,s in ipairs(a) do + nxt.FileWrite(0,s) +end +--codeExampleEnd 3 + +----codeExampleStart 4 ----------------------------------------------------------- +p = "" +i = 0 +repeat + s = nxt.XMODEMSend(p) + if( s ) then + if i == 256 then + p = nil + else + p = string.rep( string.char(i), 128 ) + i = i + 1 + end + end +until s == nil +--codeExampleEnd 4 + +--codeExampleStart 5 ----------------------------------------------------------- +p = "" +addr = 0x100000 + +repeat + local s = nxt.XMODEMSend(p) + + if( s ) then + if addr >= 0x140000 then + p = nil + else + p = nxt.MemRead(addr,128) + addr = addr + 128 + end + end + +until s == nil +--codeExampleEnd 5 + +--codeExampleStart 6 ----------------------------------------------------------- +-- Function to set up the light sensor and then start transferring data +-- to the host as quickly as possible. Call the function, then start the +-- XMOPDEM transfer on the host, and then press the orange button to +-- start the sampling... + +function LogLight( port ) +-- Set up the light sensor on port 1 for passive mode + + nxt.InputSetType(port,0) + nxt.InputSetState(port,0,0) + nxt.InputSetDir(port,1,1) + + repeat + -- Wait here until the orange button is pressed + until( 8 == nxt.ButtonRead() ) + + local startTicks = nxt.TimerRead() + local sampleTick = startTicks + local p = "" + local d = "" + local s = true + + repeat + local nowTicks = nxt.TimerRead() + + -- Grab a light sensor sample and add it to our string if + -- at least 3 milliseconds have passed and we're still sending + + if (sampleTick + 3 <= nowTicks) and (p ~= nil) then + d = d .. string.format( "%06i %06i\r\n", nowTicks, nxt.InputGetStatus(port) ) + sampleTick = nowTicks + end + + -- Only try sending if we have more than 128 bytes to send + + if string.len(d) > 128 then + s = nxt.XMODEMSend(p) + + if( s ) then + if startTicks + 10000 < nowTicks then + p = nil + else + p = string.sub(d,1,128) + d = string.sub(d,129) + print(p) + end + end + end + until s == nil +end +--codeExampleEnd 6 + +--codeExampleStart 7 ----------------------------------------------------------- +-- Start the high-speed datalogger + +LogLight(1) +--codeExampleEnd 7 + +--codeExampleStart 7r ----------------------------------------------------------- +119345 000623 +119348 000619 +119351 000621 +119354 000623 +119357 000619 +119360 000623 +119363 000622 +119366 000622 +119369 000623 +119372 000623 +119375 000619 +119378 000619 +--codeExampleEnd 7r + +--codeExampleStart n ----------------------------------------------------------- +--codeExampleEnd n + diff --git a/pbLua.rfw b/pbLua.rfw new file mode 100755 index 0000000000000000000000000000000000000000..7d803c0e85373656a06534d26fe7fa6c33901257 GIT binary patch literal 136136 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

raZcsDrAfG)leCT9btvTq4 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 literal 0 HcmV?d00001 diff --git a/usbDriver/pbLanguages.inf b/usbDriver/pbLanguages.inf new file mode 100644 index 0000000..c6b4d50 --- /dev/null +++ b/usbDriver/pbLanguages.inf @@ -0,0 +1,32 @@ +[Version] +Signature="$Windows NT$" +Class=Ports +ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318} +Provider=%ProviderName% +DriverVer=10/15/2009,1.0.0.2 + +[MANUFACTURER] +%ProviderName%=DeviceList, NTx86, NTamd64 + +[DeviceList.NTx86] +%pbLanguages%=DriverInstall,USB\VID_0694&PID_FF00 + +[DeviceList.NTamd64] +%pbLanguages%=DriverInstall,USB\VID_0694&PID_FF00 + +[DriverInstall] +include=mdmcpq.inf +CopyFiles=FakeModemCopyFileSection +AddReg=LowerFilterAddReg,SerialPropPageAddReg + +[DriverInstall.Services] +include = mdmcpq.inf +AddService = usbser, 0x00000002, LowerFilter_Service_Inst + +; This adds the serial port property tab to the device properties dialog +[SerialPropPageAddReg] +HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" + +[Strings] +ProviderName = "HDG" +pbLanguages = "pbLanguages Virtual Com Port"