Interface SSD1306 OLED Display with STM32 Using I2C
The SSD1306 OLED is one of the most common displays in embedded projects. It is small, it has high contrast, and it draws very little current. The popular 0.96 inch module has a resolution of 128×64 pixels, and it can communicate over I2C or SPI.
This tutorial shows how to connect an SSD1306 OLED to an STM32 over I2C. The I2C peripheral is configured in STM32CubeMX, the code is written with the HAL library, and a small driver draws text, numbers, bitmaps, and scrolling animations on the screen. The example runs on an STM32F103C8T6 (BluePill), and the same steps work on the F4, H7, and most other STM32 series with only the HAL header changed.
The project uses the 0.96 inch SSD1306 OLED module and the STM32F103C8T6 BluePill board. Other displays are covered in the STM32 display and GUI tutorials, and links to the most relevant ones are listed near the end of this article.
Table of contents
- Interface SSD1306 OLED Display with STM32 Using I2C
- SSD1306 OLED Display Overview and Specifications
- SSD1306 I2C Interface Advantages
- Wiring the SSD1306 OLED to STM32
- STM32 CubeMX Setup for SSD1306 I2C
- SSD1306 Driver Setup for STM32 HAL
- Display Text on SSD1306 OLED with STM32
- Display Numbers and Floats on the OLED
- Display Bitmap Images on the OLED
- SSD1306 Scrolling and Animation with STM32
- SSD1306 OLED Not Working: Troubleshooting
SSD1306 OLED Display Overview and Specifications
The SSD1306 is a driver IC for monochrome OLED panels. It is used in the 0.96 inch 128×64 modules that appear in many embedded and IoT projects.
OLED pixels emit their own light, so the display needs no backlight. This gives deep blacks, high contrast, and a wide viewing angle even at low supply current. Most breakout modules support both I2C and SPI, and this tutorial uses the I2C version.
The main specifications are listed here.
- Display type: Monochrome OLED.
- Driver IC: SSD1306.
- Resolution: 128 × 64 pixels.
- Operating voltage: 3.3 V or 5 V, depending on the module.
- Communication: I2C in this tutorial, with SPI available on some modules.
- Default I2C address: 0x3C, or 0x3D on modules where the address pin is pulled high.
- Viewing angle: More than 160 degrees.
- Backlight: None, because the pixels emit light directly.
SSD1306 I2C Interface Advantages
I2C is a two-wire serial protocol that uses a data line (SDA) and a clock line (SCL). Most STM32 devices include several I2C peripherals, which makes the protocol a good fit for displays and sensors.
Using I2C with the SSD1306 has several benefits.
- Fewer pins: Only two signal lines are needed, so more pins stay free for other peripherals.
- HAL support: CubeMX configures the I2C peripheral, and the HAL library provides the transfer functions.
- Shared bus: Several I2C devices, such as sensors and displays, can share the same two lines as long as each one has a different address.
- Simple wiring: The module needs only four connections, which reduces mistakes on a breadboard.
For these reasons, I2C is the most common and beginner-friendly way to connect the SSD1306 to an STM32.
Wiring the SSD1306 OLED to STM32
The wiring is short because the module needs only power, ground, and the two I2C lines.
The connections are as follows.
- VCC: Connect this pin to the 3.3 V pin of the STM32 board to power the display.
- GND: Connect this pin to a GND pin of the STM32 board.
- SCL: Connect this pin to the I2C clock pin of the STM32, which is PB6 for I2C1 on the BluePill.
- SDA: Connect this pin to the I2C data pin of the STM32, which is PB7 for I2C1 on the BluePill.
SSD1306 modules work from either 3.3 V or 5 V, depending on the board. Many modules have an onboard regulator, which lets them run from 5 V while the display itself runs at 3.3 V. The safest option is to power the module from 3.3 V when the module allows it.
If the module has to be powered from 5 V, check whether its pull-up resistors are connected to VCC, because the I2C lines would then also rise to 5 V. In that case, the STM32 pins used for I2C must be 5 V tolerant, which is true for many pins on the F1 and F4 series. A level shifter is the alternative for pins that are not 5 V tolerant. Most modules already include pull-up resistors, but a bare module on a custom board needs external pull-ups, typically 4.7 kΩ to 3.3 V.
STM32 CubeMX Setup for SSD1306 I2C
After the wiring is complete, the I2C peripheral is configured in STM32CubeMX.
Clock Configuration
The board uses its 8 MHz external crystal (HSE), and the PLL raises the system clock to the 72 MHz maximum of the STM32F103.
I2C Fast Mode at 400 kHz
The project uses I2C1.
- Set the I2C speed mode to Fast Mode, which sets the clock to 400 kHz. A full screen update sends 1024 bytes, so the higher speed gives noticeably faster refreshes and smoother animations.
- CubeMX assigns the pins automatically. PB6 becomes SCL, and PB7 becomes SDA.
SSD1306 Driver Setup for STM32 HAL
The project uses a small driver that consists of four files: ssd1306.c, ssd1306.h, fonts.c, and fonts.h. Copy the two .c files into the Src folder and the two .h files into the Inc folder of your CubeIDE project. In Keil, the files must also be added to the project group, because copying them into the folder is not enough.
Open ssd1306.c and make sure the I2C handle matches the one that CubeMX generated.
extern I2C_HandleTypeDef hi2c1;
#define SSD1306_I2C &hi2c1If CubeMX used I2C2 or I2C3, change hi2c1 to hi2c2 or hi2c3. A linker error that mentions an undefined reference to hi2c1 means that I2C1 is not enabled in CubeMX or that the handle name does not match.
Next, check the resolution values in ssd1306.h.
#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 64The driver uses the I2C address 0x3C by default. If your module uses 0x3D, change the address define in the driver. HAL functions expect the 7-bit address shifted left by one bit, so depending on the driver version, the same address may appear in the code as 0x78.
The header ssd1306.h also includes the HAL header for the F1 series. On other families, change it to the matching file, for example stm32f4xx_hal.h for the F4 series.
Display Text on SSD1306 OLED with STM32
The following code prints two lines of text.
#include "ssd1306.h"
#include "fonts.h"
int main(void)
{
/* HAL and peripheral initialization generated by CubeMX */
SSD1306_Init(); // initialize the display
SSD1306_GotoXY(10, 10); // move the cursor to x = 10, y = 10
SSD1306_Puts("HELLO", &Font_11x18, 1); // write the first line
SSD1306_GotoXY(10, 30);
SSD1306_Puts("WORLD !!", &Font_11x18, 1); // write the second line
SSD1306_UpdateScreen(); // send the buffer to the display
while (1)
{
}
}The functions work as follows.
SSD1306_Init()sends the complete initialization sequence to the display.SSD1306_GotoXY(x, y)sets the cursor position in pixels.SSD1306_Puts()writes the text into an internal frame buffer.SSD1306_UpdateScreen()sends the frame buffer to the display. Nothing appears on the screen until this function is called.
Display Numbers and Floats on the OLED
The display can only draw characters, so a number has to be converted to text first. The sprintf function does this conversion.
#include "ssd1306.h"
#include "fonts.h"
#include "stdio.h" // for sprintf
int main(void)
{
/* HAL and peripheral initialization generated by CubeMX */
int num = 123456;
float flt = 123.45f;
char bufnum[16];
char bufflt[16];
SSD1306_Init();
sprintf(bufnum, "%d", num);
sprintf(bufflt, "%.2f", flt);
SSD1306_GotoXY(10, 10);
SSD1306_Puts(bufnum, &Font_11x18, 1);
SSD1306_GotoXY(10, 30);
SSD1306_Puts(bufflt, &Font_11x18, 1);
SSD1306_UpdateScreen();
while (1)
{
}
}The format %d converts an integer, and %.2f converts a float with two digits after the decimal point. Each buffer must be large enough to hold all characters, including the sign, the decimal point, and the terminating null character. Printing a float with sprintf also requires float support in the linker settings of the project, which some toolchains disable by default.
The same method works for live sensor data. Convert the reading with sprintf, clear the old text with SSD1306_Clear(), and call SSD1306_UpdateScreen() after each change.
Display Bitmap Images on the OLED
The function SSD1306_DrawBitmap() draws any image that has been converted into a C byte array.
The conversion takes two steps.
- Open the LVGL image converter and load the image. Choose Binary RGB332 as the output format, Indexed 1-bit as the color format, and C array as the output.
- Download the generated
.cfile, and copy the array into a header file namedbitmap.hin your project.
The bitmap is then drawn and sent to the display.
#include "ssd1306.h"
#include "bitmap.h"
SSD1306_Init();
SSD1306_DrawBitmap(0, 0, logo, 128, 64, 1);
SSD1306_UpdateScreen();The arguments of SSD1306_DrawBitmap(x, y, bitmap, width, height, color) are the top-left position, the array, the size of the image, and the pixel color. The origin (0, 0) is the top-left pixel of the screen. The width and height must match the converted image exactly, or the picture will look distorted.
SSD1306 Scrolling and Animation with STM32
The SSD1306 can scroll the screen content in hardware. After the scroll command is sent, the display handles the movement by itself and the CPU is not involved.
#include "ssd1306.h"
#include "fonts.h"
#include "stdio.h"
#include "bitmap.h"
int main(void)
{
/* HAL and peripheral initialization generated by CubeMX */
SSD1306_Init();
SSD1306_DrawBitmap(0, 0, logo, 128, 64, 1);
SSD1306_UpdateScreen();
SSD1306_ScrollRight(0x00, 0x07); // scroll pages 0 to 7 to the right
HAL_Delay(5000);
SSD1306_Stopscroll();
SSD1306_ScrollLeft(0x00, 0x07); // scroll pages 0 to 7 to the left
HAL_Delay(5000);
SSD1306_Stopscroll();
while (1)
{
}
}The values 0x00 and 0x07 select pages 0 to 7, which cover the full 64 pixel height of the screen. The scroll must be stopped with SSD1306_Stopscroll() before new content is written, because a write during an active scroll conflicts with the scroll command.
Frame-by-frame animation works differently. The program draws a series of bitmaps one after another in a loop and calls SSD1306_UpdateScreen() after each frame. This method is shown in the video.
SSD1306 OLED Not Working: Troubleshooting
Most problems come from a small number of causes. The following checks solve the majority of reports.
- The wiring must match the pins chosen in CubeMX. SDA and SCL are easy to swap, and a swapped pair gives a blank screen with no error message.
- The I2C address must match the module. Some modules use 0x3D instead of 0x3C, and the HAL functions expect the address in its shifted form.
- The handle name in
ssd1306.cmust match the I2C peripheral that is enabled in CubeMX. SSD1306_UpdateScreen()must be called after every change to the frame buffer. The display stays blank without it.- The module needs pull-up resistors on SDA and SCL. Most modules include them, but a bare display on a custom board does not.
- The project must include the HAL header of the correct STM32 family. A build error such as
stm32f1xx_hal.h: No such file or directorymeans the header inssd1306.handfonts.hbelongs to a different family. - A column of stray pixels at the right edge can mean that the module uses an SH1106 controller instead of an SSD1306. The SH1106 has 132 columns and needs a slightly different driver, which is covered in the SH1106 OLED tutorial.
If the display still stays blank, a short scan of the I2C bus shows whether the STM32 can reach the module at all.
for (uint8_t addr = 1; addr < 128; addr++)
{
if (HAL_I2C_IsDeviceReady(&hi2c1, addr << 1, 1, 10) == HAL_OK)
{
// a device answered at this 7-bit address, for example 0x3C
}
}A breakpoint inside the if block shows the address of every device that responds.
STM32 SSD1306 OLED Summary and Next Steps
This tutorial showed how to connect a 0.96 inch SSD1306 OLED to an STM32 over I2C. It covered the wiring, the CubeMX configuration with I2C Fast Mode, and the driver setup, and it then displayed text, numbers, bitmaps, and hardware scrolling on the screen.
The pattern is the same on every STM32 family. The program initializes the display, writes into the frame buffer, and sends the buffer to the screen with SSD1306_UpdateScreen(). Only the HAL header and the I2C handle change between boards.
For a larger screen or a richer graphics library, the SH1106 tutorial and the U8G2 port are the natural next steps. For text-only output, the I2C LCD1602 tutorial is a simpler option.
Download Project Here
Download the CubeMX project files and HAL source code — all tested on real hardware.
If you find this useful, you can support us with a Donation as well.
Browse More STM32 Display Tutorials
Arun is an embedded systems engineer with 10+ years of experience in STM32, ESP32, and AVR microcontrollers. He created ControllersTech to share practical tutorials on embedded software, HAL drivers, RTOS, and hardware design — grounded in real industrial automation experience.
Recommended Tools
Essential dev tools
Categories
Browse by platform











