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
- ESP32 Dev board
- Laptop machine (Host System)
- SSD1306 OLED
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.
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.
- Command
- 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.
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.
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.
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
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/examples
. Inside 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.
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.
- Take one picture that you want to use.
- Resize that picture to 128×64 pixels using any application like paint, etc.
- 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.
- Using Software
- 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.
- 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.
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.

Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I’m SLR. I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!