移植好U8g2图形库的STM32F407标准库工程模板,0.96寸OLED驱动程序

STM32F407 Standard Peripheral Library project template with the U8g2 graphics library ported, using a 0.96-inch OLED screen (SSD1306) driven by hardware IIC.

Spent a whole evening porting it. The development board’s main MCU is STM32F407VET6, I2C interface is I2C1, SCL connected to PB6, SDA connected to PB7.

Embedded-related articles: https://blog.zeruns.com/category/IOT/

Electronics/Circuit-related articles: https://blog.zeruns.com/category/electrical/

Electronics/MCU technical discussion group: 2169025065

U8g2 Introduction

U8g2 is a monochrome graphics library for embedded devices, supporting multiple monochrome OLED and LCD controllers such as SSD1306, ST7920, etc. The U8g2 library can be installed from the Arduino IDE library manager or ported to platforms like STM32. U8g2 supports three drawing modes: full-screen buffer mode, page buffer mode, and U8x8 character mode. Using U8g2 requires selecting the appropriate constructor, initializing the display, setting pin numbers, writing callback functions, and issuing drawing commands.

Advantages of U8g2 include support for multiple fonts, Chinese character display, and rich graphics primitives like lines, rectangles, circles, and bitmaps. Disadvantages are its memory footprint, slower speed, and lack of support for controller-less displays. Typical applications include sensor data display, clocks, menus, and animations. U8g2 is a powerful, well-compatible, and easy-to-use monochrome graphics library.

Effect Pictures


Demo video: https://www.bilibili.com/video/BV17W4y197WW/

About the Crystal Oscillator

I use an 8 MHz crystal; the PLL parameters in the clock-tree configuration have been modified so the MCU runs at 168 MHz. If you switch to a crystal of another frequency you must adjust the parameters—search online for how.

Places to modify:

stm32f4xx.h, line 137.

#define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
// Change 8000000 to your crystal frequency, in Hz

system_stm32f4xx.c, lines 364 and 394.

