This article is a continuation of the Series on Linux Device Drivers and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the SPI Device Driver Tutorial (SPI Protocol Driver) – Linux Device Driver Tutorial Part 47.
We are using the Raspberry PI 4 Model B for this demonstration.
You can find a video explanation of this tutorial here. You can also find all the Linux device driver’s video playlists here.
Prerequisites
In this tutorial, we will be using the concepts which have been explained already in the below-given tutorials. So I would request you to go through those tutorials first if you are not familiar with those topics.
Hardware Required
- Raspberry Pi
- SSD1306 OLED I2C Display
Bring up Raspberry PI
- Install Raspberry Pi OS (32-bit) with desktop on the SD card.
- Then install the kernel header using
sudo apt install raspberrypi-kernel-headers
For your information, In our Raspberry PI 4 board, kernel 5.10.27-v7l is installed.
Enable SPI in the Raspberry PI
In order to write the SPI driver, we have to enable the SPI bus. In this tutorial, we are using SPI 1. To enable SPI 1, please add the below line in the /boot/config.txt
.
dtoverlay=spi1-1cs,cs0_spidev=disabled
The above line enables the SPI 1 and it disables the SPIDev application. So that, we can use the SPI 1 bus. You can refer to this if you have any doubts.
SPI Device Driver Tutorial
An SPI – Serial Peripheral Interface protocol is synchronous serial interface technology introduced by Motorola. consists of one master device and one or more slave devices.
SPI needs 4 wires at least. But some of the recent day devices support 3 pin mode and 4pin modes also.
- MOSI – Master Out Slave In
- MISO – Master In Slave Out
- SCLK – Serial Clock
- SS/CS/CE – Salve Select/Chip Select/Chip Enable
You can read more about SPI Communication.
SPI Subsystem in Linux
The SPI device driver in Linux is mainly managed by the SPI subsystem, and it is divided into 3 sections.
- SPI Core
- SPI Controller Driver
- SPI Protocol Driver
SPI Core
The SPI core provides APIs for the definition of core data structures, registration, and cancellation management of SPI controller drivers and device drivers. It is a hardware platform-independent layer, that shields the differences of the physical bus controller downwards and defines a unified access strategy and interface. It provides a unified interface upwards. So that, the SPI device driver can send and receive data through the SPI bus controller. In Linux kernel, the SPI core code is located in kernel/drivers/spi/spi.c.
SPI Controller Driver
The SPI Controller driver is the platform-specific driver. So, each SoC manufacturer has to write this driver for their platform or MCU. controllers These SPI controller drivers may be built into System-On-Chip processors, and often support both Master and Slave roles. These drivers touch hardware registers and may use DMA or they can be GPIO bit bangers, needing just GPIO pins. Its responsibility is to implement a corresponding read and write method for each SPI bus in the system. Physically, each SPI controller can connect several SPI slave devices. When the system is turned on, the SPI controller driver is loaded first. A controller driver is used to support the reading and writing of a specific SPI bus. So, this SPI controller driver is similar to the adapter/bus driver in the I2C.
You can see the all SPI controller driver in kernel/drivers/spi/.
We will see how to write the SPI controller driver in our next tutorial.
SPI Protocol Driver
Each SPI bus controller may connect to multiple slave devices. This driver is used to communicate to specific devices through the SPI bus. This is similar to the I2C client driver.
In this tutorial, we will just focus on the SPI protocol driver.
SPI Protocol Driver in Linux Kernel
Steps that involve writing the SPI protocol device driver are given below.
- Get the SPI Controller driver
- Add the slave device to the SPI Controller
- Configure the SPI
- Now the driver is ready. So you can transfer the data between master and slave.
- Once you are done, then remove the device.
Get the SPI Controller driver
First, you need to get the controller driver. To do that, you can use the below API. Before using that API, you need to know which SPI bus you are using. In this example, I am using the SPI Bus 1.
where,
Return It returns a pointer to the relevant spi_controller (which the caller must release), or |
Example
struct spi_master *master; master = spi_busnum_to_master( 1 );
Add the slave device to the SPI Controller
In this step, you need to create a spi_board_info
structure that has the slave device’s details.
struct spi_board_info { char modalias[SPI_NAME_SIZE]; const void *platform_data; const struct property_entry *properties; void *controller_data; int irq; u32 max_speed_hz; u16 bus_num; u16 chip_select; u32 mode; }; where,
|
Once you have filled this structure, then you can use the below API to add the slave device to the SPI controller driver.
where,
Return It returns the new device, or |
This spi_new_device()
is enough as this function handles the spi_alloc_device()
and spi_add_device()
inside.
Example
struct spi_board_info etx_spi_device_info = { .modalias = "etx-spi-ssd1306-driver", .max_speed_hz = 4000000, // speed your device (slave) can handle .bus_num = SPI_BUS_NUM, // SPI 1 .chip_select = 0, // Use 0 Chip select (GPIO 18) .mode = SPI_MODE_0 // SPI mode 0 }; static struct spi_device *etx_spi_device; etx_spi_device = spi_new_device( master, &etx_spi_device_info ); if( etx_spi_device == NULL ) { pr_err("FAILED to create slave.\n"); return -ENODEV; }
Configure the SPI
Till now, we have added the slave device to the controller driver. If we change any mode or clock frequency, then we have to call this below API to take effect on the bus.
where,
Return It returns zero on success, or else a negative error code. |
Message Transfer
We have done with our initialization. Now we can transfer the data using the below APIs.
This function will be blocked until the master completes the transfer.
where,
Return It returns zero on success, or else a negative error code. |
This API is used to transfer the data asynchronously. This call may be used
where,
Return It returns zero on success, or else a negative error code. |
This API is used to write the data and followed by a read. This is synchronous.
where,
|
Like these, there are many other APIs also available. Please refer to the spi.c file.
Remove the device
Using the below API, you can unregister the slave device.
where,
|
Example
static void __exit etx_spi_exit(void) { if( etx_spi_device ) { spi_unregister_device( etx_spi_device ); // Unregister the SPI slave pr_info("SPI driver Unregistered\n"); } }
SSD1306 OLED
In this example, we were using the SSD1306 OLED as an SPI slave device. You may find a very good inbuilt SPI driver for SSD1306. So, our motto is to implement an SPI protocol driver.
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 sufficient 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 does 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.
Linux SPI Device Driver Example
Connection Diagram – Linux SPI Device Driver
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 SPI1 of the Raspberry PI 4B.
Please refer to the below connection diagram.
- SDA (MOSI) – GPIO 20
- SCL (CLK) – GPIO 21
- RES – GPIO 24
- DC – GPIO 23
- Vcc – 3.3V
- GND – Ground
Concept of the Driver
The concept is to write some strings and display the image in the SSD1306 OLED display.
Source code – SPI Protocol Driver
I have divided this driver source code into 2 files.
- spi_ssd1306_driver.c (This file has the Linux SPI protocol driver-specific functions)
- ssd1306.c (This file has the SSD1306 OLED specific functions)
You can get the complete source code from GitHub.
spi_ssd1306_driver.c
/****************************************************************************//** * \file spi_ssd1306_driver.c * * \details Simple linux driver (SPI Slave Protocol Driver) * * \author EmbeTronicX * * \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+ * *******************************************************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/spi/spi.h> #include <linux/delay.h> #include "ssd1306.h" static struct spi_device *etx_spi_device; //Register information about your slave device struct spi_board_info etx_spi_device_info = { .modalias = "etx-spi-ssd1306-driver", .max_speed_hz = 4000000, // speed your device (slave) can handle .bus_num = SPI_BUS_NUM, // SPI 1 .chip_select = 0, // Use 0 Chip select (GPIO 18) .mode = SPI_MODE_0 // SPI mode 0 }; /**************************************************************************** * Name: etx_spi_write * * Details : This function writes the 1-byte data to the slave device using SPI. ****************************************************************************/ int etx_spi_write( uint8_t data ) { int ret = -1; uint8_t rx = 0x00; if( etx_spi_device ) { struct spi_transfer tr = { .tx_buf = &data, .rx_buf = &rx, .len = 1, }; spi_sync_transfer( etx_spi_device, &tr, 1 ); } //pr_info("Received = 0x%02X \n", rx); return( ret ); } /**************************************************************************** * Name: etx_spi_init * * Details : This function Register and Initilize the SPI. ****************************************************************************/ static int __init etx_spi_init(void) { int ret; struct spi_master *master; master = spi_busnum_to_master( etx_spi_device_info.bus_num ); if( master == NULL ) { pr_err("SPI Master not found.\n"); return -ENODEV; } // create a new slave device, given the master and device info etx_spi_device = spi_new_device( master, &etx_spi_device_info ); if( etx_spi_device == NULL ) { pr_err("FAILED to create slave.\n"); return -ENODEV; } // 8-bits in a word etx_spi_device->bits_per_word = 8; // setup the SPI slave device ret = spi_setup( etx_spi_device ); if( ret ) { pr_err("FAILED to setup slave.\n"); spi_unregister_device( etx_spi_device ); return -ENODEV; } //Initialize the OLED SSD1306 ETX_SSD1306_DisplayInit(); /* Print the String */ ETX_SSD1306_SetBrightness( 255 ); // Full brightness ETX_SSD1306_InvertDisplay( false ); // Invert the dispaly : OFF // Enable the Horizontal scroll for first 3 lines ETX_SSD1306_StartScrollHorizontal( true, 0, 2); ETX_SSD1306_SetCursor(0,0); // Set cursor at 0th line 0th col //Write String to OLED ETX_SSD1306_String("Welcome\nTo\nEmbeTronicX\n"); ETX_SSD1306_SetCursor(4,35); // Set cursor at 4th line 35th col //Write String to OLED ETX_SSD1306_String("SPI Linux\n"); ETX_SSD1306_SetCursor(5,23); // Set cursor at 5th line 23rd col ETX_SSD1306_String("Device Driver\n"); ETX_SSD1306_SetCursor(6,37); // Set cursor at 6th line 37th col ETX_SSD1306_String("Tutorial\n"); msleep(9000); // 9secs delay ETX_SSD1306_ClearDisplay(); // Clear Display ETX_SSD1306_DeactivateScroll(); // Deactivate the scroll /* Print the Image */ ETX_SSD1306_PrintLogo(); pr_info("SPI driver Registered\n"); return 0; } /**************************************************************************** * Name: etx_spi_exit * * Details : This function Unregister and DeInitilize the SPI. ****************************************************************************/ static void __exit etx_spi_exit(void) { if( etx_spi_device ) { // Clear the display ETX_SSD1306_ClearDisplay(); // Clear Display ETX_SSD1306_DisplayDeInit(); // Deinit the SSD1306 spi_unregister_device( etx_spi_device ); // Unregister the SPI slave pr_info("SPI driver Unregistered\n"); } } module_init(etx_spi_init); module_exit(etx_spi_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("EmbeTronicX <[email protected]>"); MODULE_DESCRIPTION("A simple device driver - SPI Slave Protocol Driver"); MODULE_VERSION("1.44");
ssd1306.c
/****************************************************************************//** * \file ssd1306.c * * \details SSD1306 related functions (SPI) * * \author EmbeTronicX * * \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+ * *******************************************************************************/ #include <linux/gpio.h> //GPIO #include <linux/delay.h> #include "ssd1306.h" /* ** 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) }; /**************************************************************************** * Name: ETX_SSD1306_ResetDcInit * * Details : This function Initializes and Configures the Reset and DC Pin ****************************************************************************/ static int ETX_SSD1306_ResetDcInit( void ) { int ret = 0; //do while(false) loop to break if any error do { /* Register the Reset GPIO */ //Checking the Reset GPIO is valid or not if( gpio_is_valid( SSD1306_RST_PIN ) == false ) { pr_err("Reset GPIO %d is not valid\n", SSD1306_RST_PIN); ret = -1; break; } //Requesting the Reset GPIO if( gpio_request( SSD1306_RST_PIN, "SSD1306_RST_PIN" ) < 0 ) { pr_err("ERROR: Reset GPIO %d request\n", SSD1306_RST_PIN); ret = -1; break; } //configure the Reset GPIO as output gpio_direction_output( SSD1306_RST_PIN, 1 ); /* Register the DC GPIO */ //Checking the DC GPIO is valid or not if( gpio_is_valid( SSD1306_DC_PIN ) == false ) { pr_err("DC GPIO %d is not valid\n", SSD1306_DC_PIN); gpio_free( SSD1306_RST_PIN ); // free the reset GPIO ret = -1; break; } //Requesting the DC GPIO if( gpio_request( SSD1306_DC_PIN, "SSD1306_DC_PIN" ) < 0 ) { pr_err("ERROR: DC GPIO %d request\n", SSD1306_DC_PIN); gpio_free( SSD1306_RST_PIN ); // free the reset GPIO ret = -1; break; } //configure the Reset GPIO as output gpio_direction_output( SSD1306_DC_PIN, 1 ); } while( false ); //pr_info("DC Reset GPIOs init Done!\n"); return( ret ); } /**************************************************************************** * Name: ETX_SSD1306_ResetDcDeInit * * Details : This function De-initializes the Reset and DC Pin ****************************************************************************/ static void ETX_SSD1306_ResetDcDeInit( void ) { gpio_free( SSD1306_RST_PIN ); // free the reset GPIO gpio_free( SSD1306_DC_PIN ); // free the DC GPIO } /**************************************************************************** * Name: ETX_SSD1306_setRst * * Details : This function writes the value to the Reset GPIO * * Argument: * value - value to be set ( 0 or 1 ) * ****************************************************************************/ static void ETX_SSD1306_setRst( uint8_t value ) { gpio_set_value( SSD1306_RST_PIN, value ); } /**************************************************************************** * Name: ETX_SSD1306_setDc * * Details : This function writes the value to the DC GPIO * * Argument: * value - value to be set ( 0 or 1 ) * ****************************************************************************/ static void ETX_SSD1306_setDc( uint8_t value ) { gpio_set_value( SSD1306_DC_PIN, value ); } /**************************************************************************** * Name: ETX_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_SSD1306_Write( bool is_cmd, uint8_t data ) { int ret = 0; uint8_t pin_value; if( is_cmd ) { //DC pin has to be low, if this is command. pin_value = 0u; } else { //DC pin has to be high, if this is data. pin_value = 1u; } ETX_SSD1306_setDc( pin_value ); //pr_info("Writing 0x%02X \n", data); //send the byte ret = etx_spi_write( data ); return( ret ); } /**************************************************************************** * Name: ETX_SSD1306_SetCursor * * Details : This function is specific to the SSD_1306 OLED. * * Argument: * lineNo -> Line Number * cursorPos -> Cursor Position * ****************************************************************************/ void ETX_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_SSD1306_Write(true, 0x21); // cmd for the column start and end address ETX_SSD1306_Write(true, cursorPos); // column start addr ETX_SSD1306_Write(true, SSD1306_MAX_SEG-1); // column end addr ETX_SSD1306_Write(true, 0x22); // cmd for the page start and end address ETX_SSD1306_Write(true, lineNo); // page start addr ETX_SSD1306_Write(true, SSD1306_MAX_LINE); // page end addr } } /**************************************************************************** * Name: ETX_SSD1306_GoToNextLine * * Details : This function is specific to the SSD_1306 OLED and move the cursor * to the next line. ****************************************************************************/ void ETX_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_SSD1306_SetCursor(SSD1306_LineNum,0); /* Finally move it to next line */ } /**************************************************************************** * Name: ETX_SSD1306_PrintChar * * Details : This function is specific to the SSD_1306 OLED and sends * the single char to the OLED. * * Arguments: * c -> character to be written * ****************************************************************************/ void ETX_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_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_SSD1306_Write(false, data_byte); // write data to the OLED SSD1306_CursorPos++; temp++; } while ( temp < SSD1306_FontSize); ETX_SSD1306_Write(false, 0x00); //Display the data SSD1306_CursorPos++; } } /**************************************************************************** * Name: ETX_SSD1306_String * * Details : This function is specific to the SSD_1306 OLED and sends * the string to the OLED. * * Arguments: * str -> string to be written * ****************************************************************************/ void ETX_SSD1306_String(char *str) { while( *str ) { ETX_SSD1306_PrintChar(*str++); } } /**************************************************************************** * Name: ETX_SSD1306_InvertDisplay * * Details : This function is specific to the SSD_1306 OLED and * inverts the display. * * Arguments: * need_to_invert -> true - invert display * false - normal display * ****************************************************************************/ void ETX_SSD1306_InvertDisplay(bool need_to_invert) { if(need_to_invert) { ETX_SSD1306_Write(true, 0xA7); // Invert the display } else { ETX_SSD1306_Write(true, 0xA6); // Normal display } } /**************************************************************************** * Name: ETX_SSD1306_SetBrightness * * Details : This function is specific to the SSD_1306 OLED and * sets the brightness of the display. * * Arguments: * brightnessValue -> brightness value ( 0 - 255 ) * ****************************************************************************/ void ETX_SSD1306_SetBrightness(uint8_t brightnessValue) { ETX_SSD1306_Write(true, 0x81); // Contrast command ETX_SSD1306_Write(true, brightnessValue); // Contrast value (default value = 0x7F) } /**************************************************************************** * Name: ETX_SSD1306_StartScrollHorizontal * * Details : This function is specific to the SSD_1306 OLED and * 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 * ****************************************************************************/ void ETX_SSD1306_StartScrollHorizontal( bool is_left_scroll, uint8_t start_line_no, uint8_t end_line_no ) { if(is_left_scroll) { // left horizontal scroll ETX_SSD1306_Write(true, 0x27); } else { // right horizontal scroll ETX_SSD1306_Write(true, 0x26); } ETX_SSD1306_Write(true, 0x00); // Dummy byte (dont change) ETX_SSD1306_Write(true, start_line_no); // Start page address ETX_SSD1306_Write(true, 0x00); // 5 frames interval ETX_SSD1306_Write(true, end_line_no); // End page address ETX_SSD1306_Write(true, 0x00); // Dummy byte (dont change) ETX_SSD1306_Write(true, 0xFF); // Dummy byte (dont change) ETX_SSD1306_Write(true, 0x2F); // activate scroll } /**************************************************************************** * Name: ETX_SSD1306_StartScrollVerticalHorizontal * * Details : This function is specific to the SSD_1306 OLED and * 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 * ****************************************************************************/ void ETX_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_SSD1306_Write(true, 0xA3); // Set Vertical Scroll Area ETX_SSD1306_Write(true, 0x00); // Check datasheet ETX_SSD1306_Write(true, vertical_area); // area for vertical scroll if(is_vertical_left_scroll) { // vertical and left horizontal scroll ETX_SSD1306_Write(true, 0x2A); } else { // vertical and right horizontal scroll ETX_SSD1306_Write(true, 0x29); } ETX_SSD1306_Write(true, 0x00); // Dummy byte (dont change) ETX_SSD1306_Write(true, start_line_no); // Start page address ETX_SSD1306_Write(true, 0x00); // 5 frames interval ETX_SSD1306_Write(true, end_line_no); // End page address ETX_SSD1306_Write(true, rows); // Vertical scrolling offset ETX_SSD1306_Write(true, 0x2F); // activate scroll } /**************************************************************************** * Name: ETX_SSD1306_DeactivateScroll * * Details : This function disables the scroll. ****************************************************************************/ void ETX_SSD1306_DeactivateScroll( void ) { ETX_SSD1306_Write(true, 0x2E); // Deactivate scroll } /**************************************************************************** * Name: ETX_SSD1306_fill * * Details : This function fills the data to the Display ****************************************************************************/ void ETX_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_SSD1306_Write(false, data); } } /**************************************************************************** * Name: ETX_SSD1306_ClearDisplay * * Details : This function clears the Display ****************************************************************************/ void ETX_SSD1306_ClearDisplay( void ) { //Set cursor ETX_SSD1306_SetCursor(0,0); ETX_SSD1306_fill( 0x00 ); } /**************************************************************************** * Name: ETX_SSD1306_PrintLogo * * Details : This function prints the EmbeTronicX Logo ****************************************************************************/ void ETX_SSD1306_PrintLogo( void ) { int i; //Set cursor ETX_SSD1306_SetCursor(0,0); for( i = 0; i < ( SSD1306_MAX_SEG * (SSD1306_MAX_LINE + 1) ); i++ ) { ETX_SSD1306_Write(false, etx_logo[i]); } } /**************************************************************************** * Name: ETX_SSD1306_DisplayInit * * Details : This function Initializes the Display ****************************************************************************/ int ETX_SSD1306_DisplayInit(void) { int ret = 0; //Initialize the Reset and DC GPIOs ret = ETX_SSD1306_ResetDcInit(); if( ret >= 0 ) { //Make the RESET Line to 0 ETX_SSD1306_setRst( 0u ); msleep(100); // delay //Make the DC Line to 1 ETX_SSD1306_setRst( 1u ); msleep(100); // delay /* ** Commands to initialize the SSD_1306 OLED Display */ ETX_SSD1306_Write(true, 0xAE); // Entire Display OFF ETX_SSD1306_Write(true, 0xD5); // Set Display Clock Divide Ratio and Oscillator Frequency ETX_SSD1306_Write(true, 0x80); // Default Setting for Display Clock Divide Ratio and Oscillator Frequency that is recommended ETX_SSD1306_Write(true, 0xA8); // Set Multiplex Ratio ETX_SSD1306_Write(true, 0x3F); // 64 COM lines ETX_SSD1306_Write(true, 0xD3); // Set display offset ETX_SSD1306_Write(true, 0x00); // 0 offset ETX_SSD1306_Write(true, 0x40); // Set first line as the start line of the display ETX_SSD1306_Write(true, 0x8D); // Charge pump ETX_SSD1306_Write(true, 0x14); // Enable charge dump during display on ETX_SSD1306_Write(true, 0x20); // Set memory addressing mode ETX_SSD1306_Write(true, 0x00); // Horizontal addressing mode ETX_SSD1306_Write(true, 0xA1); // Set segment remap with column address 127 mapped to segment 0 ETX_SSD1306_Write(true, 0xC8); // Set com output scan direction, scan from com63 to com 0 ETX_SSD1306_Write(true, 0xDA); // Set com pins hardware configuration ETX_SSD1306_Write(true, 0x12); // Alternative com pin configuration, disable com left/right remap ETX_SSD1306_Write(true, 0x81); // Set contrast control ETX_SSD1306_Write(true, 0x80); // Set Contrast to 128 ETX_SSD1306_Write(true, 0xD9); // Set pre-charge period ETX_SSD1306_Write(true, 0xF1); // Phase 1 period of 15 DCLK, Phase 2 period of 1 DCLK ETX_SSD1306_Write(true, 0xDB); // Set Vcomh deselect level ETX_SSD1306_Write(true, 0x20); // Vcomh deselect level ~ 0.77 Vcc ETX_SSD1306_Write(true, 0xA4); // Entire display ON, resume to RAM content display ETX_SSD1306_Write(true, 0xA6); // Set Display in Normal Mode, 1 = ON, 0 = OFF ETX_SSD1306_Write(true, 0x2E); // Deactivate scroll ETX_SSD1306_Write(true, 0xAF); // Display ON in normal mode // Clear the display ETX_SSD1306_ClearDisplay(); } return( ret ); } /**************************************************************************** * Name: ETX_SSD1306_DisplayDeInit * * Details : This function De-initializes the Display ****************************************************************************/ void ETX_SSD1306_DisplayDeInit(void) { ETX_SSD1306_ResetDcDeInit(); //Free the Reset and DC GPIO }
ssd1306.h
/****************************************************************************//** * \file ssd1306.h * * \details SSD1306 related macros (SPI) * * \author EmbeTronicX * * \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+ * *******************************************************************************/ #define SPI_BUS_NUM ( 1 ) // SPI 1 #define SSD1306_RST_PIN ( 24 ) // Reset pin is GPIO 24 #define SSD1306_DC_PIN ( 23 ) // Data/Command pin is GPIO 23 #define SSD1306_MAX_SEG ( 128 ) // Maximum segment #define SSD1306_MAX_LINE ( 7 ) // Maximum line #define SSD1306_DEF_FONT_SIZE ( 5 ) // Default font size extern int etx_spi_write( uint8_t data ); extern int ETX_SSD1306_DisplayInit(void); extern void ETX_SSD1306_DisplayDeInit(void); void ETX_SSD1306_SetCursor( uint8_t lineNo, uint8_t cursorPos ); void ETX_SSD1306_GoToNextLine( void ); void ETX_SSD1306_PrintChar(unsigned char c); void ETX_SSD1306_String(char *str); void ETX_SSD1306_InvertDisplay(bool need_to_invert); void ETX_SSD1306_SetBrightness(uint8_t brightnessValue); void ETX_SSD1306_StartScrollHorizontal( bool is_left_scroll, uint8_t start_line_no, uint8_t end_line_no ); void ETX_SSD1306_StartScrollVerticalHorizontal( bool is_vertical_left_scroll, uint8_t start_line_no, uint8_t end_line_no, uint8_t vertical_area, uint8_t rows ); void ETX_SSD1306_DeactivateScroll( void ); void ETX_SSD1306_fill( uint8_t data ); void ETX_SSD1306_ClearDisplay( void ); void ETX_SSD1306_PrintLogo( void );
Makefile
obj-m += spissd1306.o spissd1306-objs := spi_ssd1306_driver.o ssd1306.o KDIR = /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(shell pwd) modules clean: make -C $(KDIR) M=$(shell pwd) clean
Testing the Device Driver
- Build the driver by using Makefile (
sudo make
) - Load the driver using
sudo insmod spissd1306.ko
- See the Display. You should get some string that has been scrolling and after that image has to be filled.
- Unload the driver using
sudo rmmod spissd1306
- See the Display has been cleared.
Note: If you want to change the image, you can follow this process (Video). You can print your desired image also instead of the EmbeTronicX logo.
Output Video
Please check out the below video.
In our next tutorial, we will see how to write the SPI controller driver.
Please find the other Linux device driver tutorials here.
You can also read the below tutorials.
Reference
- https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md
- https://elinux.org/RPi_SPI
- https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
- https://github.com/STMicroelectronics/linux/blob/v5.10-stm32mp/Documentation/spi/spi-summary.rst
- https://linux-sunxi.org/SPIdev
- https://elixir.bootlin.com/linux/latest/source/drivers/spi/spi.c
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!