rabaggett
Jan 1, 2009
<span style="display: block; text-align: center;"><span style="font-size: 22pt;">**Liberty Basic and Propeller for hardware control.
Part 2**
</span></span> <span style="display: block; text-align: center;"> </span>
<span style="display: block; text-align: left;">Richard Baggett [[http://www.r2consult.net|r2consult.net]]
</span>
<span style="display: block; text-align: left;"> </span><span style="display: block; text-align: left;">In the previous article, we learned how to find an available serial port, and how to detect and do some control of our hardware using a custom protocol. We built a demonstration that turned LEDs on and off.</span>
<span style="display: block; text-align: left;">That demonstration was intended to introduce some functions and techniques, and was not intended for general use. In this article, we'll build a much more 'finished' demonstration. This demo will use the functions in a way that eliminates the possibility of our program crashing due to our use of a timer inside a function. We'll also see how to extend our protocol, and control the brightness of the LEDs!</span>
<span style="display: block; text-align: left;">**The problem in review:**</span>
<span style="display: block; text-align: left;">The DevCheck() function uses a timer internally. This is a bad thing, because if anything else happens (Button press, closing the program, etc.) while we are at the wait statement, our program will crash!</span>
<span style="display: block; text-align: left;">This happens because the label <span style="font-size: 8pt;"><span style="font-family: Courier New,monospace;">[IoWait]</span></span> is scoped locally to the function. If we execute anything else during the wait, then we're not in the function anymore, we can't find the label when the timer fires, and our program crashes!</span>
<span style="display: block; text-align: left;">**The Solution:**</span>
<span style="display: block; text-align: left;">We //could//<span style="font-style: normal;"> just not do the check inside a function, but that would make our program much harder to understand. It also takes away the extreme coolness of doing the check with one simple function call.</span></span>
<span style="display: block; text-align: left;">If we use the routine before opening any windows, there's nothing to press or close, and nothing to interfere. It makes sense for our program to be ready to do something before we open a window anyway. This is exactly what we'll do.</span>
<span style="display: block; text-align: left;"> </span><span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">As you can see, we scan for ports and check for our hardware without opening a window. If we don't find our hardware, we provide a window to allow re-scanning or quitting in an orderly fashion.</span></span>
<span style="display: block; text-align: left;"> </span><span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">Of course, we must include our functions. We'll also need to include some functions to make and operate the trackbars. We won't talk about them here, because they are covered in detail elsewhere in the LBPE. So our whole program looks like this</span></span><span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;"> </span></span>
[[code]]
'************************** Propeller IO *********************
'Demonstrating functions and techniques for successful serial
'port projects
NoMainWin
'**************** Find Propeller ***************
[Rescan]
If ComOpen = 1 Then
Close #io
ComOpen = 0
End If
If find = 1 then
Close #Find
find = 0
End If
Global Baud
Baud = 9600
For a = 1 to 30 'USB serial adapters can have some crazy numbers!
If CheckCom(a) = 1 Then
If DevCheck(a,"Propeller?","Present!") = 1 Then
Port$ = "COM"; a; ":"; Baud; ",n,8,1,ds0,cs0,rs"
Com = 1024
Open Port$ for random as #io
OnComError [ErrNotify]
ComOpen = 1 'Used to determine if this port is open at [quit]
End If
End If
Next
'********************** Handle case if hardware not found ******************
If ComOpen = 0 Then
WindowWidth = 284 : WindowHeight = 137
UpperLeftX = INT((DisplayWidth-WindowWidth)/2)
UpperLeftY = INT((DisplayHeight-WindowHeight)/2)
statictext #Find.st1, "No suitable hardware found", 50, 5, 170, 16
statictext #Find.st2, "Would you like to:", 75, 30, 110, 16
button #Find.rsc, "Rescan", [Rescan], UL, 20, 60, 90, 30
button #Find.abt, "Abort", [quit], UL, 160, 60, 90, 30
Open "Scan Results" for Window as #Find
#Find "trapclose [quit]"
find = 1 'Used to determine if this window is open at [quit]
wait
End If
'*************** Set up and Open our control window ********************
WindowWidth = 276 : WindowHeight = 279
UpperLeftX = INT((DisplayWidth-WindowWidth)/2)
UpperLeftY = INT((DisplayHeight-WindowHeight)/2)
StaticText #pa.s16, "P16", 10, 10, 30, 16
StaticText #pa.s17, "P17", 10, 40, 30, 16
StaticText #pa.s18, "P18", 10, 70, 30, 16
StaticText #pa.s19, "P19", 10, 100, 30, 16
StaticText #pa.s20, "P20", 10, 130, 30, 16
StaticText #pa.s21, "P21", 10, 160, 30, 16
StaticText #pa.s22, "P22", 10, 190, 30, 16
StaticText #pa.s23, "P23", 10, 220, 30, 16
Open "Propeller Analog Demo" for Window as #pa
#pa "trapclose [quit]"
pa = 1 'Used to determine if this window is open at [quit]
'******************** Add the sliders to the control window ******************
calldll #comctl32, "InitCommonControls",_
re as void
Dim HwndTbar(8)
HwinMain = Hwnd(#pa)
For Tidx = 0 to 7
HwndTbar(Tidx) = Tbar(HwinMain,45,(30*Tidx+5),220,24) 'Put the handles in an
Call TbarSet HwndTbar(Tidx), 255,0,0 'array for later use.
Next
'********************************************************************************
'*************************** Here's the important part **************************
Timer 50, [AnalogLoop]
[AnalogLoop]
Packet$ = "a" ' 'a' nalog command
For Tidx = 0 to 7
Tb$ = DecHex$(TbarPos(Tidx)) 'Add the position of each
If Len(Tb$) = 1 Then Tb$ = "0" + Tb$ 'trackbar as a 2 digit hex value
Packet$ = Packet$ + Tb$
Next
If Packet$ <> Old$ Then 'Lets only send if something changed
Old$ = Packet$
#io,Packet$
End If
Wait
'************************* Yep, that's the whole thing **************************
'********************************************************************************
[ErrNotify]
Notice "Unexpected error. Com";Str$(ComPortNumber);" ";ComError$ 'Report error and close
[quit]
If ComOpen = 1 Then Close #io
If pa = 1 Then Close #pa
If find = 1 Then Close #Find
END
Function DevCheck(cp,Challenge$,Expect$) 'Checks for device present by sending Challenge$, then
Port$ = "COM"; cp; ":"; Baud; ",n,8,1,ds0,cs0,rs" 'examining the reply for Expect$
Com = 1024
Reply$ = ""
Open Port$ for random as #io
#io, chr$(13)
#io, Challenge$ 'An unusual way to use the Timer. It will be available afterward.
Timer 80, [IoWait] 'Things sent to the COM port will not actually be sent out
Wait 'until we hit a wait statement! Then the wait must be long
[IoWait] 'enough to allow a reply. If funny things happen, try more time.
Timer 0
NumBytes = lof(#io)
If NumBytes > 0 Then 'We have a possible reply..
Reply$ = input$(#io, lof(#io))
If trim$(Reply$) = Expect$ Then 'Is it the response we expect?
DevCheck = 1 'Trim$ removes the terminating zero
Else 'from the Propeller string
DevCheck = 0
End If
End If
Close #io 'We should leave the computer the way we found it.
End Function
Function CheckCom(cp) 'Checks for COM port available. Thanks LB group members!
lpFileName$ = "COM"; cp 'Returns true if port can be opened
dwCreationDistribution = _OPEN_EXISTING
hTemplateFile = _NULL
CallDll #kernel32, "CreateFileA", _ 'This won't halt the program if the port doesn't exist.
lpFileName$ as ptr, _
dwDesiredAccess as ulong, _
dwShareMode as ulong, _
lpSecurityAttributes as ulong, _
dwCreationDistribution as ulong, _
dwFlagsAndAttributes as ulong, _
hTemplateFile as ulong, _
hFileHandle as ulong
CallDll #kernel32,"CloseHandle",_ 'Close the port, so we don't get an error later
hFileHandle as ulong,_
ret as boolean
If hFileHandle = _INVALID_HANDLE_VALUE Then
CheckCom = 0
Else
CheckCom = 1
End If
End Function
Function TbarPos(idx) 'Get trackbar position. Adapted from
'Liberty BASIC 4 Companion by Alyce Watson
HwTbar = HwndTbar(idx)
calldll #user32, "SendMessageA",_
HwTbar As uLong,_
1024 as long,_ 'Get Position
0 as long,_
0 as long,_
TbarPos As Long
End Function
Function Tbar(hWin,x,y,w,h) 'Make trackbar. Returns the handle. Adapted from
'Liberty BASIC 4 Companion by Alyce Watson
style = _WS_CHILD or _WS_VISIBLE or 1
calldll #user32, "CreateWindowExA",_
0 As long,_
"msctls_trackbar32" as ptr,_
"" as ptr,_
style as long,_
x as Long,_
y as Long,_
w as Long,_
h as Long,_
hWin as uLong,_
0 as long,_
hInstance as uLong,_
"" as ptr,_
Tbar As uLong
End Function
Sub TbarSet hwTbar, MaxVal, MinVal, TickFreq 'Set trackbar properties. Adapted from
'Liberty BASIC 4 Companion by Alyce Watson
calldll #user32, "SendMessageA",_
hwTbar as uLong,_
1032 as Long,_ 'Set Max Value
1 as long,_
MaxVal as Long,_
ret as Long
calldll #user32, "SendMessageA",_
hwTbar as uLong,_
1031 as Long,_ 'Set Min Value
1 as Long,_
MinVal as Long,_
ret as Long
calldll #user32, "SendMessageA",_
hwTbar as uLong,_
1044 as Long,_ 'Set Tick Frequency
TickFreq as Long,_
0 as Long,_
ret as Long
End Sub
[[code]]
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">We'll also add to our protocol. We will be able to set the brightness of the 8 LEDs using sliders for each. This means adding 8 digital to analog converters to our Propeller Demo board! Where do you order them? You Don't! You just download 'em... For FREE. Check out the Propeller Object Exchange at Parallax.com. We need the one called PWMx8. It comes zipped. Just unzip the files to the folder where you've been saving your Propeller code.</span></span><span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">
How do we add it then? We add two lines to the Propeller code! One goes in the OBJ section:</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;"> <span style="font-size: 8pt;"><span style="font-family: Courier New,monospace;">pwm: "PWMx8"</span></span></span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">The other goes in the Start object, right after <span style="font-size: 8pt;"><span style="font-family: Courier New,monospace;">Serial.start(31,30,0,9600)</span></span>:</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;"> <span style="font-size: 8pt;"><span style="font-family: Courier New,monospace;">pwm.start(16, 255, 20000)</span></span></span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">Wait a minute, I called Start an object.. Is this that evil object oriented programming? Well, yes, but for our purposes start is just a function.</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">The first function in a SPIN program gets run when the Propeller starts. We can call the other functions in a SPIN program, and they may return a value, just like in Liberty Basic. Note the PUB(lic) and PRI(vate) definitions before the function name. These just determine if other programs are allowed to use that function.</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">Using functions from other SPIN programs is exactly what we are doing with 'pwm.start(), and pwm.duty(). If you look at the OBJ section, you will see 'pwm: “PWMx8” '. This makes the SPIN program named 'PWMx8' a part of our program and names it 'pwm'. So to use the 'duty' function from 'PWMx8', all we need to do is 'pwm.duty()'</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">If we open 'PWMx8.spin' with the propeller tool, we can find the 'duty()' or the 'start()' function, see what they do, and even find out what the numbers we pass to them mean.</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">Our Run Basic users will find this familiar, as you may use ready made objects, or create your own, and use them in a similar way.</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">Of course, there's also the SPIN code that implements our analog addition to our protocol, so here's the whole SPIN program:</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;"> </span></span>
[[code]]
{{*************************************Propeller IO for LibertyBasic ***********************************
This is intended to run on the Demo Board. It will also run on a ProtoBoard with a Prop Plug. If you go the ProtoBoard route,
you will need to wire up your own LEDs if you want to see 'em go on and off from the LibertyBasic program.
This version adds analog support to the protocol.
}}
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
VAR
Byte Buffer[32] 'My serial buffer
OBJ
Serial : "FullDuplexSerial"
pwm: "PWMx8"
PUB Start |idx , digit , num , otpt
Serial.start(31,30,0,9600) 'Start the serial port through the programming adapter.
pwm.start(16, 255, 20000)
Dira[16..23]~~
repeat
If GetWord
If Strcomp(@buffer,@MyName) == -1
Serial.str(@Respond)
Buffer.byte[0] := 0 'Clear the buffer, Propeller strings are zero terminated.
If Buffer.Byte[0] == "l" ' 'l'ed command?
Repeat idx from 16 to 23
If Buffer.Byte[idx-15] == "1"
Outa[idx] ~~
Else
Outa[idx] ~
Buffer.Byte[0] := 0 'Clear the buffer
If Buffer.Byte[0] == "a" ' 'a'nalog command
otpt := 16
Repeat idx from 0 to 14 Step 2
Num := 0
Repeat digit from 1 to 2 'Dirty little hex converter
Num <<= 4
Num +=LookDownz(Buffer.Byte[idx+digit]:"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F")
pwm.duty(otpt++, Num)
Buffer.Byte[0] := 0 'Clear the buffer.
Serial.rxflush 'Clear out the serial driver. This also gets rid of unrecognized commands
PRI GetWord |idx, tmp
idx := 0 'This object attempts to recieve a return terminated string, or a full buffer.
repeat 'The string is put into Buffer, and the function returns the length of the string.
tmp := Serial.rxcheck
case tmp
13: 'Return encountered
Buffer.byte[idx] := 0
"0".."z": 'Characters only
Buffer.Byte[idx++] := tmp
if idx > 32
tmp := 13
while tmp <> 13
Result := idx
DAT
MyName Byte "Propeller?",0
Respond Byte "Present!",13,0 'Zero terminated strings
[[code]]
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">Note that we have not broken our protocol for the functions already present. In fact, you may run the new version of our program and set some LEDs on at less than full brightness. Then close the program. The LEDs will stay at exactly the same state. Then open the program from the last article and turn them on and off with the checkboxes. See what happens.. Pretty neat, huh?</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">That's the great thing about the Propeller. When you need a special function, pretty much all you need to do is add an object and start it!</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Times New Roman,serif;">Next time we'll look at ways to get and use information from our Propeller Demo board.</span></span>