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

Шаблон проекта на стандартной библиотеке STM32F407 с портированной графической библиотекой U8g2. Используется OLED-дисплей 0.96 дюйма (SSD1306), управляемый аппаратным IIC.

На портировку ушла целая ночь. Плата управляется микроконтроллером STM32F407VET6, интерфейс I2C1: SCL — PB6, SDA — PB7.

Статьи по встраиваемым системам: https://blog.zeruns.com/category/IOT/

Статьи по электронике/схемотехнике: https://blog.zeruns.com/category/electrical/

Технический чат по электронике и МК: 2169025065

Кратко о U8g2

U8g2 — монохромная графическая библиотека для встраиваемых систем, поддерживает множество контроллеров OLED и LCD, включая SSD1306, ST7920 и др. Доступна через менеджер библиотек Arduino IDE и легко портится на STM32 и другие платформы. Работает в трёх режимах: полный кадровый буфер, постраничный буфер и символьный режим U8x8. Требует выбора правильного конструктора, инициализации дисплея, указания выводов, написания callback-функций и графических команд.

Плюсы: множество шрифтов, поддержка китайских иероглифов, богатый набор примитивов (линии, прямоугольники, круги, растровые изображения). Минусы: занимает ОЗУ, ниже скорость, не работает с «голыми» дисплеями без контроллера. Применяется для вывода данных сенсоров, часов, меню, простой анимации и т. д. Мощная, совместимая и удобная библиотека.

Результат


Демонстрационное видео: https://www.bilibili.com/video/BV17W4y197WW/

Про кварц

Я использую кварц на 8 МГц и изменил параметры PLL в дереве тактирования, чтобы ядро работало на 168 МГц. При замене кварца параметры нужно пересчитать — ищите информацию в интернете.

Что править:

stm32f4xx.h, строка 137.

#define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
// Замените 8000000 на частоту вашего кварца, Гц

