主頁 > 作業系統 > U8g2圖形庫與STM32移植(I2C,軟體與硬體)

U8g2圖形庫與STM32移植(I2C,軟體與硬體)

2022-06-18 08:58:27 作業系統

U8g2圖形庫

簡介

U8g2 是一個用于嵌入式設備的簡易圖形庫,可以在多種 OLED 和 LCD 螢屏上,支持包括 SSD1306 等多種型別的底層驅動,并可以很方便地移植到 Arduino 、樹莓派、NodeMCU 和 ARM 上,

U8g2 庫同時包含了 U8x8 繪圖庫,兩者的區別為:

  • U8g2 包含各種簡單及復雜圖形的繪制,并支持各種形式的字體,但需要占用一定單片機的記憶體作為繪圖快取
  • U8x8 只包含簡單的顯示文本功能,且只支持簡單、定寬的字體,它直接繪制圖形,沒有快取功能

U8g2 庫的 GitHub 地址為:https://github.com/olikraus/u8g2 ,可以從中獲取到原始碼與檔案幫助,

移植

本次以將 U8g2 移植到 STM32 單片機與 SSD1306 通過 I2C 驅動的 128x64 OLED 為例,介紹移植的方法,不同單片機和驅動的移植可以參考這一程序,也可以參考 U8g2 的官方移植教程 https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform ,

首先下載或克隆 U8g2 的原始碼,這里主要是使用 C 語言撰寫,所以只需要用到 csrc 目錄下的檔案,

下載完成后,將 csrc 目錄拷貝或移動到工程目錄里,并重命名為合適的目錄名例如 u8g2lib

洗掉無用內容

接下來,需要洗掉一些無用的代碼,并添加底層驅動的代碼,

U8g2 的原始碼為了支持多種設備驅動,包含了許多兼容性的代碼,首先,類似 u8x8_d_xxx.c 命名的檔案中包含 U8x8 的驅動兼容,檔案名包括驅動的型號和螢屏解析度,因此需要洗掉無用的驅動檔案,只保留當前設備的驅動,例如,本次使用的是 128x64 的 SSD1306 螢屏,那么只需要保留 u8x8_d_ssd1306_128x64_noname.c 檔案,洗掉其它類似的檔案即可,U8g2 支持的所有螢屏驅動可以在 https://github.com/olikraus/u8g2/wiki/u8g2setupc 找到,

同時還需要精簡 u8g2_d_setup.cu8g2_d_memory.c 中 U8g2 提供的驅動兼容,

u8g2_d_setup.c 中,只需要保留 u8g2_Setup_ssd1306_i2c_128x64_noname_f() 這一個函式即可,注意,該檔案內有幾個命名類似的函式:命名中無 i2c 的是 SPI 介面驅動的函式,需要根據介面選擇;以 1 結尾的函式代表使用的快取空間為 128 位元組,以 2 結尾的函式代表使用的快取為 256位元組,類似以 f 結尾的函式代表使用的快取為 1024 位元組,

u8g2_d_memory.c 檔案也是同理,它需要根據 u8g2_d_setup.c 中的呼叫情況決定用到哪些函式,由于 u8g2_Setup_ssd1306_i2c_128x64_noname_f() 函式只用到 u8g2_m_16_8_f() 這一個函式,因此只需要保留它,其余函式全部洗掉即可,

還有一處必要的精簡是字體檔案 u8x8_fonts.cu8g2_fonts.c ,尤其是 u8g2_fonts.c ,該檔案提供了包括漢字在內的幾萬個文字的多種字體,僅源檔案就有 30MB ,編譯后占據的記憶體非常大,

字體型別的變數非常多,建議先復制一個備份后將所有變數洗掉,之后視情況再添加字體,字體變數的命名大致遵循以下規則:

<prefix> '_' <name> '_' <purpose> <charset>

