Skip to main content

A deep look at the internals of arduino IR library; how it works and how to implement one by yourself

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: nectrain.png 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. necmodulation 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: IR 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: 33 The sending algorithms works as follows : algo 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: rojKP.jpg Input output irsignal 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: state 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: 111.png 222.png 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. arr.png

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 : match.png match2.png A general decode algorithm can be as follows: decode

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: 40215504_2187249311550245_3283530406489489408_n 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): de.png The user code can be something like that : 40427107_2187252651549911_2300681042639454208_n The code in action: 40393911_2187253464883163_7244798947116449792_n Sending routine : send 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.

Comments

  1. Hard Rock Hotel and Casino, Casino and Spa in Wilkes Barre, PA
    Find 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: 순천 출장샵 $$

    ReplyDelete

Post a Comment

Popular posts from this blog

24h /12h Digital clock using ic555 ,7490 decade counters and 7447 bcd-7seg decoders tutorial (1/2)

This photo is from this instructables project here There are many stuff on the internet about this project, but we are gonna add something or two. This circuit was a school project and it was a 24h clock, but i decided to extend it to 12h and 24h with the transferring between them using a switch so you can choose whatever mode you want. Anyway, i made this circuit just for fun so i just simulated it on proteus, i.e we're not going into pcb design. The final project First of all we're going to divide this tuorial into 3 parts: - How it works in the 24h mode. - How it works in the 12h mode. - How to transfer between them using simple switch. So... let's get started with the first one..

24h /12h Digital clock with alarm using ic555 ,7490 decade counters and 7447 bcd-7seg decoders tutorial (2/2)

Hello again! in the first part we've seen how the basic 24h clock works (the hour coulmn counts to 24 and then resets to 00). It was quite easy and there were no problems, but let's extend the idea a little bit. What if we want to design a 12h clock?  well.. it's the same idea but there was a little problem which is: when the hours reachs 12 and counts to the end of the hour 12.59 we don't want it to reach 13, we rather want it to reset to...? 01 not 00 so i had to make a small trick you'll see later. How the circuit works? It's the same circuit as the first part   but we've to edit the hours coulmn so it resets to 00 when it reachs 13 and then clocks the first coulmn of hours so it becomes 01 .. simple! So that's how i made it , the two AND gates U9 and U10 detect the case when the hours coulmn is 13 and their output(A) then will be 1.  Output A has two jobs to do : - The first is to reset the boths hours coulmn so it's