Saturday, March 2, 2013

Programming I2C

Although you can perform simple i2c reads and writes using the command line tools i2cget and i2cset, for a more integrated approach you can use a programming language to talk to the bus.

The are dozens of languages which make claims about ease of use and learning etc. and I am sure you can program i2c from them.

What I will demonstrate here is the simple way to do it from c. Although I don't aim to teach how to program in c, I will try and explain what the code is doing so you can follow along even if you are new to c.

This will use some basic i2c read and writes as described at  http://www.kernel.org/doc/Documentation/i2c/dev-interface
We will also need to perform some IO Control (ioctl) which are i2c specific.

First we need some code to get us started. The #include basicly make certain function calls and constants available to the rest of our program.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>


All of our code will live in the main function for now. main() is where all c programs start.

We need some variables which must be declared at the start of the function.
int main( int argc, char **argv )
{
        int i;
        int r;
        int fd;
        unsigned char command[2];
        unsigned char value[4];
        useconds_t delay = 2000;

        char *dev = "/dev/i2c-1";
        int addr = 0x48;


The [ ] syntax means an array so char command[2] is actually a variable which can hold 2 char values.

Some of our variables have been initialised to specific values so they are ready to use. The ones which have not been initialised will contain random values so we must assign a value to them before they can be used.

0x48 means hexadecimal 48 (which is decimal 72).

Next we print out a banner to show that the program is running
printf("PCF8591 Test\n");

The we get down to business and open the i2c device
        fd = open(dev, O_RDWR );
        if(fd < 0)
        {
                perror("Opening i2c device node\n");
                return 1;
        }


and select our slave device
        r = ioctl(fd, I2C_SLAVE, addr);
        if(r < 0)
        {
                perror("Selecting i2c device\n");
        }


Now we have an infinite loop
         while(1)
        {

There will be no way to end the program except by pressing Control C.

Next we have another loop which will run four times
                for(i = 0; i < 4; i++)
                {

Then we build a command for the pcf8591. The value of this is specified in the data sheet http://doc.chipfind.ru/pdf/philips/pca8591.pdf

In the first 8 bits of the command we will enable the analog output bit (0x40) and select which of the 4 inputs to read ((i + 1) & 0x03). We do a bitwise or to combine these values together with the | symbol.
command[0] = 0x40 | ((i + 1) & 0x03); // output enable | read input i

The // is the start of a comment so you can explain you code to the reader.

In the next 8 bits we increment the value for the analog output
command[1]++;

Now we are ready to send the command to the i2c bus
r = write(fd, &command, 2);

It is not clear why, but we need to wait for the command to be processed
usleep(delay);

Now we are ready to read a value. Remembering that the read is always one value behind the selected input (hence the +1 we used above).
r = read(fd, &value[i], 1);
if(r != 1)
{
        perror("reading i2c device\n");
}
usleep(delay);


Then we end the loop
        }
and now we can print out our results
        printf("0x%02x 0x%02x 0x%02x 0x%02x\n", value[0], value[1], value[2], value[3]);

end our infinite loop
    }

and although we may never reach here, we will clean up and quit.
  close(fd);
  return(0);
}