system_stm32f4xx.c, строки 364 и 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
// 4 соответствует делителю /M на схеме ниже
#if defined (STM32F40_41xxx)
#define PLL_N      168
// 168 соответствует множителю ×N на схеме ниже
```![](upload://nfU4Qva05kQFBqJ1LAyXx0Og3KQ.png)

## Адреса покупки компонентов

- OLED-дисплей 0,96 дюйма: [https://u.jd.com/1zjpDNC](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BARAJK1olXwQCXVheC0IUBV8IGloTXw4AU1hVDkweBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksWAmkKE1kSWw4EU1dbFxJSXzI4WzB2K1NjPSw9VzFBQBpJTg5iPBhWElJROEonA24JGloSWgAAXG5tCEwnQgEIGV4SXwcFVW5cOEsQCmgNHV0UWg4EUVttD0seM20IGFgQWgQLUUJUDEoUAGY4K2sWbQECXUpbegpFF2l6K2sVbQUyVF9dAEgXA2cOGFMJXQMGXF9cFEsQCmgNHV0UXAEFVlttCkoWB2Y4K2tVGQViISotXEpUR2xIeSNWKGJREh4DXxF5AS1dSD0SFQNXVQoHfghLSm8LKw)
- Плата разработки STM32F407: [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)
- Провода дюпон: [https://u.jd.com/1zjFP0Z](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAQcJK1olVQAKVlZZCUoXM2oJElkcXwUHXVtYOA9IWzFXKwJQGEdAX0BDUA5DX3BTTkRHA1ocUV9UCkIVAGoBHl4KBENeCW4NDA9CAGcLXjlHWnBqEycFACNOCxZoF1clXDYCVV9cCUwQBW0AK2sVWjZDOllYAE0XAF8JK1sSVAEHUldZCU4fBWo4HFscbQQCV11YD0keBnMBH1oWXg8yZG5eOEwXCnsOaRpHSQBwZG5dOEgnA24IE1gQXwYHV1lBCEkWAW8IB1sSVAEHUlhdAUweCmY4GVoUWQ8yZG4YXDFkd2tXEjhHCXxLB1wAXzsVAmxfRAN7X19ZVgg8VhZMYB94fChoFEVmZA)

## Код

Скачать полный проект: [https://url.zeruns.com/JUoKJ](https://url.zeruns.com/JUoKJ) Код извлечения: t6wt

Часть кода:

**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(); // инициализация сторожевого таймера
      OLED_I2C_Init();      // инициализация OLED
      u8g2_t u8g2;
      u8g2_Init(&u8g2); // инициализация U8g2

      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // включение тактирования порта GPIOA

      GPIO_InitTypeDef GPIO_InitStructure;                   // объявление структуры
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;          // режим вывода — выход
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // выводы 6 и 7
      GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;       // скорость 100 МГц
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;         // push-pull выход
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;       // без подтяжки
      GPIO_Init(GPIOA, &GPIO_InitStructure);                 // инициализация GPIO

      Delay_ms(100);

      u8g2_DrawLine(&u8g2, 0, 0, 127, 63); // линия из (0,0) в (127,63)
      u8g2_SendBuffer(&u8g2);              // отправка буфера
      u8g2_DrawLine(&u8g2, 127, 0, 0, 63);
      u8g2_SendBuffer(&u8g2);

      Delay_ms(300);

      u8g2_ClearBuffer(&u8g2);  // очистка буфера
      draw(&u8g2);
      u8g2_SendBuffer(&u8g2);
      Delay_ms(1000);
      
      u8g2_ClearBuffer(&u8g2);
      IWDG_FeedDog(); // сброс сторожевого таймера
      u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);    // выбор шрифта
      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);// очистка буфера
            if (++t >= 32)
                  t = 1;
            u8g2_DrawCircle(&u8g2, 64, 32, t, U8G2_DRAW_ALL);   // круг
            u8g2_DrawCircle(&u8g2, 32, 32, t, U8G2_DRAW_ALL);
            u8g2_DrawCircle(&u8g2, 96, 32, t, U8G2_DRAW_ALL);
            u8g2_SendBuffer(&u8g2); // отправка буфера
            GPIO_ToggleBits(GPIOA, GPIO_Pin_6);
            IWDG_FeedDog(); // сброс сторожевого таймера
      }
}
// 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 никогда не отправит больше 32 байт между START_TRANSFER и 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:
            /* добавьте свой код инициализации подсистемы I2C */
            break;

      case U8X8_MSG_BYTE_START_TRANSFER:
            buf_idx = 0;
            break;

      case U8X8_MSG_BYTE_END_TRANSFER:
            HW_I2cWrite(buffer, buf_idx);   // запись байтов по аппаратному I2C
            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();    // инициализация
            break;

      case U8X8_MSG_DELAY_MILLI:
            Delay_ms(arg_int);  // задержка
            break;

      case U8X8_MSG_GPIO_I2C_CLOCK:
            break;

      case U8X8_MSG_GPIO_I2C_DATA:
            break;

      default:
            return 0;
      }
      return 1; // команда обработана успешно.
}

void u8g2_Init(u8g2_t *u8g2)
{
      // u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay); // инициализация u8g2, программный I2C
      u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8g2_gpio_and_delay); // инициализация u8g2, аппаратный I2C
      u8g2_InitDisplay(u8g2);                                                                       // инициализация дисплея по выбранному чипу, после инициализации дисплей выключен
      u8g2_SetPowerSave(u8g2, 0);                                                                   // включение дисплея
      u8g2_SetContrast(u8g2, 88);                                                                   // установка яркости
      u8g2_ClearBuffer(u8g2);                                                                       // очистка буфера
}
// https://blog.vpszj.cn
void draw(u8g2_t *u8g2)
{
      u8g2_SetFontMode(u8g2, 1);              /* выбор режима шрифта */
      u8g2_SetFontDirection(u8g2, 0);         /* выбор направления шрифта */
      u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /* выбор шрифта */
      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屏地址*/
#define OLED_ADDRESS 0x78

//I2C等待超时时间
#define I2C_TIMEOUT 5000

/*引脚初始化*/
void OLED_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//使能I2C1时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟
    
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1);  //开启PB6的复用功能连接至I2C1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);
 
	/*STM32F407芯片的硬件I2C: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;                   //定义结构体配置GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;		    //设置GPIO口模式为复用IO模式
	GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;		//设置GPIO口速度100Mhz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;          //设置GPIO口为开漏输出
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;           //设置GPIO上拉模式
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7; //设置GPIO口
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2C1);	//将外设I2C1寄存器重设为缺省值
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//工作模式
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//时钟占空比,Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//主机的I2C地址,用不到则随便写,无影响
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//使能应答位
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//设置地址长度7位
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//I2C传输速度,400K,根据自己所用芯片手册查看支持的速度。	
	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 falg 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);//开启I2C1
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))/*EV5,主模式*/
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);	//器件地址 -- 默认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]);//发送数据
		while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
		{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
			}
		}
	}
	I2C_GenerateSTOP(I2C1, ENABLE);//关闭I2C1总线
}

Рекомендуемые открытые проекты

Рекомендуем к прочтению