Python Robotics Projects
上QQ阅读APP看书,第一时间看更新

Interfacing through I2C

So far, so good. Electronic circuits can be very interesting and, while they seem very complex, often we find that the working can be very simple. In the previous section, we interfaced one sensor at a time. We can go ahead and interface multiple sensors, but we are limited by the number of GPIOs that are present. We have also seen that some sensors such as ultrasonic sensors may use more than one GPIO pin for their working. This further reduces the number of sensors that we can interface with the microcontroller. Once we move on to more complex circuits, we will also realize that the wiring can be really messy and if a problem occurs then finding what's wrong becomes one tedious task.

Now, there is an even bigger problem that we face while designing robotic systems and that's the problem of timing—all the work done in a system has to be synchronized. Most of the systems are currently sequential in nature, as in the output of one unit becomes the input of another:

Now, for the task to be completed, the PROCESSING UNIT 1 has to deliver the input to PROCESSING UNIT 2 when needed, and the same goes for PROCESSING UNIT 3. If the data is not timed perfectly, then either the PROCESSING UNIT 2 will keep waiting for the input from PROCESSING UNIT 1 or, even worse, the PROCESSING UNIT 1 will send the data to PROCESSING UNIT 2 at a time when it does not need it. In which case, the data will get lost and the process will have some errors. 

Hence, to solve this problem, the computer scientists back in the day invented a system of pulsing. The clock pulse is a very simple square wave which has a 50% duty cycle (recollect pulse width modulation (PWM)). The circuits are designed to do one operation at either the rising or the falling edge of the clock pulse. Due to this synchronization, every part of the circuit knows when to work. Here is what the clock pulse looks like:

Now, coming back to the point, we have two problems:

  • There is a physical limit to how many devices/sensors can be connected to the robot 
  • How to time the sensors and interconnected circuits to work in harmony 

To solve these problems, we use a very commonly used protocol called I2C, which stands for Inter-integrated Circuits. This protocol is extremely useful when we need to connect multiple devices on the same set of GPIOs, such as when we have only one set of GPIO pins over which multiple sensors can be linked. This is made possible due to unique addresses allocated to each hardware. This address is used to identify a sensor and then to communicate with it accordingly. Now, to implement the I2C protocol we need two lines; these lines are as follows:

  • Data 
  • Clock

As you may have guessed, the clock line is used to send a clock pulse to the devices attached to it and the data is the bus over which the data flows to and fro. 

Now, the entire I2C architecture works on a master-slave configuration, wherein the master generates the clock signal all the time for the slave devices and the slave devices have to constantly look for the clock pulse and the data packets sent by the master devices. Let's see how it's done.

As mentioned earlier, there are two lines: the data line, which is referred to as Serial Data (SDA), and the clock line, which is referred to as Serial Clock (SCL). From now on, we will be using the terms SCL and SDA:

Lets look at the main pointers shown in the diagram:

  • Start condition: To start a communication, a start condition is created indicating that the communication is about to happen. This condition is depicted by the master by keeping the SDA line low before the SCL. This indicates all the slave devices are ready for communication.
  • Address frame: Once the communication is started the master sends the address of the device that needs to be communicated with. This is a 7-bit address. In every clock pulse, a bit is sent, hence it takes seven clock pulses to send the 7-bit address. After that 7-bit address is a read/write bit. This indicates to the device whether the master would like to write in this operation or if it wants to read some data. Hence, the total address frame is of 8 bits, which takes eight clock pulses to be sent. After these eight pulses, during the ninth clock pulse, the master waits for the acknowledgement from the device. This acknowledgement is sent by the slave device when the SDA line is pulled low by the slave device which is being addressed. With this strategy, the master knows that the address sent by it has been received and the slave device is now ready for the communication. If the acknowledgement is not sent back, then it is up to the master what has to be done. 
  • Data frame: Once the acknowledgement is sent, depending on if it is a read or write operation, the data is either written by the master onto the slave or, in read operation, the data is sent by the slave over to the master. The length of this data frame can be arbitrary.
  • Stop frame: Once the data transfer is completed, the stop condition is made by the master to indicate that the communication has to stop. This condition is done when the SDA line goes from low to high after the SCL line goes from low to high. 

So this is basically how I2C communication works. For every device we have a 7-bit address, hence we can connect up to 128 devices on a single bus. That's a lot of devices. The chances of running out of physical limits is almost negligible. Now let's go ahead and see how we can connect the sensors via this protocol. Generally, it is not required to do the core programming for the I2C, as it is lengthy and cumbersome. That's where the magic of open source comes in. There are a lot of developers across the globe who are working on these sensors and most of them are generous enough to make a library and share it for ease of programming. These libraries are available online and most of them take care of the complex process of communication. 

