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
( 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