u8g2 library usage with STM32 MCU
Several years ago I wrote couple of articles about beautiful library u8glib in context of STM32 microcontrollers. Time moves on and author of this library released newer one, u8g2. u8glib is officially deprecated and not developed anymore. We cannot afford to lose code support for our libraries, time to move to newer library!
Differences between these libraries are listed there, also there are a lot of helpful information about u8g2 library.
It is not so hard to modify your old u8glib code for newer library. I'll show you how to use this library with Nucleo-F401 board and SSD1306 display, bought on Aliexpress. Display connection is SPI, pins are GND, VCC, SCL(the same as SPI clock), SDA(the same as MOSI), RES and DC.
Wiring is:
PA5 -> SCL
PA7 -> SDA
PA9 -> OLED_RES
PC7 -> OLED_DC
I use CubeMX code generation tool to initialize all the peripheral. OLED_RES and OLED_DC is just GPIO output. SPI1 initialized with default parameters.
u8g2 library has its own HAL wrapper, so we need to provide two necessary callbacks. One is GPIO and delay callback, the second is communication callback. More details you can find in porting to new MCU platform guide
This is my GPIO and delay callback:
uint8_t u8x8_stm32_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr) { switch (msg) { case U8X8_MSG_GPIO_AND_DELAY_INIT: HAL_Delay(1); break; case U8X8_MSG_DELAY_MILLI: HAL_Delay(arg_int); break; case U8X8_MSG_GPIO_DC: HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int); break; case U8X8_MSG_GPIO_RESET: HAL_GPIO_WritePin(OLED_RES_GPIO_Port, OLED_RES_Pin, arg_int); break; } return 1; }
My version of communication callback is:
uint8_t u8x8_byte_4wire_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch (msg) { case U8X8_MSG_BYTE_SEND: HAL_SPI_Transmit(&hspi1, (uint8_t *) arg_ptr, arg_int, 10000); break; case U8X8_MSG_BYTE_INIT: break; case U8X8_MSG_BYTE_SET_DC: HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int); break; case U8X8_MSG_BYTE_START_TRANSFER: break; case U8X8_MSG_BYTE_END_TRANSFER: break; default: return 0; } return 1; }
I placed csrc directory of u8g2 library to Drivers directory of STM32 project, generated by CubeMX and renamed it to u8g2. Then you should add this path to includes: right click on project "Properties"->"C/C++ Build"->"Settings"->"Includes".
Include this u8g2 header in the start of main.c file:
#include "u8g2.h"
static u8g2_t u8g2;
u8g2_Setup_ssd1306_128x64_noname_1(&u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi, u8x8_stm32_gpio_and_delay_cb); u8g2_InitDisplay(&u8g2); u8g2_SetPowerSave(&u8g2, 0);
Ok, now last point, drawing procedure itself:
while (1) { u8g2_FirstPage(&u8g2); do { u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr); u8g2_DrawStr(&u8g2, 0, 15, "Hello World!"); u8g2_DrawCircle(&u8g2, 64, 40, 10, U8G2_DRAW_ALL); } while (u8g2_NextPage(&u8g2)); }
Invoking: MCU GCC Linker arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -specs=nosys.specs -specs=nano.specs -T"../STM32F401RETx_FLASH.ld" -Wl,-Map=output.map -Wl,--gc-sections -o "f401_u8g2_test.elf" @"objects.list" -lm /home/artsin/Ac6/SystemWorkbench/plugins/fr.ac6.mcu.externaltools.arm-none.linux64_1.16.0.201807130628/tools/compiler/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld: f401_u8g2_test.elf section `.bss' will not fit in region `RAM' /home/artsin/Ac6/SystemWorkbench/plugins/fr.ac6.mcu.externaltools.arm-none.linux64_1.16.0.201807130628/tools/compiler/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld: region `RAM' overflowed by 28920 bytes makefile:36: recipe for target 'f401_u8g2_test.elf' failed collect2: error: ld returned 1 exit status make: *** [f401_u8g2_test.elf] Error 1
The operation of eliminating the unused code and data from the final executable is directly performed by the linker. In order to do this, it has to work with objects compiled with the following options: -ffunction-sections -fdata-sections.
In SystemWorkbench for STM32 this option could be enabled there: "Properties"->"C/C++ Build"->"Settings"->"Optimization".
Ok, now it looks like that is all. Here we have result:
Source code for this example is here: https://github.com/leechwort/u8g2-stm32-example
Great description and nice summery (hmm... I should rename this account to u8g2)
ReplyDeleteOliver
Created a reference from the u8g2 project to this page. Thanks again.
ReplyDeleteWhoo! Thank you too!!!:)
DeleteThank you! Works like a charm!
ReplyDeleteNice to hear!:)
DeleteI would like to ask what is the void *arg_ptr? Thanks!
ReplyDeleteHi, why in this line I get theese errors in this line:
ReplyDeleteu8g2_Setup_ssd1306_128x64_noname_1(&u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi, u8x8_stm32_gpio_and_delay_cb); ?
expected declaration specifiers or '...' before '&' token
unknown type name 'u8x8_byte_4wire_hw_spi'
Have you included u8g2.h file? Have you initialized variable? What IDE do you use?
DeleteHi Artyom and congratulations for the blog. I want to ask you, what should I do if I use a software spi? I think I should modify the u8x8_stm32_gpio_and_delay function telling to the library what pins I'm gonna using... if it's right, what should I modify and how? thank you! I'm using stm32cube IDE and my pins are: scl a5, cs a6, data a7,reset a8, rs a9.
DeleteMy code is:
InitHW();
io_lcd_pwm_set(5000);
lcd_init();
lcd_clear();
lcd_set_inverse(0);
//above init function not proper from the library (I tested them and work)
u8g2_Setup_st7565_nhd_c12864_1(&u8g2, U8G2_R2, u8x8_byte_4wire_sw_spi, u8x8_stm32_gpio_and_delay);
//u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
//test function:
u8g2_FirstPage(&u8g2);
do
{
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
u8g2_DrawStr(&u8g2, 10, 15, "Hello World!");
u8g2_DrawCircle(&u8g2, 64, 0, 5, U8G2_DRAW_ALL);
} while (u8g2_NextPage(&u8g2));
I see the correct test on the display (not properly in the correct position but it's ok, I think I'll fix it) but if I comment all the init functions above and decomment the //u8g2_InitDisplay(&u8g2); function the program is stack and go on a loop on that
Hi! I've never used software SPI on stm32, but yes, it looks like you should provide more detailed gpio and delay function. Look here for template https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform#template-for-the-gpio-and-delay-callback. For example, you should implement U8X8_MSG_GPIO_CS message handling like HAL_GPIO_WritePin(GPIOx, GPIO_PIN_m, arg_int). Also, you should implement additional delay functions, google for microseconds delay for stm32.
DeleteHello,
ReplyDeleteI am using the STM32F446-Nucleo with the SSD1309 using the following configuration (SPI):
PA5 -> SCL
PA7 -> SDA
PA9 -> RES
PC7 -> DC
PB6 -> CS
SPI speed: 22.5MBits/s
CPOL: 0
CPHA: 0
I use the same callbacks as you but my OLED doesn't dispaly anything. Nothing on clock and data bus too.
Could you give me a hint? Thanks
This comment has been removed by the author.
ReplyDeleteThank you very much! This was a quite helpful supplement to the official wiki. I used Mbed instead of the STM32CubeMX and a SSD1322 display controller.
ReplyDeleteHello,
ReplyDeleteThanks to the description, the display works well.(cubeide)
How to print variables (int, float, etc.)?
The print function does not work.
Hi! Simplest solution is to use sprintf function to print into temporary char buffer. Than you can use this buffer in u8g2_DrawStr.
DeleteIs it possible to explain step by step how to use the "-ffunction-sections -fdata-sections" feature. Use in Kiel software? I am using Keil V5 software.
ReplyDeleteI have encountered this error after completing all the steps you have described:
GLCD-1\GLCD-1.axf: Error: L6406E: No space in execution regions with .ANY selector matching u8g2_d_memory.o(.bss).
GLCD-1\GLCD-1.axf: Error: L6406E: No space in execution regions with .ANY selector matching startup_stm32f103xe.o(STACK).
GLCD-1\GLCD-1.axf: Error: L6406E: No space in execution regions with .ANY selector matching main.o(.bss).
GLCD-1\GLCD-1.axf: Error: L6406E: No space in execution regions with .ANY selector matching stm32f1xx_hal.o(.data).
GLCD-1\GLCD-1.axf: Error: L6406E: No space in execution regions with .ANY selector matching system_stm32f1xx.o(.data).
GLCD-1\GLCD-1.axf: Error: L6407E: Sections of aggregate size 0x1ee30 bytes could not fit into .ANY selector(s).
use option keil arm compiler v6.16 in option target
DeleteHI!My LCD screen does not have a DC pin, what should I do?
ReplyDeleteuint8_t Data = 0xFA;
uint8_t Cmd = 0xF8;
uint8_t Clean = 0x01;
case U8X8_MSG_GPIO_DC:
if (arg_int){
HAL_SPI_Transmit(&Spi2, (uint8_t *) &Data, 1, 10000);
}else{
HAL_SPI_Transmit(&Spi2, (uint8_t *) &Cmd, 1, 10000);}
break;
Then I wanted to use the above command to tell my LCD12864, whether I want to write data or commands, but my LCD display is messed up, please help me,thanks.
Very nice project. Could you explain to me how to use it over i2c communication? i'm a begginer in STM32. Thanks from Brazil.
ReplyDeleteReally nice project. I'm a begginer in STM32. I'm trying to use a SSD1306 OLED display in I2C mode in STM32F103C. Is it possible to send to me an example? Thanks from Brazil.
ReplyDelete