NuttX RTOS SPI Tutorial using ESP32 and SSD1306 OLED Display

Welcome to EmbeTronicX, Embedded Tutorials World. This article is a continuation of the series on NuttX RTOS tutorials using the ESP32 Dev board and carries the discussion on NuttX RTOS and their usage. The aim of this series is to provide easy and practical examples that anyone can understand. In this tutorial, we are going to see how to use the Nuttx RTOS SPI using the ESP32 and SSD1306 OLED Display.

NuttX RTOS SPI – ESP32 SPI Tutorial

Prerequisites

To continue with this tutorial, you must have configured the ESP32 toolchain and compiled the NuttX RTOS. If you aren’t configured anything yet, we suggest you to set up the esp32 using the below-given tutorial.

Hardware Required

SSD1306 OLED

SSD1306 is a single-chip CMOS OLED/PLED driver with a controller for an organic / polymer light-emitting diode dot-matrix graphic display system. It consists of 128 segments and 64commons.

SSD1306 SPI

The SSD1306 embeds with contrast control, display RAM, and oscillator, which reduces the number of external components and power consumption. It has 256-step brightness control. Data/Commands are sent from general MCU through the hardware selectable 6800/8000 series compatible Parallel Interface, I2C interface, or Serial Peripheral Interface. It is suitable for many compact portable applications, such as mobile phone sub-display, MP3 player and calculator, etc.

The operating voltage of the SSD1306 controller is from 1.65V to 3.3V while the OLED panel requires 7V to 15V supply voltage. All these different power requirements are sufficed using internal charge pump circuitry. This makes it possible to connect it to any 5V logic microcontroller easily without using any logic level converter.

SSD1306 OLED Memory Map

Regardless of the size of the OLED module, the SSD1306 driver has a built-in 1KB Graphic Display Data RAM (GDDRAM) for the screen which holds the bit pattern to be displayed. This 1K memory area is organized into 8 pages (from 0 to 7). Each page contains 128 columns/segments (block 0 to 127). And each column can store 8 bits of data (from 0 to 7). That surely tells us we have

8 pages x 128 segments x 8 bits of data = 8192 bits = 1024 bytes = 1KB memory

Here are the complete specifications:

Display Technology OLED (Organic LED)
MCU Interface I2C / SPI
Screen Size 0.96 Inch Across
Resolution 128×64 pixels
Operating Voltage 3.3V – 5V
Operating Current 20mA max
Viewing Angle 160°
Characters Per Row 21
Number of Character Rows 7

Data in SSD1306 OLED

There are two types of data that we can send to the SSD1306 OLED.

  1. Command
  2. Data to be written into the GDDRAM

Command

The command is nothing but, tells the LED to do some operations like a scroll, increases the brightness, etc.

Basically SSD1306 has 8 lines (pages) and 128 columns(segments). Please refer to the below image for a clear understanding.

SSD1306 Pages and Columns

Each page contains 8 rows. So, a total of 64 rows will be present in the SSD1306’s GDDRAM (Graphics Display Data RAM). The GDDRAM (Graphics Display Data RAM) is a bitmapped static RAM. It holds the bit pattern to be displayed. For understanding the rows, please look at the image below.

GDDRAM Structure

As per our understanding, it has 128 columns and 64 rows. So the size of GDDRAM is 128 x 64 bits and it is divided into eight pages, from PAGE0 to PAGE7, which are used for monochrome 128×64 dot matrix display as shown in the above figure.

In this tutorial, we are going to write one character in 5×8. The below image explains how to write our characters. In that, I have taken Page 0’s row 0 to row 7 and column 0 to column 4 to explain how to write our characters.

Print A

I think the above image is self-explanatory. I am trying to write the character ‘A’ (5×8) from row 0 and column 0. In order to write ‘A’, I need to make the cursor on page 0 and column 0. Then write 0x7C, 0x12, 0x11, 0x12 and 0x7C. Look at the array SSD1306_font in the source code to get the data for almost all characters.

Since you can write data pixel by pixel, you can print anything you want. In this example, we are also printing the image.

Commands in SSD1306
Command Command Description
0x00 COMMAND This command is used to indicate the next data byte is acted as a command.
0x40~0x7F Display Start Line This command sets the Display Start Line register to determine the starting address of display RAM
0xAE Display OFF This command is used to turn OFF the OLED display panel.
0xAF Display ON This command is used to turn ON the OLED display panel.
0x20 Memory Addressing Mode If horizontal address increment mode is enabled by command 20h, after finishing read/write one column data, it is incremented automatically to the next column address.
0x26/0x27 Continuous Horizontal Scroll 0x26 – Right horizontal scroll

0x27 – Left horizontal scroll

0x29/0x2A Continuous Vertical and Horizontal Scroll 0x29 – Vertical and Right Horizontal Scroll

0x2A – Vertical and Left Horizontal Scroll

0x2E Deactivate Scroll This command deactivates the scroll.
0x2F Activate Scroll This command activates the scroll if it is configured before.
0x21 Column Address This command is used to define the current read/write column address in graphic display data RAM.
0x22 Page Address This command is used to define the current read/write Line(Page as per data sheet) address in graphic display data RAM.
0x81 Contrast Control This command sets the Contrast Setting of the display. The chip has 256 contrast steps from 00h to FFh. The segment output current increases as the contrast step value increases.
0xA0 Segment Re-map This command sets the column address 0 is mapped to SEG0.
0xA1 Segment Re-map This command sets the column address 127 is mapped to SEG0.
0xA6 Normal Display This command sets the display to normal mode. In the normal display, a RAM data of 1 indicates an “ON” pixel.
0xA7 Inverse Display This command sets the display to inverse mode. In the normal display, a RAM data of 0 indicates an “ON” pixel.
0xA8 Multiplex Ratio This command sets the display multiplex ratio.
0xC0/0xC8 COM Output Scan direction 0xC0 – Normal mode (RESET) Scan from COM0 to COM[N –1]

0xC8- Remapped mode. Scan from COM[N-1] to COM0 Where N is the Multiplex ratio.

0xD3 Display Offset This command sets vertical shift by COM from 0d~63d.
0xD5 Display Clock Divide Ratio/Oscillator Frequency

 

This command sets the display Clock Divide Ratio and Oscillator Frequency.

Please go through the datasheet for complete commands. Commands are pretty straightforward. You will understand those commands easily.

Data

Data is directly written into the OLED display. We can print anything we want, using this SSD1306 OLED, unlike other alphanumerical displays. Graphical data also we can print.

How the SSD1306 OLED differentiate between command and data?

In I2C communication, we have to send the control byte first, which tells the following data is command or data. But in SPI, there is a dedicated GPIO (DC) available. We have to keep the DC line to low if we are sending the command. Otherwise, we have to keep that to high.

Please refer to the datasheet for further information.

Connection Diagram – NuttX RTOS SPI using ESP32 and SSD1306