#if defined(STM32F40_41xxx) || defined(STM32F427_437xx) || defined(STM32F429_439xx) || defined(STM32F401xx) || defined(STM32F469_479xx)
 /* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
 #define PLL_M      4
// The 4 here corresponds to /4 in the clock-tree diagram below
#if defined (STM32F40_41xxx)
#define PLL_N      168
// The 168 here corresponds to x168 in the clock-tree diagram below
```![](upload://nfU4Qva05kQFBqJ1LAyXx0Og3KQ.png)

## Component Purchase Links

- 0.96-inch OLED screen: [https://u.jd.com/1zjpDNC](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BARAJK1olXwQCXVheC0IUBV8IGloTXw4AU1hVDkweBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksWAmkKE1kSWw4EU1dbFxJSXzI4WzB2K1NjPSw9VzFBQBpJTg5iPBhWElJROEonA24JGloSWgAAXG5tCEwnQgEIGV4SXwcFVW5cOEsQCmgNHV0UWg4EUVttD0seM20IGFgQWgQLUUJUDEoUAGY4K2sWbQECXUpbegpFF2l6K2sVbQUyVF9dAEgXA2cOGFMJXQMGXF9cFEsQCmgNHV0UXAEFVlttCkoWB2Y4K2tVGQViISotXEpUR2xIeSNWKGJREh4DXxF5AS1dSD0SFQNXVQoHfghLSm8LKw)
- STM32F407 development board: [https://s.click.taobao.com/7AtnHFu](https://s.click.taobao.com/t?e=m%3D2%26s%3DczQqv026Zjtw4vFB6t2Z2ueEDrYVVa64LKpWJ%2Bin0XLjf2vlNIV67lRwohxEizvIxa9spvDO8Cl%2FmzaS0s2vXCsmIF0UHtujBkxkgN7jKg%2BiJaQjFxYBevWfPAv4x3d%2FOlGkMkr%2BSUUYmUGuVZn0uRDBbf95h6seTxpmwkkouzqi1jMNxDhLMnotgd7NXRy%2F3OppJwt5ethtXyEYK9MgsI1cD97yFV3TmSpANgNSVXSPM7MeivsNdkAXdMlWzMNyHk3i9xXmKdKMJi1gYBJvNFSsXCNd9EoxE59iYTGkDbWFi4V5KBpmMoBZLSyIfgF7gR2l5BVRVVbGDmntuH4VtA%3D%3D&union_lens=lensId%3APUB%401686477136%40210562f5_0a55_188a9dee240_346f%4001%40eyJmbG9vcklkIjo2MTM1NCwiic3BtQiiI6Il9wb3J0YWxfdjJfcGFnZXNfcHJvbW9fZ29vZHNfaW5kZXhfaHRtIn0ie)
- Jumper wires: [https://u.jd.com/1zjFP0Z](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAQcJK1olVQAKVlZZCUoXM2oJElkcXwUHXVtYOA9IWzFXKwJQGEdAX0BDUA5DX3BTTkRHA1ocUV9UCkIVAGoBHl4KBENeCW4NDA9CAGcLXjlHWnBqEycFACNOCxZoF1clXDYCVV9cCUwQBW0AK2sVWjZDOllYAE0XAF8JK1sSVAEHUldZCU4fBWo4HFscbQQCV11YD0keBnMBH1oWXg8yZG5eOEwXCnsOaRpHSQBwZG5dOEgnA24IE1gQXwYHV1lBCEkWAW8IB1sSVAEHUlhdAUweCmY4GVoUWQ8yZG4YXDFkd2tXEjhHCXxLB1wAXzsVAmxfRAN7X19ZVgg8VhZMYB94fChoFEVmZA)

## Code

Complete project download: [https://url.zeruns.com/JUoKJ](https://url.zeruns.com/JUoKJ)    Extraction code: t6wt

Partial code:

**main.c**

```C
#include "stm32f4xx.h"
// #include "Timer.h"
#include "Delay.h"
#include "u8g2.h"
#include "OLED.h"
#include "IWDG.h"

uint8_t u8x8_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr);
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
void u8g2_Init(u8g2_t *u8g2);
void draw(u8g2_t *u8g2);

int main(void)
{
      uint8_t t = 0;
      IWDG_Configuration(); // Initialize watchdog
      OLED_I2C_Init();      // Initialize OLED
      u8g2_t u8g2;
      u8g2_Init(&u8g2); // Initialize U8g2

      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // Enable GPIOA peripheral clock

      GPIO_InitTypeDef GPIO_InitStructure;                   // Define structure
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;          // Set GPIO mode to output
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // Set GPIO pins 6 and 7
      GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;       // Set GPIO speed to 100 MHz
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;         // Set GPIO to push-pull output
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;       // Set GPIO pull-up/pull-down mode
      GPIO_Init(GPIOA, &GPIO_InitStructure);                 // Initialize GPIO

      Delay_ms(100);

      u8g2_DrawLine(&u8g2, 0, 0, 127, 63); // Draw a line from (0,0) to (127,63)
      u8g2_SendBuffer(&u8g2);              // Send buffer data
      u8g2_DrawLine(&u8g2, 127, 0, 0, 63);
      u8g2_SendBuffer(&u8g2);

      Delay_ms(300);

      u8g2_ClearBuffer(&u8g2);  // Clear buffer data
      draw(&u8g2);
      u8g2_SendBuffer(&u8g2);
      Delay_ms(1000);
      
      u8g2_ClearBuffer(&u8g2);
      IWDG_FeedDog(); // Feed the dog to prevent CPU reset
      u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);    // Select font
      u8g2_DrawStr(&u8g2, 0, 15, "Hello World!");

      u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese2);
      u8g2_DrawUTF8(&u8g2, 0, 30, "H你好世界");

      u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_chinese2);
      u8g2_DrawUTF8(&u8g2, 0, 43, "H你好世界");
      
      u8g2_SetFont(&u8g2, u8g2_font_fur11_tr);
      u8g2_DrawUTF8(&u8g2, 0, 59, "blog.zeruns.com");

      u8g2_SendBuffer(&u8g2);

      Delay_ms(1300);

      while (1)
      {
            Delay_ms(100);
            u8g2_ClearBuffer(&u8g2);// Clear buffer data
            if (++t >= 32)
                  t = 1;
            u8g2_DrawCircle(&u8g2, 64, 32, t, U8G2_DRAW_ALL);   // Draw circle
            u8g2_DrawCircle(&u8g2, 32, 32, t, U8G2_DRAW_ALL);
            u8g2_DrawCircle(&u8g2, 96, 32, t, U8G2_DRAW_ALL);
            u8g2_SendBuffer(&u8g2); // Send buffer data
            GPIO_ToggleBits(GPIOA, GPIO_Pin_6);
            IWDG_FeedDog(); // Feed the dog to prevent CPU reset
      }
}
// https://blog.zeruns.com
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
      static uint8_t buffer[32]; /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
      static uint8_t buf_idx;
      uint8_t *data;

      switch (msg)
      {
      case U8X8_MSG_BYTE_SEND:
            data = (uint8_t *)arg_ptr;
            while (arg_int > 0)
            {
                  buffer[buf_idx++] = *data;
                  data++;
                  arg_int--;
            }
            break;

      case U8X8_MSG_BYTE_INIT:
            /* add your custom code to init i2c subsystem */
            break;

      case U8X8_MSG_BYTE_START_TRANSFER:
            buf_idx = 0;
            break;

      case U8X8_MSG_BYTE_END_TRANSFER:
            HW_I2cWrite(buffer, buf_idx);   // Hardware I2C write bytes
            break;

      default:
            return 0;
      }
      return 1;
}
// https://blog.zeruns.com
uint8_t u8g2_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:
            OLED_I2C_Init();    // Initialize
            break;

      case U8X8_MSG_DELAY_MILLI:
            Delay_ms(arg_int);  // Delay
            break;

      case U8X8_MSG_GPIO_I2C_CLOCK:
            break;

      case U8X8_MSG_GPIO_I2C_DATA:
            break;

      default:
            return 0;
      }
      return 1; // command processed successfully.
}

