Working with STM32 and Displays: SSD1306 SPI OLED display

In the previous guide on OLED (here), we took a look at how to connect SSD1306 I2C OLED with STM32F4. In this guide, we shall use the SPI version of the display to display text and picture.

In this guide, we shall cover the following:

  • SSD1306 OLED display.
  • Interface with STM32F4.
  • SPI driver of SSD1306.
  • Write Data, Command and Multidata
  • Initializing sequence.
  • Basic function to display text.
  • Demo.

1. SSD1306 OLED Display:

Do you want to give a perfect look to your microcontroller project which traditional liquid crystal displays (LCDs) do not promise? In this case, OLED displays will be the best choice for you. Because they offer a good view angle and pixel density which makes it the perfect choice for graphics display projects at low cost. 

SSD1306 OLED Types

There are different types of OLED displays available in the market. But the common thing in most displays is the SSD1306 CMOS OLED driver controller. The main component of OLED is an SSD1306 controller which is used to communicate with microcontrollers,  such as TM4C123 Tiva Launchpad or STM32F4, using either SPI or I2C communication. But usually, I2C communication is preferred because it requires only two wires to communicate with STM32F411. 

However, in this guide, we shall cover the SPI version.

I2C OLED Display 

These displays come in different colors, sizes and shapes. In this tutorial, we will use an SPI based OLED display having size of 128×64 as shown in the figure below. But the good thing is programming doesn’t change much with the change of OLED size and color. 

The following picture shows the pin wiring of the OLED display. As you can see from its pinout diagram, it consists of six pins such as Vcc, GND, CLK, MOSI,CS and DC. Vcc and GND pins are used to power OLED displays and the operating voltage range is between 3.3-5V. That means we can easily power it from the same source and connect directly with microcontrollers.

The pins description as following:

OLED PinsMeaning
Vcc+Ve power source (3.3 to 5V)
GND-Ve power source
CLKSerial Clock of the SPI
MOSIMaster Out Slave of SPI
CSChip Select
D/CData/Command select

2. Interface With STM32F4:

The interface as following:

SSD1306 SPISTM32F411-Nucleo64
Vcc5V
GNDGND
CLK D13 (PA5)
MOSID11 (PA7)
CSD8 (PA9)
DCD4 (PA10)

3. SPI driver for SSD1306:

Since the display is using SPI, we shall use SPI for that purpose. For more information about SPI, please refer to this guide here.

The GPIO initializing function:

void OLED_SPI_Pins_Init()
{
	RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN; //enable clock for GPIOA

	//set PA5, PA6 and PA7 to alternate function mode
	GPIOA->MODER|=GPIO_MODER_MODE5_1|GPIO_MODER_MODE6_1|GPIO_MODER_MODE7_1;
	GPIOA->MODER &=~(GPIO_MODER_MODE5_0|GPIO_MODER_MODE6_0|GPIO_MODER_MODE7_0);

	//Set PA9 and PA10 as Output
	GPIOA->MODER|=GPIO_MODER_MODE9_0|GPIO_MODER_MODE10_0;
	GPIOA->MODER&=~(GPIO_MODER_MODE9_1|GPIO_MODER_MODE10_1);

	/*select which AF for PA5, PA6 and PA7*/
	GPIOA->AFR[0]|=(0x05<<20)|(0x05<<24)|(0x05<<28);
}

SPI Initializing:

void OLED_SPI_Configure()
{
	/*Enable clock access to SPI1 module*/
		RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;

		/*Set clock to fPCLK/2*/
		SPI1->CR1 &=~(1U<<3);
		SPI1->CR1 &=~(1U<<4);
		SPI1->CR1 &=~(1U<<5);

		/*Enable full duplex*/
		SPI1->CR1 &=~(1U<<10);

		/*Set MSB first*/
		SPI1->CR1 &= ~(1U<<7);

		/*Set mode to MASTER*/
		SPI1->CR1 |= (1U<<2);

		/*Set 8 bit data mode*/
		SPI1->CR1 &= ~(1U<<11);

		/*Select software slave management by
		 * setting SSM=1 and SSI=1*/
		SPI1->CR1 |= (1<<8);
		SPI1->CR1 |= (1<<9);

		/*Enable SPI module*/
		SPI1->CR1 |= (1<<6);
}

