Week 10

Introduction

This page outlines the steps followed during week 10 of the Fab Academy assignments.

The tasks for this week involved Output Devices and the following assignments:

    group assignment:

  • Measure the power consumption of an output device
  • Individual assignment:

  • Add an output device to a microcontroller board you've designed, and program it to do something

Let's start.....................

Dancer

Assignments

Output Device

The output devices are the devices that allow us to interact with the microcontroller. The output devices are the devices that allow us to control the microcontroller and the input devices are the devices that allow us to read the signals from the microcontroller.

The output device we are going to use is the OLED display. The OLED display is a very useful tool that allows us to display the information from the microcontroller.

The OLED display is a GM009605 display that has a resolution of 128x64 pixels. This specific display uses the I2C protocol to communicate with the microcontroller. The I2C protocol is a two-wire protocol that allows us to communicate with the microcontroller.

Dancer

Understanding I2C Protocol

I2C (Inter-Integrated Circuit) is a widely used communication protocol in embedded systems. It allows multiple devices to communicate with a microcontroller using just two wires: SDA (Serial Data Line) and SCL (Serial Clock Line). This makes it ideal for connecting sensors, displays, and other peripherals in a compact and efficient manner.

Dancer

Key Features of I2C

  • Two-Wire Communication: Only two lines are required for communication: SDA for data and SCL for the clock signal.
  • Master-Slave Architecture: One device acts as the master (e.g., ESP32), while others act as slaves (e.g., sensors like ADXL343).
  • Addressing: Each slave device has a unique 7-bit or 10-bit address, allowing the master to communicate with specific devices.
  • Multi-Master Support: Although uncommon, I2C supports multiple masters on the same bus.
  • Acknowledgment: After each byte of data is sent, the receiver sends an acknowledgment (ACK) to confirm receipt.

How I2C Works

  1. Start Condition: The master initiates communication by pulling the SDA line low while the SCL line remains high.
  2. Addressing: The master sends the 7-bit address of the target slave device, followed by a read/write bit.
  3. Acknowledgment: The slave device responds with an ACK if it recognizes its address.
  4. Data Transfer: Data is transferred in 8-bit packets, with the receiver sending an ACK after each byte.
  5. Stop Condition: The master ends communication by releasing the SDA line while the SCL line is high.

Device Addressing

Each I2C device has a unique 7-bit or 10-bit address. For example, the ADXL343 accelerometer typically uses the address 0x53 or 0x1D, depending on the configuration of its address pin. The address is sent by the master during the addressing phase, allowing the slave to recognize and respond to the communication.

Pull-Up Resistors

I2C lines (SDA and SCL) require pull-up resistors to ensure proper operation. These resistors pull the lines high when no device is actively driving them low. Typical values range from 4.7kΩ to 10kΩ, depending on the bus speed and capacitance.

Advantages of I2C

  • Simple and efficient communication with multiple devices.
  • Requires only two wires, reducing complexity and cost.
  • Supports a wide range of devices and peripherals.

Limitations of I2C

  • Limited speed compared to other protocols like SPI.
  • Shorter communication distance due to signal degradation.
  • Requires careful management of device addresses to avoid conflicts.

Understanding the I2C protocol is essential for working with modern microcontrollers and peripherals. By mastering its principles, you can efficiently integrate a wide variety of devices into your projects.

The next step is to connect the OLED display to the ESP32. The OLED display has four pins: VCC, GND, SDA and SCL. The VCC pin is connected to the 3.3V pin of the ESP32, the GND pin is connected to the GND pin of the ESP32, the SDA pin is connected to the GPIO21 pin of the ESP32 and the SCL pin is connected to the GPIO22 pin of the ESP32. But this can change depending on the board you are using. The following image shows the connections of the OLED display.

Dancer

To code the ESP32 we are going to use VSCode and PlatformIO. The code is going to be written in C++ and we are going to use the Adafruit library to control the OLED display. The Adafruit library is a library that allows us to control the OLED display using the I2C protocol. The library is very easy to use and allows us to control the OLED display with just a few lines of code.