其中:

  • <prefix> 前綴基本上以 u8g2 開頭;
  • <name> 字體名,其中可能包含字符大小
  • 各種 <purpose> 含義如下表所示:
名稱 描述
t 透明字體形式
h 所有字符等高
m monospace 字體(等寬字體)
8 每一個字符都是 8x8 大小的
  • <charset> 是字體支持的字符集,如下表所示:
名稱 描述
f 只包含單位元組字符
r 只包含 ASCII 范圍為 32~127 的字符
u 只包含 ASCII 范圍為 32~95 的字符,即不包括小寫英文
n 只包含數字及一些特殊用途字符
... 還包括許多自定義的字符集,例如有一些結尾帶 gb2312 或 Chinese 的字體名就包括中文

一般建議只保留需要的字體即可,

添加回呼函式

U8g2 已經包含了 SSD1306 的驅動,只需要添加一個函式 u8x8_gpio_and_delay() 用于模擬時序即可,官方檔案給出了一個函式的撰寫模板為:

uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
    switch (msg) {
        case U8X8_MSG_GPIO_AND_DELAY_INIT:  // called once during init phase of u8g2/u8x8
            break;                          // can be used to setup pins
        case U8X8_MSG_DELAY_NANO:           // delay arg_int * 1 nano second
            break;  
        case U8X8_MSG_DELAY_100NANO:        // delay arg_int * 100 nano seconds
            break;
        /* and many other cases */
        case U8X8_MSG_GPIO_MENU_HOME:
            u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
            break;
        default:
            u8x8_SetGPIOResult(u8x8, 1);     // default return value
            break;
    }
    return 1;
}

以下是一個寫法示例:

uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
    switch (msg) {
        case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
            __NOP();
            break;
        case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
            for (uint16_t n = 0; n < 320; n++)
                __NOP();
            break;
        case U8X8_MSG_DELAY_MILLI:   // delay arg_int * 1 milli second
            delay_ms(1);
            break;
        case U8X8_MSG_DELAY_I2C:     // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
            delay_us(5);
            break;                    // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
        case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
            arg_int ? GPIO_SetBits(GPIO_B, GPIO_Pin_6) : GPIO_ResetBits(GPIO_B, GPIO_Pin_6);  
            break;                    // arg_int=1: Input dir with pullup high for I2C clock pin
        case U8X8_MSG_GPIO_I2C_DATA:  // arg_int=0: Output low at I2C data pin
            arg_int ? GPIO_SetBits(GPIO_B, GPIO_Pin_7) : GPIO_ResetBits(GPIO_B, GPIO_Pin_7);  
            break;                    // arg_int=1: Input dir with pullup high for I2C data pin
        case U8X8_MSG_GPIO_MENU_SELECT:
            u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_NEXT:
            u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_PREV:
            u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_HOME:
            u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
            break;
        default:
            u8x8_SetGPIOResult(u8x8, 1); // default return value
            break;
    }
    return 1;
}

如果使用的引腳不是 PB6 和 PB7 ,注意在對應的位置修改;如果是使用硬體 I2C 的方式,那么可以不需要模擬時序,但是需要撰寫硬體驅動函式,在結尾處,會給出一個基于標準庫的硬體移植方法,

最后,不要忘記了初始化 I2C 對應的 GPIO 引腳,

U8g2簡單使用

U8g2 的初始化可以參考如下步驟:

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 結構體
    u8g2_InitDisplay(u8g2);      // 根據所選的芯片進行初始化作業,初始化完成后,顯示幕處于關閉狀態
    u8g2_SetPowerSave(u8g2, 0);  // 打開顯示幕
    u8g2_ClearBuffer(u8g2);
}