In this tutorial, we are using the SSD1306 SPI OLED. Some of the SSD1306 SPI OLEDs might have a CS/CE pin. In that case, you can connect that to your controller’s CS/CE pin. In my case, I don’t have the separate CS/CE pin in the SSD1306 OLED as it is already grounded in the board itself.

In this example, we have used the SPI2. You can also use the SPI3, but change the driver path to /dev/spi3 in the source code.

Please refer to the below connection diagram.

  • SDA (MOSI) – Pin 13
  • SCL (CLK) –  Pin 14
  • RES – Pin 18
  • DC – Pin 19
  • Vcc –  3.3V
  • GND – Ground

ESP32 SPI - NuttX RTOS SPI

Concept of the Driver and Application

In this NuttX RTOS SPI example, we have used the inbuilt SPI driver. But, at the time we write this tutorial, there is no initialization of the NuttX RTOS SPI for ESP32 Boards available in the NuttX RTOS. They may add this in the future. So, we decided to initialize the SPI using another driver. So, we have created a new driver which initializes the SPI bus and registers the SPI bus to the built-in SPI character driver.

The concept is to write some strings and display the image in the SSD1306 OLED display.

This example also uses the GPIO driver written in our previous tutorial to control the RES and DC pin.

Writing Driver

We have created the esp32_etx_spi.c file under boards/xtensa/esp32/common/src/ and esp32_etx_spi.h file under boards/xtensa/esp32/common/include/. Some other changes also there, in Makefile, esp32_bringup.c, Make.defs, Kconfig. Please look at this commit to see all the changes.

esp32_etx_spi.c

Here, we are just initializing the SPI bus and registering that with the NuttX RTOs SPI character driver.

[Get this code from GitHub]

/****************************************************************************
 * boards/xtensa/esp32/common/src/esp32_etx_spi.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <stdbool.h>
#include <debug.h>

#include <nuttx/board.h>
#include <arch/board/board.h>
#include <nuttx/arch.h>
#include "esp32-devkitc.h"
#include "esp32_spi.h"
#include <nuttx/spi/spi_transfer.h>

#ifdef CONFIG_ESP32_ETX_SPI

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

#ifdef CONFIG_ESP32_SPI2

#define ETX_SPI_2   ( 2 )

struct spi_dev_s *etx_spi2;

#endif

#ifdef CONFIG_ESP32_SPI3

#define ETX_SPI_3   ( 3 )

struct spi_dev_s *etx_spi3;

#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: etx_spi_driver_init
 *
 * Description:
 *   Initialize the EmbeTronicX SPI device Driver. 
 *   This will create the device file as "/dev/spiX"
 *			X = 2 or 3
 *
 * 
 *   return negative number on failure.
 *   return 0 on success.
 *
 ****************************************************************************/

int etx_spi_driver_init( void )
{
  int ret = 0;

  // do while( false ) loop to break if any error.
  do
  {
#ifdef CONFIG_ESP32_SPI2

    // Initialize the SPI2 bus and register the driver
    etx_spi2 = esp32_spibus_initialize( ETX_SPI_2 );
    if ( !etx_spi2 )
    {
      ret =  -ENODEV;
      _err("ERROR: esp32_spibus_initialize() failed : SPI%d\n", ETX_SPI_2);
      break;
    }

    ret = spi_register( etx_spi2, ETX_SPI_2 );
    if (ret < 0)
    {
      _err("ERROR: spi_register() failed: SPI%d - %d\n", ETX_SPI_2, ret);
      break;
    }
#endif

#ifdef CONFIG_ESP32_SPI3
    // Initialize the SPI3 bus and register the driver
    etx_spi3 = esp32_spibus_initialize( ETX_SPI_3 );
    if ( !etx_spi3 )
    {
      ret =  -ENODEV;
      _err("ERROR: esp32_spibus_initialize() failed : SPI%d\n", ETX_SPI_3);
      break;
    }

    ret = spi_register( etx_spi2, ETX_SPI_3 );
    if (ret < 0)
    {
      _err("ERROR: spi_register() failed: SPI%d - %d\n", ETX_SPI_3, ret);
      break;
    }
#endif

  } while( false );

  return ( ret );
}

#endif /* CONFIG_ESP32_ETX_SPI */

esp32_etx_spi.h

[Get this code from GitHub]

/****************************************************************************
 * boards/xtensa/esp32/common/include/esp32_etx_spi.h
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

#ifndef __BOARDS_XTENSA_ESP32_COMMON_INCLUDE_ETX_SPI_H
#define __BOARDS_XTENSA_ESP32_COMMON_INCLUDE_ETX_SPI_H

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif

/****************************************************************************
 * Name: etx_spi_driver_init
 *
 * Description:
 *   Initialize the EmbeTronicX SPI device Driver. 
 *   This will create the device file as "/dev/spiX"
 *			X = 2 or 3
 *
 * 
 *   return negative number on failure.
 *   return 0 on success.
 *
 ****************************************************************************/

#ifdef CONFIG_ESP32_ETX_SPI
int etx_spi_driver_init( void );
#endif



#undef EXTERN
#ifdef __cplusplus
}
#endif

#endif /* __BOARDS_XTENSA_ESP32_COMMON_INCLUDE_ETX_SPI_H */

Along with this, we have called this function etx_spi_driver_init() in esp32_bringup() function. Please see these changes in this commit.

Writing Application

In application, I am going to create a task called “etx_spi_ssd1306_task” using the task_create() function. Then in that task,

  • Open the GPIO Driver (/dev/etx/gpio).
  • Then registers and configures the RES and DC pin using that GPIO driver.
  • Open the SPI driver (/dev/spi2).
  • Initializes the SSD1306 OLED Display using etx_spi_ssd1306_DisplayInit().
  • Then printing and scrolling the string “Welcome To EmbeTronicX“.
  • Then displays the logo and invert the display continuously.
  • Unregister the GPIOs and close the drivers if any error.

I am creating a new directory called etx_spi under apps/examplesInside the directory, I am adding the below files.

You can check the application changes that I have added as part of this tutorial in the GitHub commit. And you can get the complete source code from GitHub.

etx_spi_ssd1306_app.c

[Get this code from GitHub]

/****************************************************************************
 * examples/etx_spi/etx_spi_ssd1306_app.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sched.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <nuttx/spi/spi_transfer.h>
#include "etx_spi_ssd1306.h"

#ifdef CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP


/****************************************************************************
 * Private Data
 ****************************************************************************/

static etx_gpio reset_pin;   // GPIO Pin Structure for Reset pin
static etx_gpio dc_pin;      // GPIO Pin Structure for DC pin

static int fd_gpio = -1;     // File descriptor for GPIO driver
static int fd_spi  = -1;     // File descriptor for SPI driver

/*
** Variable to store Line Number and Cursor Position.
*/ 
static uint8_t SSD1306_LineNum   = 0;
static uint8_t SSD1306_CursorPos = 0;
static uint8_t SSD1306_FontSize  = SSD1306_DEF_FONT_SIZE;

/*
**  EmbeTronicX Logo
*/

static const uint8_t etx_logo[1024] = {
  0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xE1, 0xF9, 0xFF, 0xF9, 0xE1, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF8, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF,
  0xF8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x0F, 0xF8, 0xF7, 0x00, 0xBF, 0xC0, 0x7F,
  0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x3F, 0xE0, 0x0F, 0x7F, 0x00, 0xFF, 0x7F, 0x80,
  0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x04, 0xFC, 0xFC, 0x0C, 0x0C, 0x0C, 0x0C, 0x7C, 0x00, 0x00, 0x00,
  0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x04, 0xFC, 0xF8,
  0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x08,
  0x04, 0x04, 0xFC, 0xFC, 0x04, 0x04, 0x04, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x98, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x00, 0x00, 0x04, 0x0C, 0x38, 0xE0, 0x80, 0xE0, 0x38, 0x0C, 0x04, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0x3F, 0xFF, 0xFF, 0x00, 0xFD, 0xFE, 0xFF,
  0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x06, 0x06, 0x06, 0x06, 0xE0, 0x00, 0x00, 0x00,
  0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
  0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x7C, 0xFF, 0x11, 0x10, 0x1F, 0x1F, 0x00, 0x00, 0x00,
  0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x7C, 0xFF, 0x01, 0x00, 0x01, 0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x87,
  0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1F, 0x3F, 0xFC, 0xF7, 0x00, 0xDF, 0xE3, 0x7D,
  0x3E, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00,
  0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
  0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x01, 0x03, 0x02, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x03,
  0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x02, 0x02, 0x03,
  0x00, 0x00, 0x02, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0xFF, 0x03, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x88, 0x88, 0x08, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x20, 0xE0,
  0x00, 0xFC, 0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xFC, 0x00,
  0xE0, 0x20, 0x20, 0xFC, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xFC, 0x00, 0x00,
  0x00, 0x08, 0x08, 0xF8, 0x08, 0x08, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xFC, 0x20, 0x20, 0x00, 0xE0,
  0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0x00, 0xEC, 0x00, 0x20, 0x20, 0x20, 0xE0, 0x00, 0xFC,
  0x00, 0xE0, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0xC8, 0x38, 0x00, 0x00, 0xE0,
  0x20, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0xE0, 0x00, 0xE0, 0x20, 0x20, 0xE0, 0x00, 0x00, 0x00, 0xFF,
  0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x9C, 0x9C, 0x88, 0x8E, 0x83, 0x98, 0x83, 0x8E, 0x88,
  0x88, 0x9C, 0x9C, 0x80, 0x80, 0x87, 0x84, 0x84, 0x84, 0x80, 0x87, 0x80, 0x80, 0x87, 0x80, 0x87,
  0x80, 0x87, 0x84, 0x84, 0x87, 0x80, 0x87, 0x85, 0x85, 0x85, 0x80, 0x87, 0x84, 0x84, 0x87, 0x80,
  0x87, 0x84, 0x84, 0x87, 0x80, 0x87, 0x85, 0x85, 0x85, 0x80, 0x87, 0x84, 0x84, 0x87, 0x80, 0x80,
  0x80, 0x80, 0x80, 0x87, 0x80, 0x80, 0x80, 0x87, 0x84, 0x87, 0x80, 0x87, 0x84, 0x84, 0x80, 0x87,
  0x84, 0x84, 0x87, 0x80, 0x87, 0x80, 0x80, 0x80, 0x87, 0x80, 0x87, 0x85, 0x85, 0x87, 0x80, 0x87,
  0x80, 0x85, 0x85, 0x85, 0x87, 0x80, 0x80, 0x80, 0x80, 0x86, 0x85, 0x84, 0x84, 0x84, 0x80, 0x87,
  0x84, 0x84, 0x87, 0x80, 0x87, 0x80, 0x87, 0x80, 0x87, 0x85, 0x85, 0x85, 0x80, 0x80, 0x80, 0xFF
};