Now, if you enter all the code into a file called pcf8591d.c (you can copy the complete code as show below) then you are ready to compile it with this command
gcc -Wall -o pcf8591d pcf8591d.c
This says to compile the .c file and write the output (-o) to pcf8591d (if you don't specify an output file the default of a.out will be used which can be a but confusing). -Wall will make sure all warnings are printed out by the compiler.

Assuming the compile (and link) was successful you are ready to run
./pcf8591d
and you should see output like this:
PCF8591 Test
0x5f 0xd3 0xac 0x80
0xc1 0xd3 0xae 0x80
0xc1 0xd3 0xb0 0x80
0xc1 0xd3 0xb2 0x80
0xc1 0xd3 0xb7 0x80
0xc1 0xd3 0xba 0x80
0xc1 0xd3 0xde 0x80
0xc1 0xd3 0xdc 0x80
0xc1 0xd3 0xe0 0x80

To stop the program press ^c (Control + C).

Here is the complete source code:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>



// Written by John Newbigin jnewbigin@chrysocome.net

// Based on info from http://www.kernel.org/doc/Documentation/i2c/dev-interface
// And http://doc.chipfind.ru/pdf/philips/pca8591.pdf

int main( int argc, char **argv )
{
        int i;
        int r;
        int fd;
        unsigned char command[2];
        unsigned char value[4];
        useconds_t delay = 2000;

        char *dev = "/dev/i2c-1";
        int addr = 0x48;

        printf("PCF8591 Test\n");

        fd = open(dev, O_RDWR );
        if(fd < 0)
        {
                perror("Opening i2c device node\n");
                return 1;
        }

        r = ioctl(fd, I2C_SLAVE, addr);
        if(r < 0)
        {
                perror("Selecting i2c device\n");
        }

        while(1)
        {
                for(i = 0; i < 4; i++)
                {
                        command[0] = 0x40 | ((i + 1) & 0x03); // output enable | read input i
                        command[1]++;
                        r = write(fd, &command, 2);
                        usleep(delay);
                        // the read is always one step behind the selected input
                        r = read(fd, &value[i], 1);
                        if(r != 1)
                        {
                                perror("reading i2c device\n");
                        }
                        usleep(delay);
                }
                printf("0x%02x 0x%02x 0x%02x 0x%02x\n", value[0], value[1], value[2], value[3]);
        }

        close(fd);
        return(0);
}

In the next blog I will improve the output to print a graph of the values so you can see them move up and down.


32 comments:

  1. could you please explain the use of | and & in more detail?

    in particular the purpose of & 0x03

    regards

    ReplyDelete
  2. & and | are the c bitwise operators for AND and OR. This page may be of use http://www.tutorialspoint.com/cprogramming/c_bitwise_operators.htm

    ReplyDelete
  3. I believe this is a good tutorial to start using the /dev/i2c in embedded linux. thanks to writer

    ReplyDelete
  4. Hi john thanks for your tutorial
    I couldnt open i2c-1 devices
    int fd = open("/dev/i2c-1", O_RDWR); fd returns -1.
    Whats the problem can you help me please

    ReplyDelete
  5. The best way to debug this is call perror like this
    if(fd < 0)
    {
    perror("opening i2c device");
    exit(1);
    }

    That will tell you what is wrong. Likely the device node does not exist or you don't have permissions. Once you know the problem you can try and fix it up. See my post http://blog.chrysocome.net/2012/11/raspberry-pi-i2c.html on how to fix both of these issues.

    ReplyDelete
  6. thanks buddy you made my hard day come to a happy ending,
    was trying to write to the control buffers of the i2c registers of Mpu9150 for 6hrs without any result. But finally saw your post and found the result.

    ReplyDelete
  7. I want to send two sets of data from RPI to arduino via i2c ,used by the RPI c code.

    The two data sets is an integer value between + - 255.

    guide for me please.

    regards

    ReplyDelete
  8. command[1] - not initialized!
    command[0]=command[1]=0; should be

    ReplyDelete
  9. Hi,

    How do I read only the input AIN0 without for loop?

    In the first 8 bits of the command we will enable the analog output bit (0x40) and select which of the 4 inputs to read ((i + 1) & 0x03). We do a bitwise or to combine these values together with the | symbol.
    command[0] = 0x40 | ((i + 1) & 0x03); // output enable | read input i

    The // is the start of a comment so you can explain you code to the reader.

    In the next 8 bits we increment the value for the analog output
    command[1]++;

    ReplyDelete
  10. It is important that the messages were passed well and clearly.Emma

    ReplyDelete
  11. The article you have shared here very awesome. I really like and appreciated your work. I read deeply your article, the points you have mentioned in this article are useful
    gun mayhem 2

    ReplyDelete
  12. I feel extremely grateful to have discovered your web page and look forward to some more excellent moments reading
    here. Thank you again for everything. cineblog01

    ReplyDelete
  13. Thanks a lot for the post. It has helped me get some nice ideas. I hope I will see some really good result soon.
    hotmail login

    ReplyDelete
  14. I really appreciate the kind of topics you post here.
    facebook baixar

    ReplyDelete
  15. SDA is the data line. The SCL & SDA lines are connected to all devices on the I2C bus.

    1playergames66

    ReplyDelete
  16. Oh my goodness! an amazing article dude. Thank you However I am experiencing issue with ur rss. Don’t know why Unable to subscribe to it. Is there anyone getting identical rss problem? Anyone who knows kindly respond. Thnkx

    Codepen.io
    Information
    Click Here
    Visit Web

    ReplyDelete
  17. It's really nice and meaningful. It's really cool blog. Linking is very useful thing. you have really helped lots of people who visit this blog and provide them useful information. Thanks for sharing and please update some more information here. Get best App Developer Dubai service you visit here site for more info.

    ReplyDelete

  18. نقل الأثاث الثقيل ليس بالأمر السهل على الإطلاق ، وحتى أقل من ذلك عندما يتعين عليك تحريكه لأعلى الدرج! سواء كنت تنتقل إلى منزل جديد في طابق أعلى في شقة لا تحتوي على مصعد أو تحتاج إلى نقل بعض الأثاث إلى طابق آخر في المنزل ، فمن المهم استخدام الأسلوب الصحيح للقيام بذلك بأمان. اتخذ بعض الإجراءات لحماية الأثاث ومنزلك ونفسك من التلف والإصابة قبل أن تبدأ في نقل أي شيء. احمل قطعًا كبيرة على الدرج باستخدام مساعد أو استخدم عربة لنقل القطع الثقيلة الصغيرة والمتوسطة الحجم بمفردك بينما يكتشفك شخص ما من الأسفل. استفد من المعدات والتقنيات المتخصصة لتسهيل رفع بعض العناصر ووضعها في مكانها.
    تقديم خدمة نقل الأثاث بأفضل خدمة وأقل تكلفة وأسرع وقت وملائم للعملاء لتنفيذ عملية نقل الأثاث وتوفير المال والوقت للعملاء. جميع خدمات شركتنا بمعنى الخدمات الشاملة.

    شركة نقل عفش

    ReplyDelete
  19. Thanks for taking the time to discuss this. I feel strongly about it and love to learning more on this topic. If possible, as you gain expertise, would you mind updating your blog with more information? It is extremely helpful for me. Get best Remote Working Jobs provide good services visit here site for more info from Smart Job

    ReplyDelete
  20. This comment has been removed by the author.

    ReplyDelete
  21. I believe this is a good tutorial to start using the /dev/i2c in embedded linux. thanks to writer awning for patio

    ReplyDelete
  22. The ones which have not been initialised will contain random values so we must assign a value to them before they can be used. Egress Repair

    ReplyDelete
  23. I want to send two sets of data from RPI to arduino via i2c ,used by the RPI c code Waterproofing Services

    ReplyDelete
  24. Excellent explanation. Amarillo Drywall Contractors will use this in their future I2C program.

    ReplyDelete
  25. Now I understand it more. Thanks for explaining. web design

    ReplyDelete