這里需要呼叫之前保留的 u8g2_Setup_ssd1306_128x64_noname_f() 函式,該函式的4個引數,其含義為:

  • u8g2 :需要配置的 U8g2 結構體
  • rotation :配置螢屏是否要旋轉,默認使用 U8G2_R0 即可
  • byte_cb :傳輸位元組的方式,這里使用軟體 I2C 驅動,因此使用 U8g2 原始碼提供的 u8x8_byte_sw_i2c() 函式,如果是硬體 I2C 的話,可以參照撰寫自己的函式
  • gpio_and_delay_cb :提供給軟體模擬 I2C 的 GPIO 輸出和延時,使用之前撰寫的配置函式 u8x8_gpio_and_delay()

如果需要顯示字串,需要提前呼叫以下函式設定字體:

void u8g2_SetFont(u8g2_t *u8g2, const uint8_t *font);

U8g2 的繪制方式有 2 種,每種都有不同的特點,

首先是全屏快取模式(Full screen buffer mode),它的特點是繪制速度快,并且所有的繪制方法都可以使用,但是這種模式需要大量的 RAM 空間,因此使用需要用到快取為 1024 位元組的初始化函式(函式名以 f 結尾),

這種繪圖的方式首先需要清除緩沖區,呼叫繪圖 API 后繪制的內容會保留在快取內,需要手動發送快取的內容到螢屏上:

u8g2_t u8g2;
u8g2_ClearBuffer(&u8g2);
/* Draw Something */
u8g2_SendBuffer(&u8g2);

第二種是分頁模式(Page mode),它同樣可以使用所有的繪制方法,但繪制速度較慢,不過占用的 RAM 空間也少,可以使用 128 或 256 位元組的快取(函式名以 1 和 2 結尾),

這種繪圖的方式首先創建第一頁,然后在一個 do...while 回圈內部繪制圖形,不斷判斷是否到達下一頁,如果到達了就自動重繪快取:

u8g2_FirstPage(&u8g2);
do {
    /* Draw Something */
} while (u8g2_NextPage(&u8g2));

可以認為分頁模式是一塊一塊繪制的,

還可以使用 U8x8 的繪圖模式,這種情況下需要使用 U8x8 提供的結構體以及一系列函式,這里不再說明,

繪圖API

完整的 API 參考可以參見官方檔案 https://github.com/olikraus/u8g2/wiki/u8g2reference/ ,里面不僅有 API 的介紹,還有繪制效果的圖片演示,

U8g2 的坐標系和絕大多數 GUI 庫一樣,原點在左上角,(x, y) 往右下遞增,坐標的單位為像素,

簡單圖形繪制

void u8g2_DrawPixel(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y);
void u8g2_DrawHLine(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t len);
void u8g2_DrawVLine(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t len);
void u8g2_DrawLine(u8g2_t *u8g2, u8g2_uint_t x1, u8g2_uint_t y1, u8g2_uint_t x2, u8g2_uint_t y2);

分別用于繪制像素點、根據左上角頂點 (x, y) 與長度 len 繪制水平線與垂直線,以及繪制兩點之間的線段,

void u8g2_DrawFrame(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h);
void u8g2_DrawBox(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h);

根據左上角的 (x, y) 坐標與寬 wh 繪制空心與實心矩形,

void u8g2_DrawRBox(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r);
void u8g2_DrawRFrame(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r);

繪制實行與空心圓角矩形,多了一個引數圓角半徑 r

void u8g2_DrawCircle(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t option);
void u8g2_DrawDisc(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t option);

根據圓心 (x0, y0) 繪制直徑為 rad ×2+1 的空心圓和實心圓,

option 為圓的部分選項,此引數可控制繪制圓弧:

取值 結果
U8G_DRAW_ALL 整個圓弧
U8G2_DRAW_UPPER_RIGHT 右上部分的圓弧
U8G2_DRAW_UPPER_LEFT 左上部分的圓弧
U8G2_DRAW_LOWER_LEFT 左下部分的圓弧
U8G2_DRAW_LOWER_RIGHT 右下部分的圓弧

還可以使用按位或運算子 | 連接幾個部分,

void u8g2_DrawEllipse(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rx, u8g2_uint_t ry, uint8_t option);
void u8g2_DrawFilledEllipse(u8g2_t *u8g2, u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rx, u8g2_uint_t ry, uint8_t option);

根據圓心 (x0, y0) 和水平半徑 rx 、豎直半徑 ry 繪制空心和實心橢圓,

void u8g2_DrawTriangle(u8g2_t *u8g2, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);

根據三個點繪制實心三角形(空心三角形可以使用直線達到類似效果),

void u8g2_DrawXBM(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap);

在圖形左上角 (x, y) 根據寬 wh 繪制 XBM 格式的位圖,可以使用 https://tools.clz.me/image-to-bitmap-array 工具將一般圖片轉換為位圖代碼,

和 Bitmap 有關的函式還有一個:

void u8g2_SetBitmapMode(u8g2_t *u8g2, uint8_t is_transparent);

該函式用于設定 Bitmap 是否透明,

字符顯示

為了顯示字串,首先要設定字體,呼叫以下函式可以提前設定字體:

void u8g2_SetFont(u8g2_t *u8g2, const uint8_t *font);
void u8g2_SetFontMode(u8g2_t *u8g2, uint8_t is_transparent);

字體是一種特殊的位圖,因此也可以設定是否透明,所有的字體保存在 u8g2_fonts.c 源檔案中,注意在移植 U8g2 庫時曾經裁剪過該檔案,

u8g2_uint_t u8g2_DrawStr(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);

在左下角 (x, y) 處顯示字串,注意,這個方法只能繪制 ASCII 字符,如有需要顯示 Unicode 字符,需要使用以下函式:

u8g2_uint_t u8g2_DrawGlyph(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, uint16_t encoding);
u8g2_uint_t u8g2_DrawUTF8(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);

繪制 Unicode 字符和字串,U8g2 支持 16 位的 Unicode 字符集,因此 encoding 的范圍被限制在 65535 ,該函式繪制 Unicode 字串時還需要對應的字體也支持 Unicode 字符,

注意這幾個函式都有回傳值,它們回傳繪制成功的字符個數,

#define u8g2_GetAscent(u8g2)
#define u8g2_GetDescent(u8g2)

這兩個宏定義用于獲取字體基線以上和基線以下的高度,上文提到的顯示字串的函式實際上引數 y 指的是基線高度,此外注意基線以下的高度回傳的是負值,

u8g2_uint_t u8g2_GetStrWidth(u8g2_t *u8g2, const char *s);
u8g2_uint_t u8g2_GetUTF8Width(u8g2_t *u8g2, const char *str);

獲取當前字體下,字串和 UTF-8 字串的寬度,單位為像素,

void u8g2_SetFontDirection(u8g2_t *u8g2, uint8_t dir);

設定文字朝向,根據引數不同分別設定為正常朝向的順時針旋轉 dir ×90° ,

其它繪圖相關API

void u8g2_SetClipWindow(u8g2_t *u8g2, u8g2_uint_t clip_x0, u8g2_uint_t clip_y0, u8g2_uint_t clip_x1, u8g2_uint_t clip_y1);

設定采集視窗大小,設定后繪制的圖形只在該視窗范圍內顯示,設定后可以使用 u8g2_SetMaxClipWindow() 函式去掉該限制,

示例代碼

以下官方示例代碼可以在 OLED 上顯示該庫的 logo :

u8g2_t u8g2;
u8g2_FirstPage(&u8g2);
do {
    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");
} while (u8g2_NextPage(&u8g2));

首發于:http://frozencandles.fun/archives/301

附錄:使用硬體I2C移植U8g2

硬體 I2C 效率上比軟體 I2C 快了非常多,因此特別適合 U8g2 這種大型 UI 框架,下面基于標準庫介紹硬體 I2C 的移植方式,

如果使用硬體 I2C ,需要在呼叫該函式(或類似函式)時,使用自己的硬體讀寫函式:

void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb);