we are going to use the following code to control the OLED display:


                        #include "Wire.h"
                        #include "Adafruit_GFX.h"
                        #include "Adafruit_SSD1306.h"
                        
                        #define SCREEN_WIDTH 128 // OLED display width, in pixels
                        #define SCREEN_HEIGHT 64 // OLED display height, in pixels
                        
                        #define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
                        #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
                        Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
                        
                        #define NUMFLAKES     10 // Number of snowflakes in the animation example
                        
                        #define LOGO_HEIGHT   16
                        #define LOGO_WIDTH    16
                        static const unsigned char PROGMEM logo_bmp[] =
                        { 0b00000000, 0b11000000,
                            0b00000001, 0b11000000,
                            0b00000001, 0b11000000,
                            0b00000011, 0b11100000,
                            0b11110011, 0b11100000,
                            0b11111110, 0b11111000,
                            0b01111110, 0b11111111,
                            0b00110011, 0b10011111,
                            0b00011111, 0b11111100,
                            0b00001101, 0b01110000,
                            0b00011011, 0b10100000,
                            0b00111111, 0b11100000,
                            0b00111111, 0b11110000,
                            0b01111100, 0b11110000,
                            0b01110000, 0b01110000,
                            0b00000000, 0b00110000 };
                        
                        
                        void testdrawstyles(void) {
                            display.clearDisplay();
                        
                            display.setTextSize(1);             // Normal 1:1 pixel scale
                            display.setTextColor(SSD1306_WHITE);        // Draw white text
                            display.setCursor(0,0);             // Start at top-left corner
                            display.println(F("Hello, world!"));
                        
                            display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
                            display.println(3.141592);
                        
                            display.setTextSize(2);             // Draw 2X-scale text
                            display.setTextColor(SSD1306_WHITE);
                            display.print(F("0x")); display.println(0xDEADBEEF, HEX);
                        
                            display.display();
                            delay(2000);
                        }
                        
                        
                        void testdrawbitmap(void) {
                            display.clearDisplay();
                        
                            display.drawBitmap(
                            (display.width()  - LOGO_WIDTH ) / 2,
                            (display.height() - LOGO_HEIGHT) / 2,
                            logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
                            display.display();
                            delay(1000);
                        }
                        
                        #define XPOS   0 // Indexes into the 'icons' array in function below
                        #define YPOS   1
                        #define DELTAY 2
                        
                        void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
                            int8_t f, icons[NUMFLAKES][3];
                        
                            // Initialize 'snowflake' positions
                            for(f=0; f< NUMFLAKES; f++) {
                            icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
                            icons[f][YPOS]   = -LOGO_HEIGHT;
                            icons[f][DELTAY] = random(1, 6);
                            Serial.print(F("x: "));
                            Serial.print(icons[f][XPOS], DEC);
                            Serial.print(F(" y: "));
                            Serial.print(icons[f][YPOS], DEC);
                            Serial.print(F(" dy: "));
                            Serial.println(icons[f][DELTAY], DEC);
                            }
                        
                            for(;;) { // Loop forever...
                            display.clearDisplay(); // Clear the display buffer
                        
                            // Draw each snowflake:
                            for(f=0; f< NUMFLAKES; f++) {
                                display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
                            }
                        
                            display.display(); // Show the display buffer on the screen
                            delay(200);        // Pause for 1/10 second
                        
                            // Then update coordinates of each flake...
                            for(f=0; f< NUMFLAKES; f++) {
                                icons[f][YPOS] += icons[f][DELTAY];
                                // If snowflake is off the bottom of the screen...
                                if (icons[f][YPOS] >= display.height()) {
                                // Reinitialize to a random position, just off the top
                                icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
                                icons[f][YPOS]   = -LOGO_HEIGHT;
                                icons[f][DELTAY] = random(1, 6);
                                }
                            }
                            }
                        }
                        
                        void setup() {
                            Serial.begin(115200);
                        
                            Wire.begin(21,22);
                        
                            // I2C Scanner
                            Serial.println(F("Scanning for I2C devices..."));
                            byte count = 0;
                            for (byte address = 1; address < 127; address++) {
                            Wire.beginTransmission(address);
                            if (Wire.endTransmission() == 0) {
                            Serial.print(F("Found I2C device at address 0x"));
                            if (address < 16) Serial.print(F("0"));
                            Serial.println(address, HEX);
                            count++;
                            }
                            }
                            if (count == 0) Serial.println(F("No I2C devices found."));
                            else Serial.print(F("Found ")); Serial.print(count); Serial.println(F(" device(s)."));
                        
                            // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
                            if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
                            Serial.println(F("SSD1306 allocation failed"));
                            for(;;); // Don't proceed, loop forever
                            }
                        
                            display.display();
                            delay(2000); // Pause for 2 seconds
                        
                            // Clear the buffer
                            display.clearDisplay();
                        
                            // Draw a single pixel in white
                            display.drawPixel(10, 10, SSD1306_WHITE);
                        
                            display.display();
                            delay(2000);
                        
                            testdrawstyles();    // Draw 'stylized' characters
                        
                            testdrawbitmap();    // Draw a small bitmap image
                        
                            // Invert and restore display, pausing in-between
                            display.invertDisplay(true);
                            delay(1000);
                            display.invertDisplay(false);
                            delay(1000);
                        
                            testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
                        }
                        
                        void loop() {
                        }
                    
                

The code is going to generate a bitmap image of the Adafruit logo and the also some text starting with "Hello, world!".

oscilloscope
oscilloscope

Now that we are capable of controlling the OLED display, we are going to measure the power consumption of the OLED display. This is measured using a multimeter, then is connected in series with the OLED display and set to measure the current in milliamps (mA).

However, in our case, we are going to use a device called Current Ranger to measure the power consumption of the OLED display.

If you want more information about the Current Ranger, you can check the following link: CURRENT RANGER

Dancer

The Current Ranger is a device that allows us to measure current in milliamps (mA). It is connected in series with the OLED display and configured to measure current in milliamps (mA), similar to how a multimeter operates.

In this case we are going to measure the consumption of the OLED display when it is playing the animation.

> Dancer

In this case the current consumption is between 1.2mA and 14.4mA, depending on the brightness of the OLED display. The OLED display is a very low power consumption device, which makes it ideal for battery powered devices.

We use a breadboard to connect the OLED display and the Current Ranger. Just for testing purposes and time saving.

Conclusion

In this week we learned how to use the OLED display and how to measure the power consumption of the OLED display. We also learned how to use the I2C protocol to communicate with the OLED display. The I2C protocol is a very useful protocol that allows us to communicate with multiple devices using just two wires.

Lessons Learned

  • How to use the OLED display
  • How to use the I2C protocol
  • How to measure the power consumption of the OLED display
  • How to use the Current Ranger

Resources