Introduction
Last few days, I have been interested in IR remotes and how they work and whether I can write a code that sends a command to my crappy Chinese receiver. I had no idea about IR remotes or how the protocol works so I had to learn a few things about it first then learn what is the exact protocol used by my receiver.
This isn't an IR tutorial, there are many good tutorials that explains the concept very well out there, So I will just go quickly through it, then try to understand how some existing codes work.
How does IR remote works?
When you press a button on your remote control, it emits an infrared light with a specific pattern that contain information to tell your commanded device what to do.
When I say pattern, I mean "Protocol", the protocol tells us how to send a specific information to a specific device.These information usually are the command (volume up,down,OK...) and the address or the device these information are sent to. There are many IR protocols out there like Sony, Philips, NEC...etc.
Let's take NEC protocol as example:
Every IR packet consists of the following:
-Header mark
-Header space
-Actual data (1s and 0s).
The header tells the receiving device that there are some data coming so you should get ready.
Data are sent in the form of 1s and 0s, obviously, How 1 and 0 are represented differs from a protocol to another, In NEC for example to send a 1 you send a mark of constant period 560 uS followed by a space of 1690 uS, And to send a 0 you send a mark of 560 uS followed by a space of 560 uS. These timings differ from a protocol to another.
source:
SB-Projects
As you see in the above photo, the voltage at the mark period is not set at constant voltage level, It's a PWM of frequency of 40 Khz, in other words to send a mark of a period of 560 uS out of a specific pin, you connect an IR led to that pin, enable a PWM of 40 Khz frequency for 560 uS then you disable the PWM, this is called Modulation.
The reason for using modulation is to prevent interference with other IR signals emitted from other IR sources.
According to
SB-Projects, the duty cycle of the PWM should be 1/4 or 1/3 (I used 1/4 in my code).
What I'm trying to do?
Now that I have understood how IR works, I want to write a simple code that tells my receiver to change the channel, So I need to write a code that receives the IR code from my remote control so that I can know the specific data that tells to to change the channel, store it in hexadecimal format, then write a simple code that sends a specific hexadecimal value so I can send any command that I know its hexadecimal (useless machine??).
A look at ken shirriff's library
When I googled for some codes already written for that purpose, I found arduino library written by
ken shirriff, he provided a good tutorial about its internal works but I wanted to write about it in more detail. Code can be found
here.
His code is divided into 2 parts : sending and receiving, I'm not going through high level routines or how to use the library, instead I will go through the low level stuff, the ones that are closest to 1s and 0s level.
The sending routine:
The library supports many protocols, each protocol has a separate module:
In each module, you can find the basic methods that perform sending and decoding (more about decoding later) works. Let's have a look at
NEC module for example:
[gallery ids="50,51" type="square" columns="2"]
The sending method mainly depends on 2 functions mark() and space(), you can find the definition of these functions in
irSend.cpp:
The sending algorithms works as follows :
So, if you want to write a code that sends IR codes, you need to implement mark() and zero() functions, which just enables 40 Khz PWM on a specific pin for a specific period and write some #defines for the wanted protocol.
Now let's move on to the more interesting part (to me), the receiving routines.
The receiving routine:
If you want to receive IR code from a specific remote control, you need to measure the periods of marks and zeros received at your pin, but you remember that the IR code is modulated at 40 Khz, right? well we don't want that PWM frequency and just need to know when a mark or zero is detected. That's why we use an IR receiver like this one:
Input output
Notice that the output is zero when the input is the PWM signal. That means that if you are reading a zero voltage at the receiving pin, you are receiving a MARK.
Basically, the receiving routine works by measuring the periods of marks and zeros received on the receiving pin and store these periods in an array (named "rawbuf").
This array is then decoded using decode() method which is found in each protocol module.
How it measures MARKS and SPACES periods?
The library uses timer 2 to fire an interrupt every 50 uS, The ISR(interrupt service routine) of this interrupt updates the following state machine:
The state machine starts in the idle state, If a mark detected after a sufficient gap period, it stores the gap period in the array "rawbuf[]" as the first element and moves to "
mark" state.
It moves back and forth between "
mark" and "
space" states until the packet is finished, it knows that packet is finished if it's in the "
space" state and doesn't receive a mark for a long period (longer than GAP_TICKS which is a constant defined in the code).
when the packet is finished it moves to the "
stop" state and doesn't receive any codes again until user calls "resume()" that updates the current state to "
idle" again.
There's a fifth state to handle over flow situation but I have ignored it for now.
You can find the code in the module
IRremote.cpp:
It keeps track of the period using a variable called "timer" which increased by 1 every tick (interrupt). whenever we are to change the current state, we store the current "timer" value in the rawbuf[] array at the current index, increase index by one to move to the next slot and reset the timer to measure the new state period.
Since the tick happens every 50 uS, "timer value" represent the period in 50 uS period, So you need to multiply it by 50 to get the real period in micro seconds, but that's not an issue for now.
Note that to store the total periods of a given protocol, you need an array of size 2*Number of bits + 3, the first "2 because every bit (1 or 0) has a mark and a space. The "3" is for the gap period, header mark, and header space.
we check for overflow by making sure that the index of the array is smaller than the size of the array.
Now we have an array of periods we need to turn it into a hexadecimal code using "decode()" method.
How decode() works?
The first thing you want to do is to check if the header periods(mark and space) that you measured
matches the actual period of the specific protocol.
So we need a function that matches the period of the measured marks with the actual marks and the measured spaces with the actual space, and the reason we need a function not just an equality checking (if(rawbuf[1]*50 == header_mark_period ), for example) is that our measurements are not very precise. We will allow for a tolerance of .25 for example. So if we received a mark of 560 uS, the ideal measured value in 50 uS ticks is 560/50 = 11.2 (11), but with .25 tolerance any value between (1.25*560)/50 and (.75*560)/50 is just fine.
You can find this in
IRremoteInt.h and IRremote.cpp :
A general decode algorithm can be as follows:
Let's code our own basic library
I have written a basic code to make sure that I got things right. The code is written for stm32f103c8t6 (blue pill) microcontroller. It uses 3 timers: the first one is for receiving code(50 uS ticks) , the second one is for generating PWM, and the third one is used to implement a precise delay function.
The receiving state machine:
When the module receives a valid code, it decodes it and stays in the idle state, doesn't receive any other code until the user gets the decoded data using IR_getRecievedCode().
Decoding algorithm(for NEC):
The user code can be something like that :
The code in action:
Sending routine :
When I saved the above hexadecimal code(in the screenshot) received from my satellite receiver and sent it again using my Code, it worked and changed the channel! whoa!
The code and more details on Github :
Code.
Sources :
SB-Projects.
Ken shirriff's tutorial
I hope this added something to your knowledge and helped you understand the subject. If so, please leave a comment with your ideas.
We want more, very cool stuff
ReplyDeleteThank you samir, hope you enjoyed it :D
DeleteHard Rock Hotel and Casino, Casino and Spa in Wilkes Barre, PA
ReplyDeleteFind rooms from $52 to 사천 출장안마 $35 at 경기도 출장샵 Hard Rock Hotel and Casino, Casino and Spa in Wilkes Barre, PA. Free WiFi, parking 광명 출장마사지 meters and a seasonal outdoor swimming Rating: 7.5/10 · 2,128 아산 출장샵 reviews · Price range: 순천 출장샵 $$