首先還是需要撰寫一個 gpio_and_delay() 回呼函式,不過由于這里是使用硬體 I2C ,因此不再需要提供 GPIO 和時序操作的支持,只需要提供一個毫秒級的延時即可:

uint8_t u8x8_gpio_and_delay_hw(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
    switch (msg) {
        case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds
            break;
        case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
            break;
        case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
            Delay_ms(1);
            break;
        case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
            break;                    // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
        case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin
            break;                    // arg_int=1: Input dir with pullup high for I2C clock pin
        case U8X8_MSG_GPIO_I2C_DATA:  // arg_int=0: Output low at I2C data pin
            break;                    // arg_int=1: Input dir with pullup high for I2C data pin
        case U8X8_MSG_GPIO_MENU_SELECT:
            u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_NEXT:
            u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_PREV:
            u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);
            break;
        case U8X8_MSG_GPIO_MENU_HOME:
            u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);
            break;
        default:
            u8x8_SetGPIOResult(u8x8, 1); // default return value
            break;
    }
    return 1;
}

如果是使用硬體 I2C ,那么需要自行撰寫硬體驅動函式,向 OLED 寫入位元組,這個函式的撰寫可以參考官方提供的軟體驅動函式 u8x8_byte_sw_i2c() ,一個撰寫示例為:

uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
    uint8_t* data = https://www.cnblogs.com/frozencandles/p/(uint8_t*) arg_ptr;
    switch(msg) {
        case U8X8_MSG_BYTE_SEND:
            while( arg_int-- > 0 ) {
                I2C_SendData(I2C1, *data++);
                while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) 
                    continue;
            }
            break;
        case U8X8_MSG_BYTE_INIT:
        /* add your custom code to init i2c subsystem */
            RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
            I2C_InitTypeDef I2C_InitStructure = {
                .I2C_Mode = I2C_Mode_I2C,
                .I2C_DutyCycle = I2C_DutyCycle_2,
                .I2C_OwnAddress1 = 0x10,
                .I2C_Ack = I2C_Ack_Enable,
                .I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit,
                .I2C_ClockSpeed = 400000
            };
            I2C_Init(I2C1, &I2C_InitStructure);
            I2C_Cmd(I2C1, ENABLE);  
            break;
        case U8X8_MSG_BYTE_SET_DC:
        /* ignored for i2c */
            break;
        case U8X8_MSG_BYTE_START_TRANSFER:
            while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
            I2C_GenerateSTART(I2C1, ENABLE);
            while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
                continue;
            I2C_Send7bitAddress(I2C1, 0x78, I2C_Direction_Transmitter);
            while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
                continue;
            break;
        case U8X8_MSG_BYTE_END_TRANSFER:
            I2C_GenerateSTOP(I2C1, ENABLE);
            break;
        default:
            return 0;
    }
    return 1;
}

從各個 case 標簽可以很明白地看出一個 I2C 的讀寫程序:U8X8_MSG_BYTE_INIT 標簽下需要初始化 I2C 外設,U8X8_MSG_BYTE_START_TRANSFER 標簽產生起始信號并發出目標地址,U8X8_MSG_BYTE_SEND 標簽開始發送位元組,并且發送的位元組存盤在 *arg_ptr 引數中,arg_int 是位元組的總長度( U8g2 庫似乎一次不會傳輸多余 32 位元組的資訊),最后,U8X8_MSG_BYTE_END_TRANSFER 標簽處產生停止信號,

注意在使用硬體 I2C 時,GPIO 需要設定為復用開漏輸出模式 GPIO_Mode_AF_OD

最后一步,用以上撰寫的硬體函式初始化 U8g2 驅動:

u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8x8_gpio_and_delay_hw);

硬體移植程序完畢,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/492403.html

標籤:嵌入式

上一篇:Termux安裝完整版Linux(Ubuntu)詳細步驟

下一篇:VMware 虛擬機安裝CentOS鏡像詳細步驟

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more