/*
** Array Variable to store the letters.
*/ 
static const unsigned char SSD1306_font[][SSD1306_DEF_FONT_SIZE]= 
{
    {0x00, 0x00, 0x00, 0x00, 0x00},   // space
    {0x00, 0x00, 0x2f, 0x00, 0x00},   // !
    {0x00, 0x07, 0x00, 0x07, 0x00},   // "
    {0x14, 0x7f, 0x14, 0x7f, 0x14},   // #
    {0x24, 0x2a, 0x7f, 0x2a, 0x12},   // $
    {0x23, 0x13, 0x08, 0x64, 0x62},   // %
    {0x36, 0x49, 0x55, 0x22, 0x50},   // &
    {0x00, 0x05, 0x03, 0x00, 0x00},   // '
    {0x00, 0x1c, 0x22, 0x41, 0x00},   // (
    {0x00, 0x41, 0x22, 0x1c, 0x00},   // )
    {0x14, 0x08, 0x3E, 0x08, 0x14},   // *
    {0x08, 0x08, 0x3E, 0x08, 0x08},   // +
    {0x00, 0x00, 0xA0, 0x60, 0x00},   // ,
    {0x08, 0x08, 0x08, 0x08, 0x08},   // -
    {0x00, 0x60, 0x60, 0x00, 0x00},   // .
    {0x20, 0x10, 0x08, 0x04, 0x02},   // /
    {0x3E, 0x51, 0x49, 0x45, 0x3E},   // 0
    {0x00, 0x42, 0x7F, 0x40, 0x00},   // 1
    {0x42, 0x61, 0x51, 0x49, 0x46},   // 2
    {0x21, 0x41, 0x45, 0x4B, 0x31},   // 3
    {0x18, 0x14, 0x12, 0x7F, 0x10},   // 4
    {0x27, 0x45, 0x45, 0x45, 0x39},   // 5
    {0x3C, 0x4A, 0x49, 0x49, 0x30},   // 6
    {0x01, 0x71, 0x09, 0x05, 0x03},   // 7
    {0x36, 0x49, 0x49, 0x49, 0x36},   // 8
    {0x06, 0x49, 0x49, 0x29, 0x1E},   // 9
    {0x00, 0x36, 0x36, 0x00, 0x00},   // :
    {0x00, 0x56, 0x36, 0x00, 0x00},   // ;
    {0x08, 0x14, 0x22, 0x41, 0x00},   // <
    {0x14, 0x14, 0x14, 0x14, 0x14},   // =
    {0x00, 0x41, 0x22, 0x14, 0x08},   // >
    {0x02, 0x01, 0x51, 0x09, 0x06},   // ?
    {0x32, 0x49, 0x59, 0x51, 0x3E},   // @
    {0x7C, 0x12, 0x11, 0x12, 0x7C},   // A
    {0x7F, 0x49, 0x49, 0x49, 0x36},   // B
    {0x3E, 0x41, 0x41, 0x41, 0x22},   // C
    {0x7F, 0x41, 0x41, 0x22, 0x1C},   // D
    {0x7F, 0x49, 0x49, 0x49, 0x41},   // E
    {0x7F, 0x09, 0x09, 0x09, 0x01},   // F
    {0x3E, 0x41, 0x49, 0x49, 0x7A},   // G
    {0x7F, 0x08, 0x08, 0x08, 0x7F},   // H
    {0x00, 0x41, 0x7F, 0x41, 0x00},   // I
    {0x20, 0x40, 0x41, 0x3F, 0x01},   // J
    {0x7F, 0x08, 0x14, 0x22, 0x41},   // K
    {0x7F, 0x40, 0x40, 0x40, 0x40},   // L
    {0x7F, 0x02, 0x0C, 0x02, 0x7F},   // M
    {0x7F, 0x04, 0x08, 0x10, 0x7F},   // N
    {0x3E, 0x41, 0x41, 0x41, 0x3E},   // O
    {0x7F, 0x09, 0x09, 0x09, 0x06},   // P
    {0x3E, 0x41, 0x51, 0x21, 0x5E},   // Q
    {0x7F, 0x09, 0x19, 0x29, 0x46},   // R
    {0x46, 0x49, 0x49, 0x49, 0x31},   // S
    {0x01, 0x01, 0x7F, 0x01, 0x01},   // T
    {0x3F, 0x40, 0x40, 0x40, 0x3F},   // U
    {0x1F, 0x20, 0x40, 0x20, 0x1F},   // V
    {0x3F, 0x40, 0x38, 0x40, 0x3F},   // W
    {0x63, 0x14, 0x08, 0x14, 0x63},   // X
    {0x07, 0x08, 0x70, 0x08, 0x07},   // Y
    {0x61, 0x51, 0x49, 0x45, 0x43},   // Z
    {0x00, 0x7F, 0x41, 0x41, 0x00},   // [
    {0x55, 0xAA, 0x55, 0xAA, 0x55},   // Backslash (Checker pattern)
    {0x00, 0x41, 0x41, 0x7F, 0x00},   // ]
    {0x04, 0x02, 0x01, 0x02, 0x04},   // ^
    {0x40, 0x40, 0x40, 0x40, 0x40},   // _
    {0x00, 0x03, 0x05, 0x00, 0x00},   // `
    {0x20, 0x54, 0x54, 0x54, 0x78},   // a
    {0x7F, 0x48, 0x44, 0x44, 0x38},   // b
    {0x38, 0x44, 0x44, 0x44, 0x20},   // c
    {0x38, 0x44, 0x44, 0x48, 0x7F},   // d
    {0x38, 0x54, 0x54, 0x54, 0x18},   // e
    {0x08, 0x7E, 0x09, 0x01, 0x02},   // f
    {0x18, 0xA4, 0xA4, 0xA4, 0x7C},   // g
    {0x7F, 0x08, 0x04, 0x04, 0x78},   // h
    {0x00, 0x44, 0x7D, 0x40, 0x00},   // i
    {0x40, 0x80, 0x84, 0x7D, 0x00},   // j
    {0x7F, 0x10, 0x28, 0x44, 0x00},   // k
    {0x00, 0x41, 0x7F, 0x40, 0x00},   // l
    {0x7C, 0x04, 0x18, 0x04, 0x78},   // m
    {0x7C, 0x08, 0x04, 0x04, 0x78},   // n
    {0x38, 0x44, 0x44, 0x44, 0x38},   // o
    {0xFC, 0x24, 0x24, 0x24, 0x18},   // p
    {0x18, 0x24, 0x24, 0x18, 0xFC},   // q
    {0x7C, 0x08, 0x04, 0x04, 0x08},   // r
    {0x48, 0x54, 0x54, 0x54, 0x20},   // s
    {0x04, 0x3F, 0x44, 0x40, 0x20},   // t
    {0x3C, 0x40, 0x40, 0x20, 0x7C},   // u
    {0x1C, 0x20, 0x40, 0x20, 0x1C},   // v
    {0x3C, 0x40, 0x30, 0x40, 0x3C},   // w
    {0x44, 0x28, 0x10, 0x28, 0x44},   // x
    {0x1C, 0xA0, 0xA0, 0xA0, 0x7C},   // y
    {0x44, 0x64, 0x54, 0x4C, 0x44},   // z
    {0x00, 0x10, 0x7C, 0x82, 0x00},   // {
    {0x00, 0x00, 0xFF, 0x00, 0x00},   // |
    {0x00, 0x82, 0x7C, 0x10, 0x00},   // }
    {0x00, 0x06, 0x09, 0x09, 0x06}    // ~ (Degrees)
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/
 
/****************************************************************************
 * Name: etx_spi_ssd1306_ResetDcInit
 *
 * Details : This function Initializes and Configures the Reset and DC Pin
 ****************************************************************************/
static int etx_spi_ssd1306_ResetDcInit( void )
{
  int     ret      = -1;
  uint8_t gpio_val = 1u;      //Initial value of the GPIO
  
  if( fd_gpio >= 0 )
  {
    /* Register the Reset GPIO */
    reset_pin.gpio_type  = ETX_GPIO_OUT;
    reset_pin.gpio_num   = SSD1306_RST_PIN;
    reset_pin.gpio_value = &gpio_val;
    reset_pin.data       = NULL;

    ret = ioctl(fd_gpio, GPIOC_REGISTER, (unsigned long)((uintptr_t)&reset_pin));
    if (ret < 0)
    {
      int errcode = errno;
      printf("ERROR: GPIOC_REGISTER ioctl failed: RST - %d\n", errcode);
    }

    /* Register the DC GPIO */
    dc_pin.gpio_type  = ETX_GPIO_OUT;
    dc_pin.gpio_num   = SSD1306_DC_PIN;
    dc_pin.gpio_value = &gpio_val;
    dc_pin.data       = NULL;
    
    if( ret >= 0 )
    {
      ret = ioctl(fd_gpio, GPIOC_REGISTER, (unsigned long)((uintptr_t)&dc_pin));
      if (ret < 0)
      {
        int errcode = errno;
        printf("ERROR: GPIOC_REGISTER ioctl failed: DC - %d\n", errcode);
      }
    }
  }

  return( ret );
}

/****************************************************************************
 * Name: etx_spi_ssd1306_setRst
 *
 * Details : This function writes the value to the Reset GPIO
 ****************************************************************************/
static int etx_spi_ssd1306_setRst( uint8_t value )
{
  int ret = -1;
  
  if( fd_gpio >= 0 )
  {
    reset_pin.gpio_value = &value;
    
    //write the Reset GPIO value to the driver
    ret = write( fd_gpio, (const void*)&reset_pin, sizeof(reset_pin) );
  }
  return( ret );
}

/****************************************************************************
 * Name: etx_spi_ssd1306_setDc
 *
 * Details : This function writes the value to the DC GPIO
 ****************************************************************************/
static int etx_spi_ssd1306_setDc( uint8_t value )
{
  int ret = -1;

  if( fd_gpio >= 0 )
  {
    dc_pin.gpio_value = &value;
    
    //write the DC GPIO value to the driver
    ret = write( fd_gpio, (const void*)&dc_pin, sizeof(dc_pin) );
  }
  
  return( ret );
}

/****************************************************************************
 * Name: etx_spi_ssd1306_write
 *
 * Details : This function sends the command/data to the Display
 *
 * Argument: is_cmd
 *              true  - if we need to send command
 *              false - if we need to send data
 *           value
 *              value to be transmitted
 ****************************************************************************/
static int etx_spi_ssd1306_write( bool is_cmd, uint8_t value )
{
  int     ret = 0;
  uint8_t pin_value;
  
  if( fd_spi < 0 )
  {
    ret = -1;
  }

  if( ret >= 0 )
  {
    if( is_cmd )
    {
      //DC pin has to be high, if this is command.
      pin_value = 0u;
    }
    else
    {
      //DC pin has to be low, if this is data.
      pin_value = 1u;
    }
    
    ret = etx_spi_ssd1306_setDc( pin_value );
  }

  //send the bytes
  if( ret >= 0 )
  {
    struct  spi_sequence_s seq;
    struct  spi_trans_s trans;
    uint8_t rx_buf[4] = { 0 };
    uint8_t tx_buf[4] = { 0 };
    
    tx_buf[0] = value;
    
    seq.dev       = SPIDEV_ID( SPIDEVTYPE_USER, 0u );
    seq.mode      = SPIDEV_MODE0;                 // See enum spi_mode_e
    seq.nbits     = 8;                            // Number of bits
    seq.frequency = 4000000;                      // SPI frequency (Hz)
    seq.ntrans    = 1;                            // Number of transactions
    seq.trans     = &trans;
    
    trans.deselect = true;                        // De-select after transfer
    trans.delay    = 0;                           // Microsecond delay after tx
    trans.nwords   = 1;                           // Number of words in transfer
    trans.txbuffer = tx_buf;                      // Tx buffer
    trans.rxbuffer = rx_buf;                      // Rx buffer
    
    // Transfer the data
    ret = ioctl( fd_spi, SPIIOC_TRANSFER, (unsigned long)((uintptr_t)&seq) );
  }
  
  return( ret );
}

/*
** This function is specific to the SSD_1306 OLED.
**
**  Arguments:
**      lineNo    -> Line Number
**      cursorPos -> Cursor Position
**   
*/
static void etx_spi_ssd1306_SetCursor( uint8_t lineNo, uint8_t cursorPos )
{
  /* Move the Cursor to specified position only if it is in range */
  if((lineNo <= SSD1306_MAX_LINE) && (cursorPos < SSD1306_MAX_SEG))
  {
    SSD1306_LineNum   = lineNo;                     // Save the specified line number
    SSD1306_CursorPos = cursorPos;                  // Save the specified cursor position
    etx_spi_ssd1306_write(true, 0x21);              // cmd for the column start and end address
    etx_spi_ssd1306_write(true, cursorPos);         // column start addr
    etx_spi_ssd1306_write(true, SSD1306_MAX_SEG-1); // column end addr
    etx_spi_ssd1306_write(true, 0x22);              // cmd for the page start and end address
    etx_spi_ssd1306_write(true, lineNo);            // page start addr
    etx_spi_ssd1306_write(true, SSD1306_MAX_LINE);  // page end addr
  }
}

/*
** This function is specific to the SSD_1306 OLED.
** This function move the cursor to the next line.
**
**  Arguments:
**      none
** 
*/
static void etx_spi_ssd1306_GoToNextLine( void )
{
  /*
  ** Increment the current line number.
  ** roll it back to first line, if it exceeds the limit. 
  */
  SSD1306_LineNum++;
  SSD1306_LineNum = (SSD1306_LineNum & SSD1306_MAX_LINE);
  etx_spi_ssd1306_SetCursor(SSD1306_LineNum,0); /* Finally move it to next line */
}

/*
** This function is specific to the SSD_1306 OLED.
** This function sends the single char to the OLED.
**
**  Arguments:
**      c   -> character to be written
** 
*/
static void etx_spi_ssd1306_PrintChar(unsigned char c)
{
  uint8_t data_byte;
  uint8_t temp = 0;
  /*
  ** If we character is greater than segment len or we got new line charcter
  ** then move the cursor to the new line
  */ 
  if( (( SSD1306_CursorPos + SSD1306_FontSize ) >= SSD1306_MAX_SEG ) ||
      ( c == '\n' )
  )
  {
    etx_spi_ssd1306_GoToNextLine();
  }
  // print charcters other than new line
  if( c != '\n' )
  {
  
    /*
    ** In our font array (SSD1306_font), space starts in 0th index.
    ** But in ASCII table, Space starts from 32 (0x20).
    ** So we need to match the ASCII table with our font table.
    ** We can subtract 32 (0x20) in order to match with our font table.
    */
    c -= 0x20;  //or c -= ' ';
    do
    {
      data_byte= SSD1306_font[c][temp];         // Get the data to be displayed from LookUptable
      etx_spi_ssd1306_write(false, data_byte);  // write data to the OLED
      SSD1306_CursorPos++;
      
      temp++;
      
    } while ( temp < SSD1306_FontSize);
    etx_spi_ssd1306_write(false, 0x00);         //Display the data
    SSD1306_CursorPos++;
  }
}

/*
** This function is specific to the SSD_1306 OLED.
** This function sends the string to the OLED.
**
**  Arguments:
**      str   -> string to be written
** 
*/
static void etx_spi_ssd1306_String(char *str)
{
  while(*str)
  {
    etx_spi_ssd1306_PrintChar(*str++);
  }
}

/*
** This function is specific to the SSD_1306 OLED.
** This function inverts the display.
**
**  Arguments:
**      need_to_invert   -> true  - invert display
**                          false - normal display        
** 
*/
static void etx_spi_ssd1306_InvertDisplay(bool need_to_invert)
{
  if(need_to_invert)
  {
    etx_spi_ssd1306_write(true, 0xA7); // Invert the display
  }
  else
  {
    etx_spi_ssd1306_write(true, 0xA6); // Normal display
  }
}

/*
** This function is specific to the SSD_1306 OLED.
** This function sets the brightness of  the display.
**
**  Arguments:
**      brightnessValue   -> brightness value
** 
*/
static void etx_spi_ssd1306_SetBrightness(uint8_t brightnessValue)
{
    etx_spi_ssd1306_write(true, 0x81);            // Contrast command
    etx_spi_ssd1306_write(true, brightnessValue); // Contrast value (default value = 0x7F)
}

/*
** This function is specific to the SSD_1306 OLED.
** This function Scrolls the data right/left in horizontally.
**
**  Arguments:
**      is_left_scroll   -> true  - left horizontal scroll
                            false - right horizontal scroll
        start_line_no    -> Start address of the line to scroll 
        end_line_no      -> End address of the line to scroll                 
** 
*/
static void etx_spi_ssd1306_StartScrollHorizontal( bool is_left_scroll,
                                                   uint8_t start_line_no,
                                                   uint8_t end_line_no
                                                 )
{
  if(is_left_scroll)
  {
    // left horizontal scroll
    etx_spi_ssd1306_write(true, 0x27);
  }
  else
  {
    // right horizontal scroll 
    etx_spi_ssd1306_write(true, 0x26);
  }
  
  etx_spi_ssd1306_write(true, 0x00);            // Dummy byte (dont change)
  etx_spi_ssd1306_write(true, start_line_no);   // Start page address
  etx_spi_ssd1306_write(true, 0x00);            // 5 frames interval
  etx_spi_ssd1306_write(true, end_line_no);     // End page address
  etx_spi_ssd1306_write(true, 0x00);            // Dummy byte (dont change)
  etx_spi_ssd1306_write(true, 0xFF);            // Dummy byte (dont change)
  etx_spi_ssd1306_write(true, 0x2F);            // activate scroll
}

/*
** This function is specific to the SSD_1306 OLED.
** This function Scrolls the data in vertically and right/left horizontally
** (Diagonally).
**
**  Arguments:
**      is_vertical_left_scroll -> true  - vertical and left horizontal scroll
**                                 false - vertical and right horizontal scroll
**      start_line_no           -> Start address of the line to scroll 
**      end_line_no             -> End address of the line to scroll 
**      vertical_area           -> Area for vertical scroll (0-63)
**      rows                    -> Number of rows to scroll vertically             
** 
*/
static void etx_spi_ssd1306_StartScrollVerticalHorizontal( 
                                                   bool is_vertical_left_scroll,
                                                   uint8_t start_line_no,
                                                   uint8_t end_line_no,
                                                   uint8_t vertical_area,
                                                   uint8_t rows
                                                 )
{
  
  etx_spi_ssd1306_write(true, 0xA3);            // Set Vertical Scroll Area
  etx_spi_ssd1306_write(true, 0x00);            // Check datasheet
  etx_spi_ssd1306_write(true, vertical_area);   // area for vertical scroll
  
  if(is_vertical_left_scroll)
  {
    // vertical and left horizontal scroll
    etx_spi_ssd1306_write(true, 0x2A);
  }
  else
  {
    // vertical and right horizontal scroll 
    etx_spi_ssd1306_write(true, 0x29);
  }
  
  etx_spi_ssd1306_write(true, 0x00);            // Dummy byte (dont change)
  etx_spi_ssd1306_write(true, start_line_no);   // Start page address
  etx_spi_ssd1306_write(true, 0x00);            // 5 frames interval
  etx_spi_ssd1306_write(true, end_line_no);     // End page address
  etx_spi_ssd1306_write(true, rows);            // Vertical scrolling offset
  etx_spi_ssd1306_write(true, 0x2F);            // activate scroll
}

/*
** This function is specific to the SSD_1306 OLED.
** This function disables the scroll.
**
**  Arguments:
**      none.       
** 
*/
static void etx_spi_ssd1306_DeactivateScroll( void )
{
  etx_spi_ssd1306_write(true, 0x2E); // Deactivate scroll
}

/****************************************************************************
 * Name: etx_spi_ssd1306_fill
 *
 * Details : This function fills the data to the Display
 ****************************************************************************/
static void etx_spi_ssd1306_fill( uint8_t data )
{
  // 8 pages x 128 segments x 8 bits of data
  unsigned int total  = ( SSD1306_MAX_SEG * (SSD1306_MAX_LINE + 1) );
  unsigned int i      = 0;
  
  //Fill the Display
  for(i = 0; i < total; i++)
  {
    etx_spi_ssd1306_write(false, data);
  }
}

/****************************************************************************
 * Name: etx_spi_ssd1306_ClearDisplay
 *
 * Details : This function clears the Display
 ****************************************************************************/
static void etx_spi_ssd1306_ClearDisplay( void )
{
  //Set cursor
  etx_spi_ssd1306_SetCursor(0,0);
  
  etx_spi_ssd1306_fill( 0x00 );
}

/****************************************************************************
 * Name: etx_spi_ssd1306_DisplayInit
 *
 * Details : This function Initializes the Display
 ****************************************************************************/
static int etx_spi_ssd1306_DisplayInit(void)
{
  int ret = 0;

  //Make the RESET Line to 0
  ret = etx_spi_ssd1306_setRst( 0u );
  
  if( ret >= 0 )
  {
    //usleep(100000);                          // delay
    //Make the RESET Line to 1
    ret = etx_spi_ssd1306_setRst( 1u );
    //usleep(100000);                          // delay
  }

  /*
  ** Commands to initialize the SSD_1306 OLED Display
  */
  etx_spi_ssd1306_write(true, 0xAE); // Entire Display OFF
  etx_spi_ssd1306_write(true, 0xD5); // Set Display Clock Divide Ratio and Oscillator Frequency
  etx_spi_ssd1306_write(true, 0x80); // Default Setting for Display Clock Divide Ratio and Oscillator Frequency that is recommended
  etx_spi_ssd1306_write(true, 0xA8); // Set Multiplex Ratio
  etx_spi_ssd1306_write(true, 0x3F); // 64 COM lines
  etx_spi_ssd1306_write(true, 0xD3); // Set display offset
  etx_spi_ssd1306_write(true, 0x00); // 0 offset
  etx_spi_ssd1306_write(true, 0x40); // Set first line as the start line of the display
  etx_spi_ssd1306_write(true, 0x8D); // Charge pump
  etx_spi_ssd1306_write(true, 0x14); // Enable charge dump during display on
  etx_spi_ssd1306_write(true, 0x20); // Set memory addressing mode
  etx_spi_ssd1306_write(true, 0x00); // Horizontal addressing mode
  etx_spi_ssd1306_write(true, 0xA1); // Set segment remap with column address 127 mapped to segment 0
  etx_spi_ssd1306_write(true, 0xC8); // Set com output scan direction, scan from com63 to com 0
  etx_spi_ssd1306_write(true, 0xDA); // Set com pins hardware configuration
  etx_spi_ssd1306_write(true, 0x12); // Alternative com pin configuration, disable com left/right remap
  etx_spi_ssd1306_write(true, 0x81); // Set contrast control
  etx_spi_ssd1306_write(true, 0x80); // Set Contrast to 128
  etx_spi_ssd1306_write(true, 0xD9); // Set pre-charge period
  etx_spi_ssd1306_write(true, 0xF1); // Phase 1 period of 15 DCLK, Phase 2 period of 1 DCLK
  etx_spi_ssd1306_write(true, 0xDB); // Set Vcomh deselect level
  etx_spi_ssd1306_write(true, 0x20); // Vcomh deselect level ~ 0.77 Vcc
  etx_spi_ssd1306_write(true, 0xA4); // Entire display ON, resume to RAM content display
  etx_spi_ssd1306_write(true, 0xA6); // Set Display in Normal Mode, 1 = ON, 0 = OFF
  etx_spi_ssd1306_write(true, 0x2E); // Deactivate scroll
  etx_spi_ssd1306_write(true, 0xAF); // Display ON in normal mode

  // Clear the display
  etx_spi_ssd1306_ClearDisplay();
  
  printf("ETX_SPI_SSD1306: OLED Iitialized\r\n");
  
  return( ret );
}


/****************************************************************************
 * Name: etx_spi_ssd1306_task
 ****************************************************************************/

static int etx_spi_ssd1306_task(int argc, char *argv[])
{
  int ret = 0;
  
  printf("ETX_SPI_SSD1306: Starting the Task\n");
 
  /* Initialize and configure the GPIOs ( Reset and DC pin) */
  
  // Open the GPIO driver
  fd_gpio = open( ETX_GPIO_DRIVER_PATH, O_WRONLY);
  if( fd_gpio < 0 )
  {
    printf("ERROR - Failed to open %s: %d\n", ETX_GPIO_DRIVER_PATH, errno);
    ret = -1;
  }
 
  // Configure the GPIOs
  if( ret >= 0 )
  {
    ret = etx_spi_ssd1306_ResetDcInit();
  }
 
  /* Initialize and configure the SPI*/
  
  if( ret >= 0 )
  {
    // Open the SPI driver
    fd_spi = open( ETX_SPI_DRIVER_PATH, O_WRONLY);
    if( fd_spi < 0 )
    {
      printf("ERROR - Failed to open %s: %d\n", ETX_SPI_DRIVER_PATH, errno);
      ret = -1;
    }

  }
  
  if( ret >= 0 )
  {
    // Initialize the SSD1306 OLED
    ret = etx_spi_ssd1306_DisplayInit();
  }

  /* Print the String */
  //Set cursor
  etx_spi_ssd1306_SetCursor(0,0);
  
  etx_spi_ssd1306_SetBrightness( 255 );           // Full brightness
  etx_spi_ssd1306_InvertDisplay( false );         // Invert the dispaly : OFF
  
  // Enable the Horizontal scroll for first 3 lines
  etx_spi_ssd1306_StartScrollHorizontal( true, 0, 2);
  
  //Write String to OLED
  etx_spi_ssd1306_String("Welcome\nTo\nEmbeTronicX\n\n");

  usleep(9000000);
  etx_spi_ssd1306_ClearDisplay();                 // Clear Display
  etx_spi_ssd1306_DeactivateScroll();
  
  /* Print the Image */
 
  //Set cursor
  etx_spi_ssd1306_SetCursor(0,0);
  
  for(int i = 0; i < ( SSD1306_MAX_SEG * (SSD1306_MAX_LINE + 1) ); i++ )
  {
    ret = etx_spi_ssd1306_write(false, etx_logo[i]);
  }
  
  // infinite while which does nothing
  while( ret >= 0 )
  { 
    etx_spi_ssd1306_InvertDisplay( true );    // Invert the dispaly : ON
    usleep( 500000 );
    etx_spi_ssd1306_InvertDisplay( false );   // Invert the dispaly : OFF
    usleep( 500000 );
  }
  
  /* Unregister the GPIOs */
  ret = ioctl(fd_gpio, GPIOC_UNREGISTER, (unsigned long)((uintptr_t)&reset_pin));
  if (ret < 0)
  {
    int errcode = errno;
    printf("ERROR: GPIOC_UNREGISTER ioctl failed: RST - %d\n", errcode);
  }
  
  ret = ioctl(fd_gpio, GPIOC_UNREGISTER, (unsigned long)((uintptr_t)&dc_pin));
  if (ret < 0)
  {
    int errcode = errno;
    printf("ERROR: GPIOC_UNREGISTER ioctl failed: DC - %d\n", errcode);
  }

  //Close the drivers
  close(fd_gpio);
  close(fd_spi);
  
  printf("ETX_SPI_SSD1306: ERROR - Task finishing ret = %d\n", ret);

  return EXIT_FAILURE;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * main
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  int ret;
  
  printf("ETX_SPI_SSD1306: Starting the Application\n");

  
  ret = task_create( "ETX_SPI_SSD1306",                       	   // Task Name
                     CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP_PRIORITY, // Task priority
                     CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP_STACKSIZE,// Task Stack size
                     etx_spi_ssd1306_task,                    	   // Task function
                     NULL
                   );
  if (ret < 0)
  {
    int errcode = errno;
    printf("ETX_SPI_SSD1306: ERROR: Failed to start etx_spi_ssd1306_task: %d\n",
                                                                       errcode);
    return EXIT_FAILURE;
  }
  
  return EXIT_SUCCESS;
}

#endif //#ifdef CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP

etx_spi_ssd1306.h

[Get this code from GitHub]

/****************************************************************************
 * examples/etx_spi/etx_spi_ssd1306.h
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#ifdef CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP

#define SPIIOC_TRANSFER         _SPIIOC(0x0001) // IOCTL Command

#define GPIOC_REGISTER          _GPIOC(1)       // IOCTL command to register
#define GPIOC_UNREGISTER        _GPIOC(2)       // IOCTL command to unregister

#define ETX_SPI_DRIVER_PATH     "/dev/spi2"     // SPI Driver path (SPI 2)
#define ETX_GPIO_DRIVER_PATH    "/dev/etx_gpio" // GPIO Driver path

#define SSD1306_RST_PIN  CONFIG_EXAMPLES_ETX_SPI_SSD1306_RST_PIN // Reset Pin
#define SSD1306_DC_PIN   CONFIG_EXAMPLES_ETX_SPI_SSD1306_DC_PIN  // data/cmd Pin 

#define SSD1306_MAX_SEG         ( 128 )         // Maximum segment
#define SSD1306_MAX_LINE        (   7 )         // Maximum line
#define SSD1306_DEF_FONT_SIZE   (   5 )         // Default font size

typedef enum
{
  ETX_GPIO_IN,        // GPIO as input
  ETX_GPIO_OUT,       // GPIO as ouput
  ETX_GPIO_IN_INT     // GPIO as input and enable the interrupt
} GPIO_TYPE;

typedef struct
{
  GPIO_TYPE gpio_type;    // GPIO type
  uint8_t   gpio_num;     // GPIO number
  uint8_t  *gpio_value;   // GPIO value
  void     *data;         // Data
}etx_gpio;

#endif //#ifdef CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP

Makefile

############################################################################
# apps/examples/etx_spi/Makefile
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################

include $(APPDIR)/Make.defs

# ETX SPI built-in application info

PROGNAME  = $(CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP_PROGNAME)
PRIORITY  = $(CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP_PRIORITY)
STACKSIZE = $(CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP_STACKSIZE)
MODULE    = $(CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP)

# ETX SPI SSD1306 OLED Example

ifeq ($(CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP),y)
MAINSRC += etx_spi_ssd1306_app.c
endif

include $(APPDIR)/Application.mk

Make.defs

############################################################################
# apps/examples/etx_spi/Make.defs
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################

ifneq ($(CONFIG_EXAMPLES_ETX_SPI_SSD1306_APP),)
CONFIGURED_APPS += $(APPDIR)/examples/etx_spi
endif

Kconfig

#
# For a description of the syntax of this configuration file,
# see the file kconfig-language.txt in the NuttX tools repository.
#

config EXAMPLES_ETX_SPI_SSD1306_APP
  tristate "EmbeTronicX SSD1306 OLED App"
  default n
  depends on ESP32_ETX_SPI && ESP32_ETX_GPIO
  ---help---
    Enable the EmbeTronicX SSD1306 OLED app that write the text,
    Image to the OLED Display.

config EXAMPLES_ETX_SPI_SSD1306_APP_PROGNAME
  string "Program name"
  default "etx_spi_oled"
  depends on EXAMPLES_ETX_SPI_SSD1306_APP
  ---help---
    This is the name of the program that will be used when the NSH ELF
    program is installed.

config EXAMPLES_ETX_SPI_SSD1306_APP_PRIORITY
  int "etx_spi_oled task priority"
  default 100
  depends on EXAMPLES_ETX_SPI_SSD1306_APP

config EXAMPLES_ETX_SPI_SSD1306_APP_STACKSIZE
  int "etx_spi_oled stack size"
  default DEFAULT_TASK_STACKSIZE
  depends on EXAMPLES_ETX_SPI_SSD1306_APP
  
config EXAMPLES_ETX_SPI_SSD1306_RST_PIN
  int "SSD1306 Reset Pin number"
  default 18
  range 0 39
  depends on EXAMPLES_ETX_SPI_SSD1306_APP

config EXAMPLES_ETX_SPI_SSD1306_DC_PIN
  int "SSD1306 Data/Command Pin number"
  default 19
  range 0 39
  depends on EXAMPLES_ETX_SPI_SSD1306_APP
  

Building the driver and app

Let’s build our app and the NuttX RTOS SPI driver together. I have created the build.sh shell script to combine all the required steps into one file.

build.sh

This build.sh has been added to the nuttx/ directory. You can get the build.sh from GitHub.

# !/bin/bash
# A simple bash script to build the NuttX RTOS
#export ESP32 env
. $HOME/esp/esp-idf/export.sh
if [ $1 -eq 1 ]
then
  #distclean
  echo "Dist clean"
  make distclean
  
  #ESP32 configure
  echo "configuring ESP32 with NuttX RTOS"
  ./tools/configure.sh esp32-devkitc:nsh
fi
#menuconfig
make menuconfig
#make and flash
make download ESPTOOL_PORT=/dev/ttyUSB0

The above script has one argument. If you pass 1, then it does distclean and initializes the board configs. If you pass other than 1, then it does only the make menuconfig and make.

Run the below command in NuttX/nuttx directory.

. ./build.sh 1

When you run the above command, you should get the menuconfig window. In that menuconfig, we have to enable the SPI, SPI driver, ETX GPIO Driver, and the etx_spi_oled application. Let’s do that.

Just navigate to System Type —> ESP32 Peripheral Selection —> and check (enable) the SPI2 like the below image.

After that, let’s enable the SPI driver by navigating to Device drivers —>enable the SPI Driver Support. And go to that menu and enable the SPI Character Driver like the below image.

Then, enable the EmbeTronicX SPI driver and GPIO driver by navigating to Board Selection —> EmbeTronicX Drivers > enable the EmbeTronicX GPIO driver and EmbeTronicX SPI driver like the below image.

That’s all from the driver. Let’s enable the Application that we have written earlier in this tutorial. Navigate to Application Configuration —> Examples —> and check (enable) the EmbeTronicX SSD1306 OLED App like the below image.

Enable OLED App - NuttX RTOS SPI

That’s all. Let’s build the NuttX now.

Save and Exit the menuconfig and let it build. Once it is trying to connect, hold the BOOT button. Then it will start flashing.

Once you build and flashed it, you connect the device using the picocom using the picocom -b 115200 /dev/ttyUSB0 command.

Now you will get the NuttShell (nsh). Enter the ? or help command. You will get the etx_spi_oled app. Now run the etx_spi_oled app.

Output – ESP32 SSD1306 Interfacing

Once the system is booted, then run the etx_spi_oled application. You should see the Welcome To EmbeTronnicX and Logo in the SSD1306 OLED Display. Please refer to the video that we have attached below.

How to put our own image into the OLED Display?

Some of them don’t want to put our(EmbeTronicX) logo into their OLED display. They wanted to try theirs. In that case, you can follow the below steps and put your pictures on the display. There are plenty of ways to that. But here, we have explained two methods. For that two methods, certain steps are common.

  1. Take one picture that you want to use.
  2. Resize that picture to 128×64 pixels using any application like paint, etc.
  3. Then convert that to a bitmap image like below( I have used paint).

Then once after the conversion, then you can try any one of the methods below.

  1. Using Software
  2. Using online webpage

Using Software

  • If you are using the windows machine, then download the LCD Assistant app by going to http://en.radzio.dxp.pl/bitmap_converter/.
  • Then open the LCD Assistant,  File –> Load Image, then add your converted .bmp image like below.

LCD Assistant - NuttX RTOS SPI

  • Then make sure that you are selecting the Vertical Byte orientation and keep other settings by default.
  • Then click File –> Save output. Give the file name. If you open that file, then there will be a 1024 bytes array. You can copy that array and paste it to our application’s etx_logo[1024] array.

Using online webpage

  • Navigate to http://javl.github.io/image2cpp/.
  • Click the Choose Files and select your .bmp file.
  • Select the Vertical orientation.
  • Then press the Generate code. Refer to the below image.
  • You copy that array and paste it to our application’s etx_logo[1024] array.

image2cpp - NuttX RTOS SPI

Then build and load the new nuttx.bin by following this. You should see your picture on the OLED. Enjoy!!!

Demonstration

The code walk-through and output have been given in the below video.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials - STM32Zephyr RTOS Tutorials - ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Table of Contents