Raspberry Pi IoT In C Using Linux Drivers - The I2C Linux Driver
Written by Harry Fairhead   
Monday, 25 July 2022
Article Index
Raspberry Pi IoT In C Using Linux Drivers - The I2C Linux Driver
A Real Device
Reading Temperature Data
Checksum

Linux drivers make working with devices so easy - assuming you know how. Here's how to get a Raspberry Pi to work with the i2c bus using the standard driver.

This content comes from my newly published book:

Raspberry Pi IoT In C Using Linux Drivers

By Harry Fairhead

Cdrivers360

Buy from Amazon.

Contents

  1.  Choosing A Pi For IoT

  2. C and Visual Studio Code

  3.  Drivers: A First Program

  4.  The GPIO Character Driver
         Extract: GPIO Character Driver

  5. GPIO Using I/O Control

  6.  GPIO Events

  7.  The Device Tree
        Extract: The DHT22

  8.  Some Electronics

  9.  Pulse Width Modulation
    Extract:  The PWM Driver 

  10. SPI Devices
    Extract: The SPI Driver 

  11. I2C Basics

  12. The I2C Linux Driver ***NEW!

     

  13. Advanced I2C

  14. Sensor Drivers – Linux IIO & Hwmon
      Extract: Hwmon  

  15. 1-Wire Bus
      Extract: 1-Wire And The DS18B20 

  16. Going Further With Drivers

  17. Appendix I

 <ASIN:1871962641>

<ASIN:B08W9V7TP9>

If you want to program in Python see Raspberry Pi IoT In Python Using Linux Drivers.

There are many specific device drivers which make use of the I2C bus and, as in the case of the SPI bus, there is usually a choice of hand-coding the interaction using the I2C driver or using the specific device driver. In this chapter we will look at the basics of making use of the driver for BSC1 which works on all versions of the Pi.

Enabling The Driver

To make use of the Linux I2C driver you have to enable it by adding dtparam=i2c_arm=on to the /boot/config.txt file. Alternatively you can load it dynamically:

void checkI2CBus() {
    FILE *fd = doCommand("sudo dtparam -l");
    char output[1024];
    int txfound = 0;
    while (fgets(output, sizeof (output), fd) != NULL) {
        printf("%s\n\r", output);
        fflush(stdout);
        if (strstr(output, "i2c_arm=on") != NULL) {
            txfound = 1;
        }
        if (strstr(output, "i2c_arm=off") != NULL) {
            txfound = 0;
        }
    }
    pclose(fd);
    if (txfound == 0) {
        fd = doCommand("sudo dtparam i2c_arm=on");
        pclose(fd);
    }
}
FILE * doCommand(char *cmd) {
    FILE *fp = popen(cmd, "r");
    if (fp == NULL) {
        printf("Failed to run command %s \n\r", cmd);
        exit(1);
    }
    return fp;
}

This is slightly different to the earlier driver loading functions. The first part of the function uses dtparam -1 to get a list of loaded overlays. If it finds ic2_arm=on as the last ic2_arm overlay then it does nothing. If it doesn’t find it then it activates the overlay. As you can also use ic2_arm=off and it is the last overlay that controls the state of the system we need to find the last occurrence of ic2_arm and make sure it is “=on”.

Both actions enable BSC1 and create a device file:

/dev/i2c-1

You can check that the driver has been installed using:

ls /dev/i2c*

which will return a list of I2C devices.

Using The I2C Driver From C

As is the case for all Linux devices, the I2C device /dev/i2c-x, where x is the I2C bus number, looks like a file. You can do a block read by simply opening the file for reading and reading an array of bytes:

int i2cfd = open("/dev/i2c-1", O_RDWR);
read(i2cfd,buf,n);

This reads a maximum of n bytes of data and returns it as an array of bytes. The only problem is how do you specify the address of the device? Opening the file only opens the I2C channel and there might be multiple devices connected to it. As in the case of other /dev character devices we need to use the standard Linux ioctl function to send a command to it.

In the case of the I2C driver the most important ioctl command is:

I2C_SLAVE

This is used to set the address of the slave that subsequent read and writes apply to. So to set the address of the device you want to read from to 0x40 you would use:

#include <linux/i2c-dev.h> 
ioctl(i2cfd, I2C_SLAVE, 0x40);

Finally to reset the hardware and return all GPIO lines to their default modes you have to close the file:

close(i2cfd); 

Putting all of the together a block read/write is:

#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include<fcntl.h>
#include <linux/i2c-dev.h>
void checkI2CBus();
FILE * doCommand(char *cmd);
int main(int argc, char** argv) {
    checkI2CBus();
    int i2cfd = open("/dev/i2c-1", O_RDWR);
    ioctl(i2cfd, I2C_SLAVE, 0x40);
    char buf[4] = {0xE7};
    write(i2cfd,buf,1);
    read(i2cfd,buf,1);
    close(i2cfd
    return (EXIT_SUCCESS);
}

By default stop bits not are sent between each byte read, a stop bit is only sent at the end of the block of data that is written.

If you try these programs out you will discover that the I2C clock frequency is the default 100KHz. You can’t change the clock frequency dynamically but you can add:

dtparam=i2c_arm=on,i2c_arm_baudrate=10000

to the /boot/config.txt file and after a reboot the I2C clock will be set to the frequency specified as the baudrate. The baud rate is simply the clock speed in Hz. Notice that the I2C clock speed depends on the core clock rate and this can be slower than the maximum possible when the device is idling or under heat pressure. To run at the fastest clock speed, even when idling, use the command:

sudo sh -c "echo performance > 
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"



Last Updated ( Monday, 25 July 2022 )