Liberty Basic and Propeller for hardware control.
Part 2

Richard Baggett

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.
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!
The problem in review:
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!
This happens because the label [IoWait] 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!
The Solution:
We could 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.
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.

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.

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

'************************** Propeller IO *********************
'Demonstrating functions and techniques for successful serial
'port projects
'**************** Find Propeller ***************
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
'********************** 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]
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.
'*************************** Here's the important part **************************
Timer 50, [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$
If Packet$ <> Old$ Then                         'Lets only send if something changed
    Old$ = Packet$
End If
'************************* Yep, that's the whole thing **************************
    Notice "Unexpected error. Com";Str$(ComPortNumber);" ";ComError$    'Report error and close
    If ComOpen = 1 Then Close #io
    If pa = 1 Then Close #pa
    If find = 1 Then Close #Find
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
        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
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 We need the one called PWMx8. It comes zipped. Just unzip the files to the folder where you've been saving your Propeller code.
How do we add it then? We add two lines to the Propeller code! One goes in the OBJ section:

pwm: "PWMx8"
The other goes in the Start object, right after Serial.start(31,30,0,9600):
pwm.start(16, 255, 20000)
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.
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.
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()'
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.
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.
Of course, there's also the SPIN code that implements our analog addition to our protocol, so here's the whole SPIN program:

{{*************************************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.
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000
Byte    Buffer[32]                                      'My serial buffer
  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)
    If GetWord
      If Strcomp(@buffer,@MyName) == -1
        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] ~~
            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
MyName  Byte  "Propeller?",0
Respond Byte  "Present!",13,0      'Zero terminated strings
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?
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!
Next time we'll look at ways to get and use information from our Propeller Demo board.