Now is the time that we interface our first I2C device, which is an analogue to digital converter. You must be wondering why we use this converter in the first place. Recall the time when we started understanding GPIO pins. These magic pins can be used both as input and output; you may also remember that these pins can either be on or off—these are all digital pins, not only when it comes to output but also for input. But there are a huge amount of sensors that work over analogue communication. Due to the digital architecture of Raspberry Pi, it is difficult to interface these sensors directly. Hence, we use an analogue to digital converter (ADC), this converter converts the analogue value of the sensors to the digital bits that are understandable by Raspberry Pi.

We will be connecting an LDR, the resistor will change the value of resistance based on how much light is falling onto it. Hence, the voltage will be dependent upon how much light is falling over the LDR. 

Now let's see how it is practically done. Take up your Pi and let's get going. To start, firstly we need to enable I2C on our Raspberry Pi; follow the steps listed here:

  1. Open the terminal (Ctrl + Shift + T)
  2. Type sudo raspi-config
  3. Select the interfacing options:
  1. Then go to Advanced Options:
  1. Then select I2C to enable it. Then select Yes:

Now install the adafruit library to interface the ADC1115:

  1. Open the terminal and copy the following command:
sudo apt-get install build-essential python-dev python-smbus python-pip 

This command downloads the libraries and the dependencies over to Raspberry Pi

  1. Now type the following:
sudo pip install adafruit-ads1x15

This command installs the libraries and the dependencies over to Raspberry Pi.

Now that the software is set up, let's get the hardware ready. Connect Raspberry Pi to the ADS1115 as shown in the following diagram:

Once you are ready, go ahead and upload this code in Pi:

import time
import Adafruit_ADS1x15
import RPi.GPIO as GPIO
LED =14

GPIO.setmode(GPIO.BCM)
GPIO.setup(LED,GPIO.OUT)

adc = Adafruit_ADS1x15.ADS1115()
GAIN = 1
channel=0
adc.start_adc(channel, gain=GAIN)

while True:
value = adc.get_last_result()
print(str(value))
time.sleep(0.1)
if value >= 100:
GPIO.output(LED,1)
else :
GPIO.output(LED,0)

adc.stop_adc()

Note that there can be times when this code may not work, in which case try tweaking the value of threshold:

if value >= 100:

What you might have noticed is that whenever the LDR is faced towards a light source, the LED also switches on, and whenever it is away from light, the LED switches off. 

So now you have interfaced an I2C device. Let's understand how this code is actually working:

import Adafruit_ADS1x15

The preceding line of code imports the Adafruit_ADS1x15 library in the code so that we can use all its functions during the program.

adc = Adafruit_ADS1x15.ADS1115()

The preceding line of code creates the instance of the library Adafruit_ADS1x115. The line .ADS1115() is the function for creating the instance as adc. Understood anything? Let me put it in English. 

Now, instead of writing Adafruit_ADS1x15 all the time, we can simply write adc to call the library functions. Further, you can use any word instead of adc; it can be your cat's name or your neighbor's name, and it would still work:

GAIN = 1

This is the value to which the sensing would be done. 1 depicts that the sensing would happen in full range. Which for our ADC is from a voltage range of 0V to +/-4.096V.  Now changing the gain would result in change of the sensing range. I.e. if we change the value of gain to 2 Then the Range in which the sensing would happen would be Half of the original range i.w. 0 to +/- 2.048 Volts. 

Now you must be asking what is the voltage range and why are we changing the gain ? 

The reason is simple. There are different types of analog sensors. Which give output in a wide variety of voltage range. Some sensors can give you output in the range of 0.5 volt to 4 volt others can give you from 0.1 volt to 0.98 volts. Now if we set the gain to 1 then the all of these sensors could be easily interfaced. As all of them fall in between the sensing range of 0 to 4.098 Volts. However as it is a 16 bit ADC hence the total number of discrete values that the ADC can provide would be in between 216 or 65,536 readings. Hence at the gain of 1 the minimum voltage change that the ADC could detect would be: 4.096 / 65536 = 0.000062.

But if increase the gain to 4 then the sensing range would reduce to a mere 0 to +/- 1.0245. So this would be able to work with the output range between 0.1 volt to 0.98 volt. But now lets see the minimum voltage change that it could detect: 1.0245 / 65536 = 0.00001563.

Now as you can see the minimum voltage that can be detected is very low. Which is a good thing for the compatibility with sensor.

Now, it is up to you as to what gain value you want. The LDR is working on 5V, hence it is better for us to use the entire gain reading of 1:

channel=0

When you look closely at the ADC hardware, you will notice that there are various pins including A0, A1, A2, and A4 This is a four-channel ADC—it can convert four analogue inputs and convert them into digital data. As we are only using one single data stream, we will be letting Pi know which pin it is connected on. With the following line, we are telling Pi to start the process of converting the data:

adc.start_adc(channel, gain=GAIN)

In the following line, we are instructing the ADC to stop the conversion, and that's where the code ends. 

adc.stop_adc()