This is a beginner friendly project for those interested in using i2c or IIC protocol with Arduino Master and Arduino Slave.
The use of other i2c peripherals such as 0.96" OLED display and MPU 6050 sensor has also been demonstrated in this code.
Furthermore, this project also includes code for using a Rotary Encoder, an Analog Stick, push-buttons and LED lights with Arduino.
All these major components types (10 in total) include:
- One Arduino Nano as i2c Master
- One Arduino Pro Micro (5V) as i2c Slave
- One 0.96" i2c OLED Display
- One MPU 6050 Gyroscope and Accelerometer
- One Rotary Encoder
- One Analog Stick (like those found in old PS2 controllers)
- Three Generic Push Buttons
- Two 5mm LED Lights
- Lots of Jumper Cables (both M-M and M-F)
- 5V Power Supply
Note: I used two 18650 Li-Ion Cells connected in Series to give 7.4V in total. The power supply was then connected to RAW Voltage pins for both the Arduinos. The rest of the components were directly powered from the regulated 5V pin of the Arduinos, since they dont draw too much power anyways. Remember to always use the seperate power supply for power hungry components such as motors, etc.
All Components fixed on a Breadboard
Arduino Nano as i2c Master (Left) and Arduino Pro Micro as i2c Slave (Left)
The code is divided into two parts: "Master_Code" and "Slave_Code" which has to be uploaded to the master and slave Arduino seperatly.
I used the Arduino Nano as the i2c Master, while the Arduino Pro Micro became the i2c Slave.
As seen in the block diagramm, the master interacts with 3 individual components directly:
- the Rotary Encoder
- and two Push Buttons.
The other devices are connected to the master using i2c Bus:
- Arduino Pro Micro as i2c Slave,
- OLED Display,
- and MPU 6050 Sensor.
On the other hand, the components connected directly to the slave Arduino are:
- Analog Stick,
- two LED lights
- and one Push Button (for resetting the Arduino Pro Micro when uploading the code, if using Arduino Nano as an i2c Slave, this button is not required).
The functions implemented in the Master Arduino can summarized as:
Switching LED Lights using Buttons
-
void pressBUT_1 (); void pressBUT_2 ();
These two fucntions are same. First one is used to detect whether Button 1 is pressed or not and the second fucntion detects the Button 2. Both of these buttons include Software Debounce for the push buttons, which prevents extra unwanted button presses whenever a button is pressed or released.
These fucntions also use the i2c Bus to signal the Slave Arduino to turn ON or OFF the respective LED lights connected to it.
Different Display Pages
-
void pressEncoderBUT ();
Pretty much the same as the above two fucntions. This fucntions reads the Rotary Encoder's Button and is used to switch between the different display pages on the OLED screen.
LED Lights React to the Rotary Encoder's Direction
-
void encoderISR ();
This is an Interrup Serivce Routine which reads the interrupt from the Rotary Encoder (only one pin) to detect its direction. The UP-Down Counter shown on the display reacts to the Rotary Encoder's direction, while the LED lights also blink in the appropriate direction.
-
void init_Accel_Gyro ();
This function is used to initialize the MPU 6050 with custom configuration settings. This is done by setting the values of the specific registors on this sensor.
LEDs' Reaction due to Orientation using Accelerometer
-
void readingAccel(); void readingGyro();
The Accelerometer and Gyroscope data from the MPU 6050 on the i2c Bus is accessed using these two fucntions. This is done by first requesting the appropriate registor to read from, and then reading the data sent over by the MPU. The data is sent Bit-by-Bit in i2c transmissions. Therefore, on reception the data is combined back together. Offsets are also applied. The LEDs reaction process is also performed on the Accelerometer data to determine the current orientation, and the result is shown by blinking the two LEDs.
-
void leftLED_Comm (); void rightLED_Comm ();
These two functions perform the task of sending a signal to the slave Arduino to blink the appropriate LED light when the function is called.
-
void drawPage_1 (); Status of the two LEDs void drawPage_2 (); Up-Down Counter for Rotary Encoder void drawPage_3 (); Accelerometer Axis Data void drawPage_4 (); Gyroscope Axis Data void drawPage_5 (); Analog Stick Axis Data void drawPage_6 (); Moving the Text Graphic using the Analog Stick
The OLED display is used with "U8glib" library. To render something on the display, a page function has to be created. Only content of a single page function is displayed at once. Each of these six fucntions represent a different page as described next to the each function.
-
void i2c_Tx_Arduinos (int Address, int DataToBeSent, int DataSizeinBytes);
This function simplifies the i2c Transmission process and the overall code length. An i2c Transmission is done by first selecting the slave address, then sending it the registor Byte to tell the Slave what to send. This is done because the slave is unable to send anything by itself, only when the master request some data, the slave can then send it. Next, the data is actually requested from the slave using its i2c Address and the number of Bytes the master expects to receive. Instead of doing this everytime, this function is called which will perform the i2c Transmission
Moving Text Graphic Using Analog Stick
-
void analogStickReception ();
This fucntion requests the RAW Analog Stick readings from the slave Arduino. Analog Stick's Button status is also requested here from the Slave. The data is then received in a buffer of 5 Bytes; one for Button status and two Bytes for each axis. The two Bytes for the axis are then commbined together. After that, it is mapped to the display resolution of the OLED Screen. This mapped data is shown on the page # 5 of the OLED display. This data is also used for the Text Graphic to move it across the screen without any clipping using the Analog Stick on the slave Arduino.
Swiping the Display Pages the Analog Stick
-
void analogStickSwipe ();
When enabled by pressing the Analog Stick's Button, this functions reads the Analog Stick's motion to swipe the display pages/menus. This is all done through the i2c Bus from Slave to Master.
-
long rangeMapper (double value, double fromMin, double fromMax, double toMin, double toMax);
A function for mapping two different ranges of data together based on the minimum and maximum values of both ranges. The current value in the range from which mapping is performed is also required. Here this function is used for mapping the RAW Analog Stick data from the slave Arduino to the display resolution of OLED display.
The slave code is far too simple, considering the master Arduino is doing all the heavy-lifting. There are only four functions here:
-
void pressAnalogStick_Btn ();
The Analog Button's status is read here in this function and a Byte flag is set. This flag is then sent to the Master which then enables the Analog Stick swiping motion or turns it off.
-
void analogStickReader ();
This function simply simply reads the Analog Stick data for both X and Y-Axis and stores them in a struct. The offsets, which were calculated manually, are also applied to get accurate data.
-
void slaveRead(int);
This function is used when the master Arduino sends data on the i2c Bus. This triggers the ISR for the slave Arduino, where this function is called. Since this happens inside the ISR, this function must be short so that the duration of the ISR is short as possible. The data is sent in Bytes from the master. Therefore, if there are more than one Bytes of data, it has to be stored inside a buffer and then later combined if required. The single "int" parameter is passed from the i2c ISR and it tells the slave how many Bytes are expected to be received from the master Arduino.
-
void slaveWrite();
This function is used to send the data to the master Arduino when requested. For a single Byte, the "Wire.write()" is called once and the data is passed to it. For sending multiple Bytes, a buffer is required. The buffer is filled Byte-by-Byte and then the buffer and its size in Bytes is passed to the "Wire.write()" function. The master Arduino specifies the number of Bytes the master will receive. Even if the slave tries to send more, the master Arduino will ignore them.
Note that the 6050 MPU provides a 2-Byte or 16-Bit data for each axis readings, but i2c only sends 1-Byte data from the buffer at a time. So we have to take those two bytes, and "shift-add" them together.
Best explained using this example:
The 16-Bits or 2-Bytes to be sent using i2c are:
10101010 01010101
This is split up in two Bytes for i2c transmission.
-
First Byte that is received: 10101010
Shifting the First Byte by 8-Bits to make space for the second one: 10101010 00000000
-
Second Byte is received: 01010101
ORing them: 10101010 00000000 + 01010101
This results in: 10101010 01010101
Which is actually the 16-Bit reading taken by the MPU, but due to i2c limitations, it is split up in 2-Bytes to be sent seperately.
The code has been commented properly to expain each function and block of code, but the code can still be simplified further by turning the repetative lines of code into seperate functions. So far, there are no bugs in the code and the hardware responds within the expectations. The last version introduced more functions to replace some repetative code and made use of the Button in Analog Stick to swipe the Display Pages. There will be no further revisions.
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =