Older Version Newer Version

RodBird RodBird Nov 25, 2015

<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-family: Courier New,monospace; font-size: 8pt;">[IoWait]</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>
[[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 long
    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: Courier New,monospace; font-size: 8pt;"> pwm: "PWMx8"</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><span style="font-family: Courier New,monospace; font-size: 8pt;">Serial.start(31,30,0,9600)</span><span style="font-family: Times New Roman,serif;">:</span></span>
<span style="display: block; text-align: left;"><span style="font-family: Courier New,monospace; font-size: 8pt;"> pwm.start(16, 255, 20000)</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>
[[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>