SPI Write function for OLED:

void OLED_SPI_Write(char *data,uint32_t size)
{
	uint32_t i=0;

	while(i<size)
	{
		/*Wait until TXE is set*/
		while(!(SPI1->SR & (SPI_SR_TXE))){}

		/*Write the data to the data register*/
		SPI1->DR =(uint8_t) data[i];
		i++;
	}
	/*Wait until TXE is set*/
	while(!(SPI1->SR & (SPI_SR_TXE))){}

	/*Wait for BUSY flag to reset*/
	while((SPI1->SR & (SPI_SR_BSY))){}

	/*Clear OVR flag*/
	(void)SPI1->DR;
	(void)SPI1->SR;
}

Chip select and deselect:

void OLED_Select(void)
{
	GPIOA->BSRR =GPIO_BSRR_BR9;

}

/*Pull high to disable*/
void OLED_Deselect(void)
{
	GPIOA->BSRR =GPIO_BSRR_BS9;
}

Data/Command toggle:

void OLED_DataMode()
{
	GPIOA->BSRR=GPIO_BSRR_BS10;
}

void OLED_CommMode()
{
	GPIOA->BSRR=GPIO_BSRR_BR10;
}

Thats all for the SPI section.

4. Write Data, Command and Multidata:

In order to write data, we need to set D/C pin to high, set CS pin to low, write data and then set CS high as following:

void SSD1306_WRITEDATA(char command)
{
	OLED_DataMode();
	OLED_Select();
	OLED_SPI_Write(&command,1);
	OLED_Deselect();
}

To write command, same procedure as writing data. However, we need to set D/C to low as following:

void SSD1306_WRITECOMMAND(char command)
{
	OLED_CommMode();
	OLED_Select();
	OLED_SPI_Write(&command,1);
	OLED_Deselect();
}

To write multiple data when updating the display:

void SSD1306_Write_Multi_Data(char * data, uint16_t length)
{
	OLED_DataMode();
	OLED_Select();
	OLED_SPI_Write((char*)data,length);
	OLED_Deselect();

}

5. Initialization Sequence:

The initialization sequence as following:

uint8_t SSD1306_Init(void)
{
	 OLED_SPI_Pins_Init();

	 OLED_SPI_Configure();
	/* A little delay */
	uint32_t p = 2500;
	while(p>0)
		p--;
	
	/* Init LCD */
	SSD1306_WRITECOMMAND(0xAE); //display off
	SSD1306_WRITECOMMAND(0x20); //Set Memory Addressing Mode   
	SSD1306_WRITECOMMAND(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	SSD1306_WRITECOMMAND(0xB0); //Set Page Start Address for Page Addressing Mode,0-7
	SSD1306_WRITECOMMAND(0xC8); //Set COM Output Scan Direction
	SSD1306_WRITECOMMAND(0x00); //---set low column address
	SSD1306_WRITECOMMAND(0x10); //---set high column address
	SSD1306_WRITECOMMAND(0x40); //--set start line address
	SSD1306_WRITECOMMAND(0x81); //--set contrast control register
	SSD1306_WRITECOMMAND(0xFF);
	SSD1306_WRITECOMMAND(0xA1); //--set segment re-map 0 to 127
	SSD1306_WRITECOMMAND(0xA6); //--set normal display
	SSD1306_WRITECOMMAND(0xA8); //--set multiplex ratio(1 to 64)
	SSD1306_WRITECOMMAND(0x3F); //
	SSD1306_WRITECOMMAND(0xA4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	SSD1306_WRITECOMMAND(0xD3); //-set display offset
	SSD1306_WRITECOMMAND(0x00); //-not offset
	SSD1306_WRITECOMMAND(0xD5); //--set display clock divide ratio/oscillator frequency
	SSD1306_WRITECOMMAND(0xF0); //--set divide ratio
	SSD1306_WRITECOMMAND(0xD9); //--set pre-charge period
	SSD1306_WRITECOMMAND(0x22); //
	SSD1306_WRITECOMMAND(0xDA); //--set com pins hardware configuration
	SSD1306_WRITECOMMAND(0x12);
	SSD1306_WRITECOMMAND(0xDB); //--set vcomh
	SSD1306_WRITECOMMAND(0x20); //0x20,0.77xVcc
	SSD1306_WRITECOMMAND(0x8D); //--set DC-DC enable
	SSD1306_WRITECOMMAND(0x14); //
	SSD1306_WRITECOMMAND(0xAF); //--turn on SSD1306 panel
	

	SSD1306_WRITECOMMAND(SSD1306_DEACTIVATE_SCROLL);

	/* Clear screen */
	SSD1306_Fill(SSD1306_COLOR_BLACK);
	
	/* Update screen */
	SSD1306_UpdateScreen();
	
	/* Set default values */
	SSD1306.CurrentX = 0;
	SSD1306.CurrentY = 0;
	
	/* Initialized OK */
	SSD1306.Initialized = 1;
	
	/* Return OK */
	return 1;
}

6. Basic function to display text:

In order to display text, we need the following:

  • Go to xy point:
oid SSD1306_GotoXY(uint16_t x, uint16_t y) {
	/* Set write pointers */
	SSD1306.CurrentX = x;
	SSD1306.CurrentY = y;
}
  • Put character into the buffer:
char SSD1306_Puts(char* str, FontDef_t* Font, SSD1306_COLOR_t color) {
	/* Write characters */
	while (*str) {
		/* Write character by character */
		if (SSD1306_Putc(*str, Font, color) != *str) {
			/* Return error */
			return *str;
		}
		
		/* Increase string pointer */
		str++;
	}
	
	/* Everything OK, zero should be returned */
	return *str;
}
  • Update the display:
void SSD1306_UpdateScreen(void) {
	uint8_t m;
	
	for (m = 0; m < 8; m++) {
		SSD1306_WRITECOMMAND(0xB0 + m);
		SSD1306_WRITECOMMAND(0x00);
		SSD1306_WRITECOMMAND(0x10);
		
		/* Write multi data */

		SSD1306_Write_Multi_Data(&SSD1306_Buffer[SSD1306_WIDTH * m], SSD1306_WIDTH);
	}
}

7. Code:

You may download the full source code from here:

8. Demo:

Happy coding 🙂

9 Comments

  • MH Posted May 13, 2024 4:20 pm

    I did your project Working with STM32 and Displays: SSD1306 SPI OLED display but with Nucleo-F446RE and STM32F401 it does not work for me I spent a lot of time; I imported the project to stm32cubeide program. 2. Interface With STM32F4 schematic and the below pin details does not match.
    The YouTube video the wiring looks different I do not know what I’ m doing wrong.

    • Husamuldeen Posted May 13, 2024 5:56 pm

      Which OLED are you using?

      • MH Posted May 13, 2024 8:30 pm

        I am using OLED SPI 7 pin 1 = GND ,,2 = VCC, 3 = SCK, 4 = MOSI, 5 = RES, 6 = DC, 7 = CS purchased from AliExpress, I’ m able to build the project but when I downloaded to nucleo-F446RE the OLED display is dead black, I tested with Arduino uno it works fine. I connected the res pin to VCC or GND still display is not ON, I have used a debug the code looks fine.

      • MH Posted May 13, 2024 10:23 pm

        Original 0.96″ OLED SPI module 128X64 7 pin work fine with Arduino Uno

  • PJH Posted May 17, 2024 3:48 am

    /* Set default values */
    SSD1306.CurrentX = 0;
    SSD1306.CurrentY = 0;

    /* Initialized OK */
    SSD1306.Initialized = 1;

    At this part, Error display ( ‘SSD1306’ undeclared (first use in this function); did you mean ‘SSD1306_H’? ) .

    How can I fix it?

    • Husamuldeen Posted May 18, 2024 12:32 pm

      Hi,
      download the source code.

Add Comment

Your email address will not be published. Required fields are marked *