So, I bought this:
It's an IR Receiver module that operates on IR signals modulated at 38 kHz. It has three terminals for Vcc, Ground, and signal Out. The Out signal is normally high ( digital one ). When it detects a pulse modulated width signal with a period of 38 kHz ( with a duty cycle somewhere around 1/3 to 1/2 ), the out signal goes low ( digital 0 ). This component has a great range and arc, and it ignores just about anything else around it. It's typical of the IR receiver on your TV, stereo, etc.
I had an old JVC stereo remote, and I wanted to see if I could decode its signals with this component on my Pic 16F88. I did a bit of Google research on IR protocols. The first thing to note in hindsight: If you can, use a Sony remote!! Their protocol is by far the easiest one I found. It has clear, easy to detect boundaries that all focus on what will be the low signals you receive. That said, I had a JVC remote lying around that wasn't connected to anything I needed. The information on this protocol was not as clear as it could have been. I'll attempt to clear some of that up here if I can.
JVC uses space-width codification for its transmission. This means that the length of time a signal is high or low is of great importance. Everything is based on a basic time unit, T. For JVC, this time interval is defined as 527 us +/- 60 us. For my tests,
we split the difference and assume that T is 560 us. From the anals of days past, the protocol wants to send a start sequence. This sequence was intended to help receivers calibrate themselves to the sender. It is basically unpredictable when this sequence will be sent. Every remote seems different. We will eventually ignore it. The bulk of a send consists in 16 bits ( 2 bytes or 1 word ) of information sent. The first 8 bits is the address. This is an identifier for the device. The next 8 bits is the command. Both of these bytes are sent Least Significant Bit first. Here's the tricky part: The documentation I found on the Web doesn't say much about the end of the packet. There IS A STOP BIT!!! More on that in a bit.
From the point of view of the IR component ( and microcontroller ), the signal is usually high. The tendency is to want to pay attention to the low signals. With Sony, this is true and easy. With JVC, the time significant signals are high is what is
important. It turns out that there are 2 low "marks" and 4 high "spaces" to watch for:
-----------------------
Times
-----------------------
Low Marks:
----------
Header Mark: 8.44 ms
Bit Marks: 527 us ( T )
High Spaces:
------------
Header Space: 4.22 ms
Zero: 527 us ( T )
One: 1.58 ms ( 3T )
Word Space: 12.53 - 29.54 ms
Here's what a transmission looks like from the point of view of a microcontroller:
Every bit begins with the line going low for time T. The ONLY time the line ever goes low is to signal either a header or the start of a bit. The trick is to know where we are in a transmission. Retransmits ( any transmit where we don't have a header ) just start firing bits. We need to know that we are at the FIRST bit of the tranmission. The only way to be sure of this is to make sure that no bit transmission stays high too long.
Here's how it breaks down. We look for the signal to go low. We wait for it to go high. We wait for 1.5 T. If the signal has gone low, we have a zero transmitted. We are halfway into the bit mark for the next bit. If the signal is still high, we wait 2T ( total time elapsed 3.5T ). If the signal has gone low, we have a one and we are halfway into the bit mark for the next bit. If it is still high, we are in either the header or the wait time after a stop bit. Either way, we are mis-aligned, so we return and wait for a better start position.
The stop bit is a normal pull low for time T. Then, a time of 12.53 - 29.54 ms will be high.
Here's what works:
Signal goes low, call getIrData.
getIrData
; setup, init values
movlw 0x80 ; load dataPacket with 128.
movwf dataPacket
clrf addressPacket
getBitLoop
; Wait for line to go high
btfss irPortBit
goto getBitLoop
testIrHighTime
; wait for 1.5 T
call delay700us
btfss irPortBit
goto bitIsAZero ; shift in a zero
; now wait 2T. This will either put us .5T into the start mark of the next bit, or an error
call delay1ms
btfss irPortBit
goto bitIsAOne
; Here, we are in a misalignment zone. We are either in the high time of a start bit
; or in the rest time between repeats. Clean up and return.
return
bitIsAZero
; Signal is now low.
; bit is a zero. rotate zero in.
bcf STATUS,C
rrf dataPacket,F
rrf addressPacket,F
; if Carry is set, we've acquired 16 bits and we are done.
btfss STATUS,C
goto getBitLoop
; if it is set, go wait for the stop bit.
goto waitForStopBit
bitIsAOne
; Signal is now low.
; Bit is a one. rotate one in.
bsf STATUS,C
rrf dataPacket,F
rrf addressPacket,F
; if Carry is set, we've acquired 16 bits and we are done.
btfsc STATUS,C
goto getBitLoop
waitForStopBit
; signal is low. Wait for high
btfss irPortBit
goto waitForStopBit
; Now we can send our packets
Here is the full code: Read IR Test Code
In the init of getIrData, we put a one in the eighth bit of the addressPacket. The data is coming to us address, then data, and each will come least significant bit first. So, we will rotate each data bit we receive through the data packet, then the address packet. When we use the rrf rotate function, it takes what is in the Carry bit of the STATUS register and pushes that on the eighth bit of the dataPacket. If the bit received is a one, the code puts a one in Carry and shifts it onto the eighth bit of dataPacket. This shifts every bit to the right. The one that is placed on the eighth
bit of dataPacket will eventually pop out and go into the Carry ( rotate is like a loop of the register and the Carry flag ). The code can check the Carry bit for a one to see if all 16 bits have been received.
The testIrHighTime routine serves two very important functions. First, it waits 1.5T ( ~700 us ). This places the code about .5T past where a zero signal would end. If the signal has gone low already, the code knows we received a zero. If it is still high, the code waits an additional 2T. This places it .5T past where a one would end. If it has gone low, the code processes a one. If it is still high, then one of two things has happened. Either a start header is being sent, or the signal is in the high time of a stop bit when the code was not expecting it. In either case, we simply return. The
next low signal will pop in again. Eventually, it will align at the start of a signal and read the packets.
Here is a picture of a simple test rig. Note, the numbers being displayed are wrong. I was still fiddling with the routine when I took the pictures.
Here are the codes for my stereo remote:
Address: 10011111 ( Decimal 159, Hex 0x9F )
Value Code Decimal Hex
1 00100001 33 0x21
2 00100010 34 0x22
3 00100011 35 0x23
4 00100100 36 0x24
5 00100101 37 0x25
6 00100110 38 0x26
7 00100111 39 0x27
8 00101000 40 0x28
9 00101001 41 0x29
10 00101010 42 0x2A
10+ 00101110 46 0x2E
Disk:
1 11110001 241 0xF1
2 11110010 242 0xF2
3 11110011 243 0xF3
4 11110100 244 0xF4
5 11110101 245 0xF5
6 11110110 246 0xF6
Power 00000000 0 0x00
Volume Up 00000001 1 0x01
Volume Down 00000010 2 0x02
EDIT:
On Feb 3rd, 2012, I received an inexpensive logic analyzer that I had ordered. It is the Open Workbench Logic Sniffer. I was able to capture the signals coming in from the IR remote. Here is a shot of the header, address, command for the "10" key:
I'm pretty stoked to have this little guy even if the memory is small.
Search This Blog
Tuesday, December 20, 2011
Saturday, December 17, 2011
Programming 16F Series Pics WIth pk2cmd
Okay, this has happened before. I solved it and forgot how I did it. Lesson learned. Here is the problem and solution. I'm using a PicKit2 programmer with the open source pk2cmd software on Linux to program my Microchip PICs. Most of the time, I have no problem. Today, my 16F88 simple failed to do an erase and verify blank. After a ton of Googling, I found a post that suggested that the -X switch may help. This switch raises VPP before VDD. I tried it, and it works! Here are some samples:
pk2cmd -ppic16f88 -x -e
pk2cmd -ppic16f88 -x -c
pk2cmd -ppic16f88 -x -mcp -fmyCoolProg.hex
More later!
EDIT: So, I had the same troubles with my PIC18F2455 the other day. This freaked me out a bit because I don't have the picp software for my PicStart Plus programmer anymore, and I don't think it can program my 18F2455s. I tried the trick above, but it didn't work. After a few deep breaths, I fiddled around a bit more. The PicKit2 could identify the chip correctly when I used autodetect ( -p ). I tried an erase and verify with autodetect, and it worked! So, if you have trouble, first try autodetect. If that doesn't work, try the voltage trick above. If it does work, try using autodetect:
pk2cmd -p -e
pk2cmd -p -c
pk2cmd -p -mcp -fmyCoolProg.hex
Once this has worked, the PicKit2 was able to take a chip reference again with no problem. I was programming 2 different chips ( 16F88 and 18F2455 ) back and forth rather rapidly. I think the PicKit2 just got confused or failed to reinitialize after each programming effort.
pk2cmd -ppic16f88 -x -e
pk2cmd -ppic16f88 -x -c
pk2cmd -ppic16f88 -x -mcp -fmyCoolProg.hex
More later!
EDIT: So, I had the same troubles with my PIC18F2455 the other day. This freaked me out a bit because I don't have the picp software for my PicStart Plus programmer anymore, and I don't think it can program my 18F2455s. I tried the trick above, but it didn't work. After a few deep breaths, I fiddled around a bit more. The PicKit2 could identify the chip correctly when I used autodetect ( -p ). I tried an erase and verify with autodetect, and it worked! So, if you have trouble, first try autodetect. If that doesn't work, try the voltage trick above. If it does work, try using autodetect:
pk2cmd -p -e
pk2cmd -p -c
pk2cmd -p -mcp -fmyCoolProg.hex
Once this has worked, the PicKit2 was able to take a chip reference again with no problem. I was programming 2 different chips ( 16F88 and 18F2455 ) back and forth rather rapidly. I think the PicKit2 just got confused or failed to reinitialize after each programming effort.
Saturday, December 3, 2011
Pulse Width Modulation (PWM) Calculations for PIC Micros
Pic micros are really awesome little chips, and Microchip does a really good job documenting them. Usually. Sometimes, though, their docs assume something that I just completely fail to know about. One such area was using their built in PWM on the CCP modules. I could get them working. I just cheated and used this Pic PWM Calculator. But, reading their data sheets, I could never understand how the values were computed. Tonight, I finally took some time to get it. Here are the fruits of my labors.
First, a review. PWM wants to set up a period and a duty cycle. The period is the length of time of one pulse cycle. The duty cycle is the percentage of time in the period that the pulse will be high. For Pic micros, the period is set with register PR2. The duty cycle is a bit more complex. It is shared between a register, such as CCPRnL and two bits in a control register such as CCPCONn<5:4>. The lease significant bits are stored in the CCPCON<5:4> bits. I'll talk about the simple Pic16F88. It only has one CCP module, so the CCP register is CCPR1L and the control register is CCPCON.
First, the period must be set in register PR2. For this example, we will set a 38 kHz period for a Pic16F88, running at 8 MHz. The data sheet says that:
PWM Period = [(PR2) + 1] • 4 • TOSC • (TMR2 Prescale Value)
A bit of algebra turns that into:
PR2 = ( PWM Period / ( TOsc x 4 x TMR2 Prescaler ) ) - 1
Definitions:
PWM Period = 1 / PWM frequency
TOsc = 1 / MCU clock frequency
So, this looks like this for our 8 MHz Pic for a 38 kHz PWM:
PR2 = ( ( 1/pulse frequency in Hz ) / ( 1/clock speed x 4 x prescaler value ) ) - 1
PR2 = ( ( 1/38,000 ) / ( 1/8,000,000 ) x 4 x 1 ) ) - 1
PR2 = ( ( .0000263 ) / ( .000000125 ) x 4 x 1 ) - 1
PR2 = 52.6 - 1
PR2 = 51.6 ( use 52 )
So, PR2 will get the value decimal 52 ( 0x34 ). The prescaler worked with a simple x1 modifier, so TM2Con will be 0x04 ( 0b00000100 ). If the value had been greater than 255, the TMR2 prescaler allows values of 1, 4, and 16. Plug each value into the equation to get a PR2 value between 1 and 255. If the value is still out of range, the PWM module cannot provide that frequency. This is especially true of low frequencies needed for servo control. It is fairly easy to implement those in software. I've written code that controls 16 servos in software with interrupts. I'll post that at some point.
Now, for the duty cycle. This is the part that drove me nuts. The data sheet states that the duty cycle is:
PWM Duty Cycle = (CCPR1L:CCP1CON<5:4>) • TOSC • (TMR2 Prescale Value)
I couldn't for the life of me get this to translate into sensible values. I used the PWM calculator to reverse engineer the equation. Here are the definitions I came up with.
Duty Cycle = ( 1/ pwm freq ) x percentage
TOsc = 1 / clock freq.
So, if I have a FOsc of 8 MHz and want 38 kHz at 50% duty cycle:
Duty Cycle = ( 1 / 38,000 ) x .50 = .0000132
TOsc = .000000125
(CCPR1L:CCP1CON<5:4>) = .0000132 / .000000125 = 105.6
use 106, in binary = 0b1101010
So CCPR1L gets 0b11010 and CCP1CON<5:4> gets the lsbs: 0b10
No matter what, the least significant bits go into CCP1CON<5:4>. The rest goes into CCPR1L. Note that the value for duty cycle is only 7 bits long, so my resolution is 7 bits for 38 kHz at 8 MHz. That's it!
EDIT:
I've been looking this over again. The Duty Cycle is the percentage of the period frequency. So, 50% of 38KHz is:
1/38000*.5 = 1.3157e-5 or ~ 0.00001316
0.0000132 * 8e6 = 105.6
This seems easier to think about. It's just 1/pwm freq. * percentageAsDecimal * FOsc / prescaler.
First, a review. PWM wants to set up a period and a duty cycle. The period is the length of time of one pulse cycle. The duty cycle is the percentage of time in the period that the pulse will be high. For Pic micros, the period is set with register PR2. The duty cycle is a bit more complex. It is shared between a register, such as CCPRnL and two bits in a control register such as CCPCONn<5:4>. The lease significant bits are stored in the CCPCON<5:4> bits. I'll talk about the simple Pic16F88. It only has one CCP module, so the CCP register is CCPR1L and the control register is CCPCON.
First, the period must be set in register PR2. For this example, we will set a 38 kHz period for a Pic16F88, running at 8 MHz. The data sheet says that:
PWM Period = [(PR2) + 1] • 4 • TOSC • (TMR2 Prescale Value)
A bit of algebra turns that into:
PR2 = ( PWM Period / ( TOsc x 4 x TMR2 Prescaler ) ) - 1
Definitions:
PWM Period = 1 / PWM frequency
TOsc = 1 / MCU clock frequency
So, this looks like this for our 8 MHz Pic for a 38 kHz PWM:
PR2 = ( ( 1/pulse frequency in Hz ) / ( 1/clock speed x 4 x prescaler value ) ) - 1
PR2 = ( ( 1/38,000 ) / ( 1/8,000,000 ) x 4 x 1 ) ) - 1
PR2 = ( ( .0000263 ) / ( .000000125 ) x 4 x 1 ) - 1
PR2 = 52.6 - 1
PR2 = 51.6 ( use 52 )
So, PR2 will get the value decimal 52 ( 0x34 ). The prescaler worked with a simple x1 modifier, so TM2Con will be 0x04 ( 0b00000100 ). If the value had been greater than 255, the TMR2 prescaler allows values of 1, 4, and 16. Plug each value into the equation to get a PR2 value between 1 and 255. If the value is still out of range, the PWM module cannot provide that frequency. This is especially true of low frequencies needed for servo control. It is fairly easy to implement those in software. I've written code that controls 16 servos in software with interrupts. I'll post that at some point.
Now, for the duty cycle. This is the part that drove me nuts. The data sheet states that the duty cycle is:
PWM Duty Cycle = (CCPR1L:CCP1CON<5:4>) • TOSC • (TMR2 Prescale Value)
I couldn't for the life of me get this to translate into sensible values. I used the PWM calculator to reverse engineer the equation. Here are the definitions I came up with.
Duty Cycle = ( 1/ pwm freq ) x percentage
TOsc = 1 / clock freq.
So, if I have a FOsc of 8 MHz and want 38 kHz at 50% duty cycle:
Duty Cycle = ( 1 / 38,000 ) x .50 = .0000132
TOsc = .000000125
(CCPR1L:CCP1CON<5:4>) = .0000132 / .000000125 = 105.6
use 106, in binary = 0b1101010
So CCPR1L gets 0b11010 and CCP1CON<5:4> gets the lsbs: 0b10
No matter what, the least significant bits go into CCP1CON<5:4>. The rest goes into CCPR1L. Note that the value for duty cycle is only 7 bits long, so my resolution is 7 bits for 38 kHz at 8 MHz. That's it!
EDIT:
I've been looking this over again. The Duty Cycle is the percentage of the period frequency. So, 50% of 38KHz is:
1/38000*.5 = 1.3157e-5 or ~ 0.00001316
0.0000132 * 8e6 = 105.6
This seems easier to think about. It's just 1/pwm freq. * percentageAsDecimal * FOsc / prescaler.
Subscribe to:
Posts (Atom)