Introduction: A Custom Shield for the Propeller ASC - Infra-Red Remote Control - and Multiple COGS!

In my first project we added a 2-line LCD display to the Arduino Shield Compatible (ASC) board.
    (  https://www.instructables.com/id/Propeller-Power-for-the-Arduino/ )

In this project we will also introduce the use of multiple COGS, a unique feature of the Parallax Propeller chip.

LCD Display:
The display  is available from Radio Shack and only needs three wires to run.

IR Detector:
Now, to provide user control and input to the ASC system, we will add an infra-red detector so
that a common universal TV remote control unit can be used.  This code uses the SONY device
protocalls, so set the remote TC code to the first SONY code listed in its' booklet.

I am using the PNA4602 IR detector chip. It looks a lot like a normal three-legged  transistor, but it isn't.
It contains on-chip 38 KHZ demodulator and filters which makes it very easy to use with common TV
remotes.    They cost about $2 each.
All the fascinating technical details can be found at Ada Fruit at :  http://learn.adafruit.com/ir-sensor

Proto-Shield:
To mount the detector, I'm using a Seeed "Proto-Shield" from Radio Shack.
It's a little overkill for such a small circuit, but it will grow more stuff in the near future.

A 3x2 female header was installed as a socket for the decoder chip.
That's the IR detector hiding in the upper right corner under that mess of wires.

Since the Proto-Shield pins plug into the ASC socket headers it is not feasible to use
normal wire-wrap techniques.  That would take up too much of the length of the pin,
making it questionable if the shield could be plugged into the host ASC board at all.
So I'm using a modified technique - a single wrap around the pin, then solder.
It's delicate work.  But for the moment. it's only three wires, power (3.3V), ground, and
signal, so no problem here.  See the wires photo.

Software:
The demo software uses several objects from the Parallax Object Exchange (OBEX).
     IR remote driver       : "IR_Remote.Spin"
     LCD driver                 : "serial_lcd.spin"
     Numbers                   : "simple_numbers.spin"

These can be downloaded from the OBEX, but I have included them in this ZIP file attached
to this Instructible.

This demo program  reads the remote control codes and shows the results on the LCD display.
That part is fairly simple and straight forward.

BUT...
What if we wanted the LCD display to turn off the back-light after a few seconds of inactivity?

Normally, we'd use an interrupt handler for that. 
A periodic interrupt would provide the time base, and the handler would count down to determine
when it was time to shut down the back lighting.

But the Propeller doesn't USE interrupts.  That's what the multiple cogs are for.  It takes a bit of a
paradigm shift to wrap your mind around parallel processing, but it's well worth the effort.

In this demo program, we will spin off a small procedure to another cog.
It will provide a time base, and count down a time-out variable.
You can find this code at the end of the demonstration program.
Look for -  PUB Timeout.
And note that all that does is wait for one second, then check to see if the LCDtime variable
is greater than zero, and decrement it if it is.  That's it/.

Also, consider this; what if you needed two (or more) completely different time bases?
Assign each task to a different COG and they run concurrently and completely independently of each other.
It just doesn't get any simpler.

Spin may look a bit strange at first, but it's easy to learn and does the job quite well.

But remember that indentation defines the structure of the program, and carelesly changing the
indentation will completely change the flow of the program!  So be careful of that.

The Propeller Tool (with all documentation) can be downloaded for free from Parallax at:
http://www.parallax.com/tabid/832/Default.aspx


Demo Source Code:

{{ ASC_LCD_IR_TimeOut.spin }}
CON
  _CLKMODE = XTAL1 + PLL16X        ' 80 Mhz clock
  _XINFREQ = 5_000_000
 IRpin         = 23                ' IR Receiver - Propeller Pin
 LcdTimeOut    = 10                
 LcdOn1        = $16               ' LCD on; cursor off, blink off
 LcdLine0      = $80               ' move to line 1, column 0
 LcdLine1      = $94               ' move to line 2, column 0
 POL           = 15                ' proof of life LED

OBJ
  ir      : "IR_Remote"
  lcd     : "serial_lcd"
  num     : "simple_numbers"
  
VAR
  byte IRcode                        ' keycode from IR Receiver here
  byte LCDtime
  long IRcog
  long Stack1[6]                     ' Stack space for LCDtimeout cog
 
PUB Init | freq, index, cog, lcode
'init LCD
  if lcd.start(0, 9600, 2)
    lcd.putc(lcd#LcdOn1)              ' no cursor
    lcd.cls
    lcd.backlight(1)
    lcd.str(string(LcdLine0, "IR Remote"))

'Proof of Life      
  dira[pol]~~
  !outa[pol]

'Start Timeout
  LCDTime := LCDTimeout              ' reset timeout each time a key is pressed
  cognew (TimeOut, @stack1 )

'Init IR remote
  IRcog := ir.Start(IRpin, @IRcode)  ' Pin of IR receiver, address of variable

  if IRcog > 0
      repeat
        If LCDtime >0
           LCD.backlight(1)          ' turn it on       
        else                         ' timed out
           LCD.backlight(0)          ' turn if off
        If IRcode <> ir#NoNewCode    ' we have a key code
           lcode := IRcode
           ir.Start(IRpin, @IRcode)  ' set up for next code
           if LCDtime := 0           ' if it was off,
              LCD.backlight(1)       '    turn it back on
          LCDTime := LCDTimeout      ' reset timeout each time a key is pressed           
           lcd.gotoxy(1,1)          
           case lcode
              ir#one     :  lcd.str(string("<1>   "))
              ir#two      :  lcd.str(string("<2>   "))
              ir#three   :  lcd.str(string("<3>   "))
              ir#four     :  lcd.str(string("<4>   "))
              ir#five      :  lcd.str(string("<5>   "))
              ir#six       :  lcd.str(string("<6>   "))
              ir#seven :  lcd.str(string("<7>   "))
              ir#eight   :  lcd.str(string("<8>   "))
              ir#nine    :  lcd.str(string("<9>   "))
              ir#zero    :  lcd.str(string("<0>   "))
              ir#chUp  :  lcd.str(string("chUp "))
              ir#chDn  :  lcd.str(string("chDn "))
              ir#volUp  :  lcd.str(string("volUp"))
              ir#volDn  :  lcd.str(string("volDn"))
              ir#mute   :  lcd.str(string("mute "))
              ir#power :  lcd.str(string("power"))
              ir#last      :  lcd.str(string("last "))
              other        :  lcd.str(string("      "))

        waitcnt((clkfreq / 1000) * 30 + cnt)
        !outa[pol]

PUB Timeout
  Repeat                            ' loop forever
      waitcnt(clkfreq + cnt)        ' wait one second
     if  byte[@LCDtime] =>   1      ' keep counting       
         byte[@LCDtime] --          ' down