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;
}
As you see,there are several messages, which should be handled. In "Porting Guide" there are two templates for both functions. Template for the GPIO and Delay callback has a lot of messages, which should be handled, but in our case most of them are unnecessary. For example, very small delays are required for software SPI communication. We have hardware SPI and we need to implement millisecond delay only. Also there are a lot of messages about pins, not related to SPI command at all.

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;
}
As you see, two messages are just empty: U8X8_MSG_BYTE_START_TRANSFER and U8X8_MSG_BYTE_END_TRANSFER. According to this comment, possibly you can use this messages to toggle CS pin. But actually my display does not have CS pin and moreover, I think that toggling CS pin is not required after transfer. But if you will have some problems with communication, you can try to play with toggling this pin there.

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"
And place global u8g2 variable:
static u8g2_t u8g2;
Setup code looks like:
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);
First line setup itself. More about startup is there. I can add only that startup functions has different endings: _1, _2 or _f: buffer size will depends on this. _1 in our case means 128 byte page buffer.

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));
  }
But actually, if we'll try to compile this project, we'll get this message

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
As we see, RAM is totally overflowed! This is due to file u8g2_d_memory.c. There are a lot of static variables, most of them is unused, but still linked into our firmware. The same is with fonts - they all will try to be linked into our FLASH region. To prevent inclusion of such dead code you should use -fdata-sections gcc option. This is what documentation says:

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

Comments

  1. Great description and nice summery (hmm... I should rename this account to u8g2)

    Oliver

    ReplyDelete
  2. Created a reference from the u8g2 project to this page. Thanks again.

    ReplyDelete
  3. I would like to ask what is the void *arg_ptr? Thanks!

    ReplyDelete
  4. Hi, why in this line I get theese errors in this line:
    u8g2_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'

    ReplyDelete
    Replies
    1. Have you included u8g2.h file? Have you initialized variable? What IDE do you use?

      Delete
    2. Hi 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.
      My 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

      Delete
    3. 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.

      Delete
  5. Hello,

    I 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

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Thank 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.

    ReplyDelete
  8. Hello,

    Thanks to the description, the display works well.(cubeide)
    How to print variables (int, float, etc.)?
    The print function does not work.

    ReplyDelete
    Replies
    1. Hi! Simplest solution is to use sprintf function to print into temporary char buffer. Than you can use this buffer in u8g2_DrawStr.

      Delete
  9. Is 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.
    I 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).

    ReplyDelete
    Replies
    1. use option keil arm compiler v6.16 in option target

      Delete
  10. HI!My LCD screen does not have a DC pin, what should I do?

    uint8_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.

    ReplyDelete
  11. Very nice project. Could you explain to me how to use it over i2c communication? i'm a begginer in STM32. Thanks from Brazil.

    ReplyDelete
  12. Really 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

Post a Comment

Popular posts from this blog

Use PCM5102 with STM32

RFFT in CMSIS DSP. Part 1.