void u8g2_Init(u8g2_t *u8g2)
{
      // u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay); // Initialize u8g2, software I2C
      u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8g2_gpio_and_delay); // Initialize u8g2, hardware I2C
      u8g2_InitDisplay(u8g2);                                                                       // Initialize according to selected chip, display remains off after init
      u8g2_SetPowerSave(u8g2, 0);                                                                   // Turn on display
      u8g2_SetContrast(u8g2, 88);                                                                   // Set screen brightness
      u8g2_ClearBuffer(u8g2);                                                                       // Clear buffer
}
// https://blog.vpszj.cn
void draw(u8g2_t *u8g2)
{
      u8g2_SetFontMode(u8g2, 1);              /* Font mode selection */
      u8g2_SetFontDirection(u8g2, 0);         /* Font direction selection */
      u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /* Font selection */
      u8g2_DrawStr(u8g2, 0, 20, "U");

      u8g2_SetFontDirection(u8g2, 1);
      u8g2_SetFont(u8g2, u8g2_font_inb30_mn);
      u8g2_DrawStr(u8g2, 21, 8, "8");

      u8g2_SetFontDirection(u8g2, 0);
      u8g2_SetFont(u8g2, u8g2_font_inb24_mf);
      u8g2_DrawStr(u8g2, 51, 30, "g");
      u8g2_DrawStr(u8g2, 67, 30, "\xb2");

      u8g2_DrawHLine(u8g2, 2, 35, 47);
      u8g2_DrawHLine(u8g2, 3, 36, 47);
      u8g2_DrawVLine(u8g2, 45, 32, 12);
      u8g2_DrawVLine(u8g2, 46, 33, 12);

      u8g2_SetFont(u8g2, u8g2_font_4x6_tr);
      u8g2_DrawStr(u8g2, 1, 54, "github.com/olikraus/u8g2");
}
```OLED.c

```C
#include "stm32f4xx.h"
#include "OLED_Font.h"
#include "Delay.h"

/*OLED screen address*/
#define OLED_ADDRESS 0x78

//I2C wait timeout
#define I2C_TIMEOUT 5000

/*Pin initialization*/
void OLED_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//Enable I2C1 clock
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//Enable GPIOB clock
    
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1);  //Enable PB6 alternate function connected to I2C1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);
 
	/*STM32F407 hardware I2C: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;                   //Define structure to configure GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;		    //Set GPIO mode to alternate function
	GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;		//Set GPIO speed to 100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;          //Set GPIO to open-drain output
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;           //Set GPIO pull-up mode
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7; //Set GPIO pins
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2C1);	//Reset I2C1 peripheral registers to default values
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//Operating mode
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Clock duty cycle, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//Host I2C address, not used so can be arbitrary, no effect
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Enable acknowledge bit
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Set address length to 7 bits
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//I2C transfer speed, 400K, check chip datasheet for supported speeds.
	I2C_Init(I2C1, &I2C_InitStructure);

	I2C_Cmd(I2C1, ENABLE);
}
// https://blog.zeruns.com
void HW_I2cWrite(uint8_t *buf,uint8_t len)
{
	if(len<=0)
		return ;
	uint32_t wait_time=0;

	/* wait for the busy flag to be reset */
	while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	I2C_GenerateSTART(I2C1, ENABLE);//Start I2C1
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))/*EV5, master mode*/
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);	//Device address -- default 0x78
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	for(uint8_t i=0;i<len;i++)
	{
		I2C_SendData(I2C1, buf[i]);//Send data
		while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
		{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
			}
		}
	}
	I2C_GenerateSTOP(I2C1, ENABLE);//Close I2C1 bus
}

Recommended Open-Source Projects

Recommended Reading