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

Đã port thư viện đồ họa U8g2 cho mẫu dự án thư viện chuẩn STM32F407, sử dụng màn hình OLED 0,96 inch (SSD1306), điều khiển bằng phần cứng IIC.

Mất cả một tối để port. Board phát triển sử dụng MCU chính là STM32F407VET6, giao tiếp I2C dùng I2C1, SCL nối PB6, SDA nối PB7.

Bài viết liên quan đến nhúng: https://blog.zeruns.com/category/IOT/

Bài viết liên quan đến điện tử/mạch: https://blog.zeruns.com/category/electrical/

Nhóm kỹ thuật điện tử/vi điều khiển: 2169025065

Giới thiệu U8g2

Thư viện đồ họa U8g2 là thư viện đồ họa đơn sắc dành cho thiết bị nhúng, hỗ trợ nhiều bộ điều khiển màn hình OLED và LCD đơn sắc như SSD1306, ST7920, v.v. Thư viện U8g2 có thể cài quản trị thư viện Arduino IDE, cũng có thể port sang các nền tảng như STM32. U8g2 hỗ trợ ba chế độ vẽ: toàn màn hình bộ nhớ đệm, bộ nhớ đệm trang và chế độ ký tự U8x8. Khi dùng U8g2 cần chọn constructor phù hợp, khởi tạo màn hình, đặt số chân, viết hàm callback và lệnh vẽ.

Ưu điểm của U8g2: nhiều font chữ, hỗ trợ tiếng Trung, cung cấp nhiều hình vẽ như đường, khung, tròn, bitmap, v.v. Nhược điểm: chiếm không gian bộ nhớ, tốc độ chậm, không hỗ trợ màn hình không có bộ điều khiển. Ứng dụng: hiển thị dữ liệu cảm biến, đồng hồ, menu, hoạt hình, v.v. U8g2 là thư viện đồ họa đơn sắc mạnh, tương thích tốt, dễ dùng.

Hình ảnh demo


Video demo: https://www.bilibili.com/video/BV17W4y197WW/

Về vấn đề thạch anh

Mình dùng thạch anh 8 MHz, đã sửa tham số PLL trong cấu hình cây clock để MCU chạy 168 MHz. Nếu đổi thạch anh khác cần chỉnh lại tham số, cách sửa tự Google nhé.

Chỗ cần sửa như sau:

stm32f4xx.h dòng 137.

#define HSE_VALUE    ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
// Thay 8000000 thành tần số thạch anh của bạn, đơn vị Hz

