ASkr 8/2009:
PAGE REDESIGN IN PROGRESS...
Just a few notes on getting the most out of direct pin driving methods in pbLua.
From simple switching up to highly sophisticated real-time bus controlled applications or closed-loop
control, pbLua has it all...
ASkr 12/09:
My time is limited.
Until this is completed, you may wish to browse through the (yet few) stuff of applied pbLua science,
here.
For almost any NXT-attachable circuit, pbLua source code is available...
And remember:
It is spelled N-X-T, but pronounced pbLua ;-)
Hardware Basics
Let's take a quick look at what we have, here...
Each of the four input ports of the NXT, the ones labeled "1-4", offer
- a 10bit analogue input (5V reference)
- a 9V power supply, current limited, mutually exclusive with A/D functionality
- a 4-5V current limited power supply pin
- two digital IO pins (DIG0, DIG1; 3V3)
Additionally, the digital IO pins of port #4 can be turned into an EIA-485
(aka. RS-485) half-duplex bus (8N1 UART, 3V3), with a bit rate of up to ~1MBit/s.
Schematic of an input port:
The two digital port pins, coming directly from the AT91, are well protected. Except for very unusual things,
they won't get damaged.
Keep in mind that this is still a "weak" port pin. Especially in conjunction with the 4k7 series resistor,
it has limited driving and sink capabilities. Attaching a cable with a 100pF capacitive load will result in
a ~0.5us time constant, which creates slow signal edges. Usually too slow for digital clock inputs. You are
advised to add buffers of a Schmitt trigger type, in both directions.
In conjunction with an attached modular cables (like the ones that came with your NXT), the signals look like this.
Note the spikes on the blue trace, caused by cross coupling across only 25cm of cable length
25cm modular cable cross coupling
clock to enlarge...
Apart from that, the port pins offer everything port pins should do.
Each of them can be turned into a digital in- or output, independent from the state of the other one.
Pin #1 serves two functions, which are mutually exclusive.
It can be turned into an input for a 10 bit A/D converter or it can be used as a 9V power supply.
If the 9V functionality is turned on statically, the A/D converter will obviously produce nothing else but 0x3ff values
(except for drawing excessive current out of the pin ;).
Note that "mutually exclusive" does not necessarily mean it can not be done (*1*). It just does not make sense
in most cases and is a popular method of damaging hardware or at least causing unexpected problems.
If you attach hardware, which makes use of the A/D converter, make sure it's 9V proof, just in case
a stupid programming error or whatever slams out 9V into your electronics...
Because pin 1 has an internal 10k pull-up resistor to VCC5V, using it in A/D converter mode requires a low impedance
source. You may read a little more about this here and
here.
----------------------------
(*1*)
In rare cases one might decide to power a reactionless circuit from the 9V supply, quickly change the
port pin's state to A/D converter operation and measure anything.
something like this
Unfortunately, the NXT internal 10k pull-up resistor to VCC5V does not allow high impedance A/D connections.
In this cases a linear current sink, which triggers after the 9V supply dropped below ~6V, would be a better choice.
Hooking It Up
If you can't get your hand on the special NXT plugs, the cheapest and easiest way, of attaching your own stuff
to the Lego NXT, is buying a set of cables, cut them and crimp on a standard RJ-12 connector.
(You may wish to visit Philo's great pages for the real art of crimping custom connectors.)
Five 50cm cables cost about 5 EUR, look here (German only),
for example.
Other (cheap) stuff you may need (German distributor examples):
12/2009, Randnotiz für Reichelt Freaks:
Angelika hat ihren Nachwuchs nicht für Elektronik begeistern können.
Ab 1.1.2010 gehört Reichelt zur Dätwyler-Gruppe...
Getting In Control
The pbLua functions associated with fiddling around the input ports are:
nxt.InputSetType( port, [type=0], [mode=0] )
nxt.InputSetDir( port, dig0, dig1 )
nxt.InputSetState( port, dig0, dig1 )
ADval, dig0, dig1, colour = nxt.InputGetStatus( port )
|
Look here, for a complete reference.
In addition, the EIA-485 interface can be controlled via:
nxt.RS485Enable( rate )
nxt.RS485SendData( string )
string = nxt.RS485RecvData( )
|
To make this one complete, the I2C interface (available on every port's digital pins) can be driven by:
nxt.I2CInitPins( port )
state, txstatus, rxstatus = nxt.I2CGetStatus( port )
nxt.I2CSendData( port, string [, rxlen=0] )
string = nxt.I2CReceiveData( port [, rxlen] )
|
Note that these can be used all together! It is possible to chain in direct pin driving (or querying) methods in the
middle of e.g. a EIA-485 transmission.
First of all, you need to call "nxt.InputSetType()" for a basic setup. For custom hardware you will most likely only
need two versions of this call.
A standard setup with 9V supply turned off:
port=1
-- standard setup, A/D pin available (9V turned off)
nxt.InputSetType( port, 0 )
-- because "mode" defaults to "0", one could just type:
nxt.InputSetType( port )
|
And the same with 9V supply turned on (no A/D pin operation):
port=1
-- 9V setup, type=LOWSPEED_9V, A/D pin "off"
nxt.InputSetType( port, 11 )
|
To set or change the "direction" of a port's pin, call nxt.InputSetDir(). A '1', as argument for any of the two
digital pins, will make the pin an output, a '0' will change its state to an input.
port=1
-- both pins are inputs
nxt.InputSetDir( port, 0 , 0 )
-- both pins are outputs
nxt.InputSetDir( port, 1 , 1 )
-- DIG0 input, DIG1 output
nxt.InputSetDir( port, 0 , 1 )
-- DIG0 output, DIG1 input
nxt.InputSetDir( port, 1 , 0 )
|
To change the state (low/high, aka.: 0V/3.3V) of an output pin, call nxt.InputSetState(). A '1', as argument, will bring the
output to an high state, a '0' to a low state.
port=1
-- DIG0 low, DIG1 high
nxt.InputSetState( port, 0 , 1 )
-- DIG0 high, DIG1 high
nxt.InputSetState( port, 1 , 1 )
-- and so on...
|
A first example:
-- A very simple port toggle example
-- Left button toggles DIG0
-- Right button toggles DIG1
-- Orange button quits
function t(port)
local i
local d0=0
local d1=0
local lastbut=0
nxt.InputSetType(port,0)
nxt.InputSetDir(port,1,1)
while 1 do
nxt.InputSetState(port,d0,d1)
i=nxt.ButtonRead()
if i>7 then break end
if lastbut-i ~= 0 then
if i==0 then
lastbut=0
end
if i==4 then
if d0==0 then
d0=1
else
d0=0
end
lastbut=4
end -- if i==4
if i==2 then
if d1==0 then
d1=1
else
d1=0
end
lastbut=2
end -- if i==2
end -- if lastbut
end -- end while
-- Although not required, it sometimes is indeed
-- a good strategy to (re)set the ports to a defined state
-- if your program exits.
nxt.InputSetState(port,0,0)
nxt.InputSetDir(port,0,0)
end -- end function
|
Now, call the function 't' with an argument that represents your port number (1-4): E.g.: 't(1)'.
The example above will do nothing else, but toggle the state of the digital pins on the push of a button.
The "LEFT" button toggles DIG0, whereas the "RIGHT" button toggles the state of DIG1.
Hitting the orange button quits the "program".
If you don't have a multimeter, scope, ..., at hand, you can attach high efficiency red LEDs directly
to the port pins (a red LED will be powered by ~360uA only -> standard LEDs will only give a very dim light!).
For static control of pins, configured as outputs, that's all you need to know...
A more "advanced" (...) example and its effects...
function t(port)
-- standard setup, "NO_SENSOR"
nxt.InputSetType(port,0)
-- make both pins an output
nxt.InputSetDir(port,1,1)
-- toggle both pins...
repeat
nxt.InputSetState(port,1,0)
nxt.InputSetState(port,0,1)
until nxt.ButtonRead() ~= 0
end
|
Both pins are set to output state and toggled until the orange button is pressed.
This is what the outputs look like (RED=DIG0, BLUE=DIG1):
Things to mention:
- DIG0 is set ~6us before DIG1
- time for setting a pin via ...SetState() is ~43us
Now, let's scroll out a little to see the complete graph:
Well, probably this is not what you expected, right? Every ~1ms, the execution of the program paused due to an interrupt
routine.
nxt.InputSetState(port,1,0) gets interrupted right before setting DIG1 to 0:
The interrupt can be disabled by calling nxt.DisableNXT(1) and turned back on with nxt.DisableNXT(0).
Fast port access, or other important time critical stuff, should be embedded between these calls:
nxt.DisableNXT(1)
-- your fast stuff,
-- in here...
nxt.DisableNXT(0)
|
If we apply this to our little beginner's program...
-- WARNING:
-- bad code ahead!
function t(port)
nxt.InputSetType(port,0)
nxt.InputSetDir(port,1,1)
-- disable interrupt
nxt.DisableNXT(1)
-- an endless loop (power cycle or hard reset required!)
repeat
nxt.InputSetState(port,1,0)
nxt.InputSetState(port,0,1)
-- ButtonRead() will not work if interrupt turned off!
until nxt.ButtonRead() ~= 0
-- this line will never be reached!
nxt.DisableNXT(0)
end
|
... the outputs look much better now:
interrupt turned off
As you already might have found out, the program can not be aborted any more. The current state of the buttons
is updated/queried in the interrupt routine, which is not called any more. Therefore the return value of
nxt.ButtonRead() will not get updated.
Usually, nxt.DisableNXT() is best used for short bursts, packets (and controlling the pins, only), like demonstrated here:
-- time for execution ~8ms
function t(port)
local i
nxt.InputSetType(port,0)
nxt.InputSetDir(port,1,1)
nxt.DisableNXT(1)
for i=1,100 do
nxt.InputSetState(port,1,0)
nxt.InputSetState(port,0,1)
end
nxt.DisableNXT(0)
end
|
|
There are some more commands/routines/functions that should/must not be used (or simply will not work) after
calling nxt.DisableNXT(1):
- nxt.ButtonRead()
- print()
- nxt.FileWrite()
- and, most notably but underrated: ERRORS WON'T GET PRINT TO THE CONSOLE!
- t.b.c...
Note that any error messages, which can not be detected by pbLua's byte compiler,
won't appear on the console. The NXT will simply crash (or at least appearing so...)
-- a simple way to crash the NXT
function lockup(mytable)
local i
nxt.DisableNXT(1)
i=mytable[1]
nxt.DisableNXT(0)
end
lockup(666)
This will also lock up your NXT (basically the same as above, but more hidden).
While every call to print() fills up an internal send buffer, its contents can never be sent because this
is done, you guess it, during the interrupt:
-- WARNING
-- bad code ahead!
function t(port)
local i
nxt.InputSetType(port,0)
nxt.InputSetDir(port,1,1)
nxt.DisableNXT(1)
-- Using print() with interrupt turned
-- off is a bad idea...
for i=1,100 do
nxt.InputSetState(port,1,0)
nxt.InputSetState(port,0,1)
print(i)
end
nxt.DisableNXT(0)
end
And a last one. Note that a call to nxt.DisableNXT(1) even might affect preceeding instructions, like shown here.
(Though I totally agree that this is -somehow- fictitious, it may frequently happen during debug sessions ;-)
-- a simple way to crash the NXT
function lockup(mytable)
local i
print("If you are lucky enough,")
print("you may be able to read a few lines,")
print("probably only a few words, just")
print("try it ;)")
nxt.DisableNXT(1)
i=mytable[1]
nxt.DisableNXT(0)
end
lockup(666)
Making It Bidirectional
TO BE CONTINUED...
Real-Time (well, almost)
just some remarks for me...
TO BE CONTINUED...
Dirty Tricks
TO BE CONTINUED...
Example #1: Hand Coded PID Loop PWM
TO BE CONTINUED...
ASkr 03/2009
ASkr 12/2009
|