U8g2 porting
這裏所謂的 porting,單是指讓 U8g2 可用在其他尚未被支援的 MCU 上。易明,就是與該 MCU 硬體相依的部份還未被支援到,即,interfaces 的部份。目前筆者自己已知的 interfaces 用在顯示上,就是 UART,IIC,SPI,及 MCU(gpios/6800/8080),其皆適用於 MCU 等級以處理小呎吋顯示部份的資料傳輸。其他相依的部份還有 timing,delay,及 GPIOs 等。
如何調整 timing,如何做 delay,如何控制 GPIO,如何驅動與存取 interfaces,方式/函式皆因不同的 MCU 而異(指的是那些還未被 arduino 歸一化/支援的 MCUs)。
因此 U8g2 針對上述問題,有一篇導引。本文針對該篇導引再作個實行上的簡單導引。並且僅以 ESP8266,的 software iic 為例(不過它已被支援了)。
使用 U8g2
- 首先確認 display 的驅動 ic 型號是否被支援,若沒有,可以自己做 porting,但這不在本文的探討之內。
- 支援清單
- 筆者的 0.91 吋 OLED display 128×32 的驅動 ic 是 ssd1306。可查一下支援清單。
- 查到了以下這行定義,關於命名規則再請找一下相關資料。
- U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C(rotation, clock, data [, reset]) [full framebuffer, size = 512 bytes]
- 於 arduino sketch 中直接宣告這行,具現化出 u8g2_try 物件並代入參數,成如下,
- U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2_try(U8G2_R0, OLED_SCL, OLED_SDA , OLED_RST);
- 其中,U8G2_R0 是 u8g2 定義的常數,指的是正常顯示,不旋轉角度。
- OLED_SCL, OLED_SDA 是自定義常數,看使用哪兩支 GPIOs 作為 scl and sda pins。
- OLED_RST 筆者之前敍述過,此處不探討。即,不加此參數或指定某 pin 請自行嘗試。
- 接著,單次執行 u8g2_try.begin();
- 再來就可以作顯示了。列印出一行字串,如下用法:
u8g2_try.clearBuffer();
u8g2_try.setFont(u8g2_font_8x13B_mf);
u8g2_try.drawStr(x_pos1, y_pos1, str1);
u8g2_try.sendBuffer();
- 可以不 clearBuffer,則在 buffer frame 上面依指定位置覆寫,沒被覆寫到的仍會被顯示出來。
- 關於支援的 font 可參考此處。
移植
- U8g2 有完整的一份函式庫(for 所有支援平台,最新版),及只用在 arduino 平台上而精簡過的另一份函式庫(稍舊,穩定版),當然我們使用的是後者。
- 前面提到的 U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C() 這個定義,我們可在源碼中的 ./src/ 中的 U8g2lib.h 中找到。我們看一下定義內容,
class U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C : public U8G2 {
public: U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C(const u8g2_cb_t *rotation, uint8_t clock, uint8_t data, uint8_t reset = U8X8_PIN_NONE) : U8G2() {
u8g2_Setup_ssd1306_i2c_128x32_univision_f(&u8g2, rotation, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_SW_I2C(getU8x8(), clock, data, reset);
}
};
- 其中看到,此類別繼承了 U8G2 類別,並將腳位資訊傳給相關函式,並也設定了兩個回呼函式,u8x8_byte_arduino_sw_i2c,u8x8_gpio_and_delay_arduino
- 這兩個函式正是我們要修改/移植的地方。其中,第二支 gpio and delay 的函式我們可以忽略,或是說,若真換成某顆 MCU,我們再比照做法細追入該函式以相對應修改取代之。於此,我們是使用 ESP8266,其已被支援。故只看第一支函式。
- 再來,我們並不會去動到源碼,故,最簡便的方式就是在我們的 sketch code 中比照新增一個䫶別。且命名不衝突便成。
class U8G2_SSD1306_128X32_UNIVISION_F_customize_I2C : public U8G2 {
public: U8G2_SSD1306_128X32_UNIVISION_F_customize_I2C(const u8g2_cb_t *rotation, uint8_t clock, uint8_t data, uint8_t reset = U8X8_PIN_NONE) : U8G2() {
u8g2_Setup_ssd1306_i2c_128x32_univision_f(&u8g2, rotation, u8x8_byte_arduino_customize_i2c, u8x8_gpio_and_delay_arduino);
u8x8_SetPin_SW_I2C(getU8x8(), clock, data, reset);
}
};
- 請比較此兩段程式碼的差異,只有將字串 SW 換成 customize,及 sw 換成 customize
- 因此,我們在具現化時,就是使用這支新類別來宣告
- 最後,就剩須於 sketch code 中定義出回呼函式了 u8x8_byte_arduino_customize_i2c(),
- 我們直接把 u8g2 提供的範例貼出來,就以它來取代,函式如下:
#include <Wire.h>
uint8_t u8x8_byte_arduino_customize_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
switch(msg)
{
case U8X8_MSG_BYTE_SEND:
Wire.write((uint8_t *)arg_ptr, (int)arg_int);
break;
case U8X8_MSG_BYTE_INIT:
Wire.begin(OLED_SDA, OLED_SCL);
break;
case U8X8_MSG_BYTE_SET_DC:
break;
case U8X8_MSG_BYTE_START_TRANSFER:
if ( u8x8->display_info->i2c_bus_clock_100kHz >= 4 )
{
Wire.setClock(400000L);
}
Wire.beginTransmission(u8x8_GetI2CAddress(u8x8)>>1);
break;
case U8X8_MSG_BYTE_END_TRANSFER:
Wire.endTransmission();
break;
default:
return 0;
}
return 1;
}
- 留意這行的用法,Wire.begin(OLED_SDA, OLED_SCL); 參數為我們自定義的腳位
- 因此,當我們的 iic 模組,例如說 esp8266 未來改版成具有硬體 iic 模組,那麼 iic 的存取方式或有不同或尚未被 arduino 支援;又或現下我們是以 esp8266 去控制某顆 iic hub/router,才間接存取到 display,那麼就是從此函式中去做處理了。
補充說明
- 我們可經由此範例中觀察出對於 display driver 只需做寫入 write 的動作。
- 又因範例將 wire.h 的 write 用法切分開來。因此我們可藉以在各個 case 中埋入 print,並再搭配 logic analyzer,以觀察出 wire.h 乃至於對於 display driver 的行為。
- 故得到,(筆者未完全追蹤,只有追部份,故以下另部份是推論,此僅供參考)
- Wire.beginTransmission 用以傳送出 address,僅此而已。
- 若干次 Wire.write 將會先置入 Wire.buffer,除非 buffer 已滿才會即時寫出。
- 若 buffer 未滿,且遇到 Wire.endTransmission(的這個時間點),才會 flush data 全部寫出。