system_stm32f4xx.c dòng 364 và 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 này tương ứng /M = /4 trong ảnh cây clock dưới
#if defined (STM32F40_41xxx)
#define PLL_N      168
// 168 này tương ứng *N = x168 trong ảnh cây clock dưới
```![](upload://nfU4Qva05kQFBqJ1LAyXx0Og3KQ.png)

## Địa chỉ mua linh kiện

- Màn hình OLED 0.96 inch: [https://u.jd.com/1zjpDNC](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BARAJK1olXwQCXVheC0IUBV8IGloTXw4AU1hVDkweBV9MRANLAjZbERscSkAJHTdNTwcKBlMdBgABFksWAmkKE1kSWw4EU1dbFxJSXzI4WzB2K1NjPSw9VzFBQBpJTg5iPBhWElJROEonA24JGloSWgAAXG5tCEwnQgEIGV4SXwcFVW5cOEsQCmgNHV0UWg4EUVttD0seM20IGFgQWgQLUUJUDEoUAGY4K2sWbQECXUpbegpFF2l6K2sVbQUyVF9dAEgXA2cOGFMJXQMGXF9cFEsQCmgNHV0UXAEFVlttCkoWB2Y4K2tVGQViISotXEpUR2xIeSNWKGJREh4DXxF5AS1dSD0SFQNXVQoHfghLSm8LKw)
- Board phát triển 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)
- Dây Dupont: [https://u.jd.com/1zjFP0Z](https://union-click.jd.com/jdc?e=618%7Cpc%7C&p=JF8BAQcJK1olVQAKVlZZCUoXM2oJElkcXwUHXVtYOA9IWzFXKwJQGEdAX0BDUA5DX3BTTkRHA1ocUV9UCkIVAGoBHl4KBENeCW4NDA9CAGcLXjlHWnBqEycFACNOCxZoF1clXDYCVV9cCUwQBW0AK2sVWjZDOllYAE0XAF8JK1sSVAEHUldZCU4fBWo4HFscbQQCV11YD0keBnMBH1oWXg8yZG5eOEwXCnsOaRpHSQBwZG5dOEgnA24IE1gQXwYHV1lBCEkWAW8IB1sSVAEHUlhdAUweCmY4GVoUWQ8yZG4YXDFkd2tXEjhHCXxLB1wAXzsVAmxfRAN7X19ZVgg8VhZMYB94fChoFEVmZA)

## Mã nguồn

Tải toàn bộ project: [https://url.zeruns.com/JUoKJ](https://url.zeruns.com/JUoKJ)    Mã giải nén: t6wt

Một phần mã:

**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(); // Khởi tạo Watchdog
      OLED_I2C_Init();      // Khởi tạo OLED
      u8g2_t u8g2;
      u8g2_Init(&u8g2); // Khởi tạo U8g2

      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // Bật clock ngoại vi GPIOA

      GPIO_InitTypeDef GPIO_InitStructure;                   // Khai báo cấu trúc
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;          // Đặt chế độ GPIO là ngõ ra
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // Chân GPIO 6 và 7
      GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;       // Tốc độ 100 MHz
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;         // Ngõ ra Push-Pull
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;       // Không kéo lên/xuống
      GPIO_Init(GPIOA, &GPIO_InitStructure);                 // Khởi tạo GPIO

      Delay_ms(100);

      u8g2_DrawLine(&u8g2, 0, 0, 127, 63); // Vẽ đường thẳng từ (0,0) đến (127,63)
      u8g2_SendBuffer(&u8g2);              // Gửi dữ liệu bộ đệm
      u8g2_DrawLine(&u8g2, 127, 0, 0, 63);
      u8g2_SendBuffer(&u8g2);

      Delay_ms(300);

      u8g2_ClearBuffer(&u8g2);  // Xóa bộ đệm
      draw(&u8g2);
      u8g2_SendBuffer(&u8g2);
      Delay_ms(1000);
      
      u8g2_ClearBuffer(&u8g2);
      IWDG_FeedDog(); // “Cho ăn” Watchdog, tránh reset CPU
      u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);    // Chọn 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);// Xóa bộ đệm
            if (++t >= 32)
                  t = 1;
            u8g2_DrawCircle(&u8g2, 64, 32, t, U8G2_DRAW_ALL);   // Vẽ hình tròn
            u8g2_DrawCircle(&u8g2, 32, 32, t, U8G2_DRAW_ALL);
            u8g2_DrawCircle(&u8g2, 96, 32, t, U8G2_DRAW_ALL);
            u8g2_SendBuffer(&u8g2); // Gửi bộ đệm
            GPIO_ToggleBits(GPIOA, GPIO_Pin_6);
            IWDG_FeedDog(); // “Cho ăn” Watchdog
      }
}
// 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 không bao giờ gửi quá 32 byte giữa START_TRANSFER và 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:
            /* thêm mã tùy chỉnh để khởi tạo hệ thống I2C */
            break;

      case U8X8_MSG_BYTE_START_TRANSFER:
            buf_idx = 0;
            break;

      case U8X8_MSG_BYTE_END_TRANSFER:
            HW_I2cWrite(buffer, buf_idx);   // Ghi byte qua I2C phần cứng
            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();    // Khởi tạo
            break;

      case U8X8_MSG_DELAY_MILLI:
            Delay_ms(arg_int);  // Trì hoãn
            break;

      case U8X8_MSG_GPIO_I2C_CLOCK:
            break;

      case U8X8_MSG_GPIO_I2C_DATA:
            break;

      default:
            return 0;
      }
      return 1; // lệnh xử lý thành công
}

void u8g2_Init(u8g2_t *u8g2)
{
      // u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay); // khởi tạo u8g2, I2C mềm
      u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8g2_gpio_and_delay); // khởi tạo u8g2, I2C phần cứng
      u8g2_InitDisplay(u8g2);                                                                       // khởi tạo theo chip đã chọn, sau đó màn hình ở trạng thái tắt
      u8g2_SetPowerSave(u8g2, 0);                                                                   // bật màn hình
      u8g2_SetContrast(u8g2, 88);                                                                   // độ sáng
      u8g2_ClearBuffer(u8g2);                                                                       // xóa bộ đệm
}
// https://blog.vpszj.cn
void draw(u8g2_t *u8g2)
{
      u8g2_SetFontMode(u8g2, 1);              /*chọn chế độ font*/
      u8g2_SetFontDirection(u8g2, 0);         /*chọn hướng font*/
      u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /*chọn font*/
      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"

/*Địa chỉ màn hình OLED*/
#define OLED_ADDRESS 0x78

//Thời gian chờ timeout I2C
#define I2C_TIMEOUT 5000

/*Khởi tạo chân*/
void OLED_I2C_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);	//Bật clock I2C1
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//Bật clock GPIOB
    
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1);  //Bật chức năng alternate PB6 nối tới I2C1
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);
 
	/*Phần cứng I2C của STM32F407: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitTypeDef  GPIO_InitStructure;                   //Khai báo cấu trúc cấu hình GPIO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;		    //Đặt chế độ GPIO thành chế độ AF
	GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;		//Tốc độ GPIO 100 MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;          //Đặt kiểu ra open-drain
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;           //Kéo lên
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7; //Chọn chân
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_DeInit(I2C1);	//Reset thanh ghi I2C1 về mặc định
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;			//Chế độ làm việc
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//Tỷ lệ duty-cycle, Tlow/Thigh = 2
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;	//Địa chỉ I2C của master, không dùng thì điền tùy ý
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//Bật bit ACK
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//Độ dài địa chỉ 7 bit
	I2C_InitStructure.I2C_ClockSpeed = 400000;	//Tốc độ I2C 400 kHz, xem datasheet chip để biết tốc độ hỗ trợ	
	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;

	/* chờ cờ busy bị xóa */
	while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY))
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	I2C_GenerateSTART(I2C1, ENABLE);//Bắt đầu I2C1
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))/*EV5, chế độ master*/
	{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
		}
	}

	I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);	//Địa chỉ thiết bị -- mặc định 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]);//Gửi dữ liệu
		while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
		{
		wait_time++;
		if(wait_time>=I2C_TIMEOUT){
			wait_time=0;
			break;
			}
		}
	}
	I2C_GenerateSTOP(I2C1, ENABLE);//Dừng bus I2C1
}

Dự án mã nguồn mở đề xuất

Đọc thêm