At line 70 of ssd1306.c, this statement “SSD1306_WRITECOMMAND(0x10);” seems not conform to datasheet. It uses A[1:0] so valid settings are 00b/01b/10b but not 0x10. Could you check that?
This configuration uses DMA? If not, can I do some changes for use it?
Hello there, how do I add some new/custom fonts instead of using the inner fonts in library files?
Probably like file fonts.h you can add you own custom file .h with your fonts or download a font on internet, there are some programs that create it (pixel font for example)
I cannot find where to download the following?? Can you please direct me where to find these files that you mentioned?
”Download the code below, unzip it, Copy the fonts.h, test.h and ssd1306.h in the Inc folder of your project, and fonts.c, test.c and ssd1306.c in the Src folder and open the project now.”
Thank you for very good tutorial. Example runs with minor modification (PB6 & PB7 –> PB8 , PB9) also on NUCLEO-F411RE board and stmcube ide 1.11.0
Would it work in any I2C OLED 128×64 display? and 256×64?
Hi. I am trying to add more fonts. But I did’nt find any program to output 32bit format font archive. Only 8 bit format! Could you tell me wich program did you use? Thanks a lot.
Hi, have you found how to create front ?
Probably didn’t exists, fonts are made 16x16pix, and escalates if you want more resolution, or maybe 256×256, but 32 bits?? That will be 65536 x 65536 resolution, not even 8K
Great tutorial and well explained. One small change I made to your SSD1306_DrawBitmap function. You can add “else SSD1306_DrawPixel(x+i, y, 0);” right after the if statement to draw a black pixel instead of white. This will make for a very smooth horse running and eliminates the need to clear the screen following each call to DrawBitmap. Thanks again for the awesome video.
Hello Sir,
Thank you for the tutorial it’s awesome and very helpful, I have just one question, can I use this SSD1306 library with FreeRTOS, I think it will be very interesting, we could use an SD card with bitmaps in it and print them on the screen with the OLED display.
Thanks, Sir!
if u have possible provide with spi interfacing
I am having a problem in ssd1306.c with undefined references to ‘hi2c1’. What fixes can I do?
you need to enable the I2C in cubeMX. That’s how the OLED will connect to the MCU
i have enabled i2c in cubeMX,why i have the same problem? maybe because there exist 2 i2c?
the code is set to use the i2c1. Either enable that or change the code to use the I2C2
That’s a really helpful tutorial. I want to know if I can connect MPU6050 and the LCD to the same pins at the same time (using I2C only). Will the config of any affect the other.
Of course you can. That’s the entire purpose of I2C. With 7 bit addressing, you can connect upto 128 devices with just 2 pins.
I have a video on it too… Connecting on the same pins https://youtu.be/xxphp9wDnHA
Thanks a lot!! I really Appreciate it!
Hi, I really liked the tutorial, I wandering if this code can be used for a NUCLEO-F446RE (STM32F446RE)? I have been trying to make it work but I haven’t been able to. Thanks.
Yes , check this video https://www.youtube.com/watch?v=Z6VyFYzQWSs. Just need to change #include “stm32f1xx_hal.h” to f4 inside ssd1306.h.
what function has this code line?
It’s defined in main.c file
I don´t have money for you but, I would like to tell you “I LOVE YOU S MUC FOR THIS.”
Is it possible to interface the lcd 128x32u using your library?
why it is can not downloaded
try right click on the download and open in new tab
Thank you for your good writing and explanation.
Hello,
Thank you .
I downloaded the zip file and tried to build the project.
But I have below error :
./Inc/fonts.h:41:10: fatal error: stm32f1xx_hal.h: No such file or directory
What do you think? Can you share me this library please?
create your own project, and then use the library files from this one.
It works if you change to the same header file as the MCU you use.
Thanks for this tutorial! Excellent job! I ran into a problem compiling the code first time in a C++ project in STM32CubeIDE: “error: ‘C’ does not name a type; did you mean ‘_C’?” Needed to add quotation marks on C to get it to compile correctly: extern “C” {
Hi,
Where is the link for all these libraries which you are mentioned in the above tutorials?
click download and look in the src and inc folders
If you click on the download but the download does not start, you right click on the download button then copy the address of the line then paste in the search bar.
The link is obviously broken.. link to a github repo would be much better
Can Anyone send me the library for SSD1309 for STM32?
good work
good work
hi there, really good thanks.. may i ask, could this ssd1306 driver be repurposed for a ssd1327 (128×128) i2c oled ? im using the same ssd1306 code with a different init sequence, however the oled simply doesnt display anything.. any help would be appreciated.. kind thanks
Hi! Thanks, it worked perfectly. Is there any LCDAssistant avaliable for Linux distributions?
Good afternoon. Tell me how you can edit your code to use a smaller buffer?
Brother I started the Payment Process but it says you have exceeded the limit for receiving any more funds.
If this resolves just reply.
Thanks for this contribution
I don’t know what the issue is. It’s fine though. Keep visiting and that’s enough 🙂
Hello,
I need ssd1306 code for stm8s controller. Actually i have done this , but the problem i am facing is font or size. I am not able to change the size of the letter in my code.
The library i used is from this site.
Please help me out how can i change the size.
http://embedded-lab.com/blog/stm8-microcontrollers-final-chapters/3/
Please help me out how can i change the size.
Thanks you.
[email protected]
just google some font generator or look in the arduino librarries, you can find different fonts there
hello sir i want ask you something, i make this code to stm32 bluephill and then i get error selected processor does not support `dsb 0xF’ in Thumb mode
Hallo awesome library
only i have this following error message and i dont know how to get around it can you please help me with it?
.\Objects\OLED_i2c.axf: Error: L6218E: Undefined symbol Font_11x18 (referred from main.o).
.\Objects\OLED_i2c.axf: Error: L6218E: Undefined symbol SSD1306_GotoXY (referred from main.o).
.\Objects\OLED_i2c.axf: Error: L6218E: Undefined symbol SSD1306_Init (referred from main.o).
.\Objects\OLED_i2c.axf: Error: L6218E: Undefined symbol SSD1306_Puts (referred from main.o).
.\Objects\OLED_i2c.axf: Error: L6218E: Undefined symbol SSD1306_UpdateScreen (referred from main.o).
Not enough information to list image symbols.
Not enough information to list load addresses in the image map.
Finished: 2 information, 0 warning and 5 error messages.
“.\Objects\OLED_i2c.axf” – 5 Error(s), 0 Warning(s).
Edit:
Nevermind i fixed it already, i was using Keilv5 IDE, where you cannot just copy paste into the source and inc folder, but you have to add the item to the group by right clicking,
and then i modify it for my stm32f030xx and it worked fine, thank you again
thank you for your code just send you a coffee , you saved me some times
i want sent u donate but we are Boycott bye united state.
very very very thanks 🙂
the best likable aspiration for you.
Thanks Again
from IRAN
no worries.. keep supporting this website that’s enough
hi! excellent project, but I have a problem. when I download the .zip, i don´t see any libraries fonts.h, test.h, and ssd1306.h, and neither do libraries fonts.c, test.c and ssd1306.h; but I do see libraries font.d and font.o, likewise with test and ssd1306. Are these libraries similar to .c and .h?
HI
IT IS VERY HARD TO SWITCH ON STM32 FROM AVR 8 BIT BUT YOU DID A GREAT JOB FOR ALL PEOPLE LIKE ME . THANKS
Thank you for the example, got it working on Nucleo-L476RG board
Was anyone able to get this to work on a STM32F466RE?
If you mean 446RE, than yes I got it working.
I adapted the code for Nucleo-32, it works perfectly but sometimes it stops working for no reason.
Once the display just scrolled down alone and almost half-top was the bottom part of the display. Then I disassembled the display to try to reset it or something but when I assembled it again it stopped working. This happened to me before but without de scrolling.
The code works perfectly, I was just adjusting some parameters. I have searched some information about it and some people says it’s because of using the nucleo’s power supply but it doesn’t work when I put an external input voltage to the display. Other people says that it is because its power supply is 3V3 not 5V as in this guide but I have also tried it and guess what, sometimes it works and but when you disassemble the display it doesn’t.
If anyone knows why this happens please tell me.
MUCHAS GRACIAS. It works perfectly, you saved my day, I didn’t know how to use it in keil. Thank you so much.
STM32F103 (BLUE-PILL)
SCK (oled) to PB6
SDA(oled) to PB7
Works very well on my STM32F407 board, Thank you!
A detail…
The ssd1306_I2C_WriteMulti() function has a little bug in the counters, that is the reason why @Oleksandr Gusiev had to change SSD1306_WIDTH 128 to 129
Here is the fix
void ssd1306_I2C_WriteMulti(uint8_t address, uint8_t reg, uint8_t* data, uint16_t count) {
uint8_t dt[256];
dt[0] = reg;
uint8_t i;
for(i = 0; i < count; i++)
dt[i+1] = data[i];
HAL_I2C_Master_Transmit(&hi2c1, address, dt, count+1, 10);
}
I hope it can help to another freaky electronic lover like us 😉
Thanks.. will check and update the code
/* USER CODE BEGIN Includes */
#include “ssd1306.h”
#include “i2c-lcd.h”
/* USER CODE END Includes */
Why i2c-lcd.h in code exists,but in video are not??
/* USER CODE BEGIN Includes */
#include “ssd1306.h”
#include “i2c-lcd.h”
/* USER CODE END Includes */
Why i2c-lcd.h in code exists,but in video are not?
How I can Put float number from adc in the display?
google it. It depends on what IDE you are using.
/* Private variables ———————————————————*/
//variable para LCD
char buf[25];
int dato=1;
…….
…….
while (1)
{
dato +=1;
if(dato>1000)
dato = 5;
dato = ((float)dato/24); // for example
sprintf(buf,”Dat:%.1f”,dato); //convert dato to string
SSD1306_GotoXY (0,0);
SSD1306_Puts (buf, &Font_11x18, 1);
SSD1306_UpdateScreen(); //Update the LCD
//End of LCD display
HAL_Delay(100);
SSD1306_Clear();
}
Thanks Working good,
please tell me command to clear lcd.
to clear lcd, you need to send 0x01
But I prefer sending blank (‘ ‘) as data
thanks….
it was fantastic
Hello. I followed your guide. Thanks a lot, it helped me clarify some points, but i have some troubles. First of all https://goo.gl/XD3yQC there are some dots at right side of screen (tested on two screens, same problem). And the second, but the most important, during run in debug, screen looks https://goo.gl/UMFquG at any
debug step, and it is always so when boot pins in such a state. Please, relpy, if possible. Thanks.
I fixed this issues. First problem with point to the right – bug into the lib. You should change #define SSD1306_WIDTH 128 in ssd1306.h to 129, cause numeration is starts from zero, not 1. The second problem was it that, I changed boot pins to 1. Both should be at 0.
In mi OLED work #define SSD1306_WIDTH 130
many thanks for this, really good c code
Hi, thanks for the explanation, I’m trying to port this code to a STM32L4 family.
I have managed to change all STM32f1xx lines. As well as changes the I2C number that I’m using hi2c3.
What’s is strange is that I’m getting only one error now which is on a completely different part of the code. It’s telling me the variable “count” needs a constant value. So maybe it’s not receiving anything.
void ssd1306_I2C_WriteMulti(uint8_t address, uint8_t reg, uint8_t* data, uint16_t count) {
uint8_t dt[count + 1];
dt[0] = reg;
uint8_t i;
for(i = 1; i <= count; i++)
dt[i] = data[i-1];
HAL_I2C_Master_Transmit(&hi2c3, address, dt, count, 10);
}
thanks,very professional and useful.