上次写了一篇u8g2的移植,使用的是软件模拟I2C来驱动OLED屏幕。今天看论坛有小伙伴使用硬件I2C移植u8g2,没驱动起来,于是自己来试试。
不得不称赞一下u8g2的库,设计的非常好,将OLED显示与底层驱动解耦的很彻底,无论底层的硬件是何种芯片,如果使用硬件i2c驱动oled,只需要在软件层面实现i2c初始化、i2c数据传输,和系统延时这样三个基本功能即可。这样就可以将注意力集中到OLED屏幕图像开发上来,并且写好的显示内容,可以在不同的MCU上使用,同时也可以在不同主控的OLED上使用了。
看图可知,MAX78000的硬件I2C管脚是P0_16-SCL , P0_17-SDA。将OLED屏幕管脚用杜邦线接到这两个GPIO上。接下来进行代码移植。
移植第一步:拷贝u8g2的文件,与软件模拟i2c步骤一样,将u8g2下csrc里部分文件拷贝过来,新建文件夹,这里新建文件夹名为U8g2。
第二步:修改U8g2下的u8g2_d_setup.c和u8g2_d_memory.c两个文件。这里就不再赘述了。这里需要留意一下在 u8g2_d_setup.c 中,只需要保留 u8g2_Setup_ssd1306_i2c_128x64_noname_f() 这一个函数。注意,该文件内有几个命名类似的函数:命名中需要根据接口选择;以 1 结尾的函数代表使用的缓存空间为 128 字节,以 2 结尾的函数代表使用的缓存为 256字节,类似以 f 结尾的函数代表使用的缓存为 1024 字节。按自己的硬件情况进行选择。同样地还需要修改Makefile文件,保证编译路径和搜索路径。
第三步:写回调函数。
#define I2C_MASTER MXC_I2C1 // SCL P0_16; SDA P0_17 #define I2C_SCL_PIN 16 #define I2C_SDA_PIN 17 #define I2C_FREQ 100000 // 100kHZ uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */ static uint8_t buffer[32]; static uint8_t buf_idx; uint8_t *data; mxc_i2c_req_t reqMaster; reqMaster.i2c = I2C_MASTER; reqMaster.addr = 0; reqMaster.tx_buf = NULL; reqMaster.tx_len = 0; reqMaster.rx_buf = NULL; reqMaster.rx_len = 0; reqMaster.restart = 0; reqMaster.callback = NULL; switch (msg) { case U8X8_MSG_BYTE_INIT: { /* add your custom code to init i2c subsystem */ // I2C初始化 MXC_I2C_Init(I2C_MASTER, 1, 0); MXC_I2C_SetFrequency(I2C_MASTER, I2C_FREQ); } break; case U8X8_MSG_BYTE_START_TRANSFER: { buf_idx = 0; } break; 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_END_TRANSFER: { // 数据传输 reqMaster.addr = OLED_ADDRESS; reqMaster.tx_buf = buffer; reqMaster.tx_len = buf_idx; if ((MXC_I2C_MasterTransaction(&reqMaster)) == 0) return 0; } break; case U8X8_MSG_BYTE_SET_DC: break; default: return 0; } return 1; }
回调函数中,需要完成I2C初始化,和I2C的数据传输。参考着官方例程I2C_SCAN的写法
硬件i2c初始化很简单,这里初始化i2c1硬件接口,然后设置了一个传输速率(100000)。硬件i2c的传输,需要填写的内容有地址、数据、数据长度。这些内容都是用一个结构体传输。只需要将OLED的地址填入,将需要传输的数据交给tx_buf,注明数据长度,即可调用系统函数给i2c写数据。
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 // MXC_Delay(1); break; case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds MXC_Delay(10); break; case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second MXC_Delay(1000); break; case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz MXC_Delay(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 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; }
这个基本上是一个系统延时的设置,官方提供了MXC_Delay方法,能实现微秒级的延时,用起来很方便。
void u8g2_Init(u8g2_t *u8g2) { u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8x8_gpio_and_delay); u8g2_InitDisplay(u8g2); u8g2_SetPowerSave(u8g2, 0); u8g2_ClearBuffer(u8g2); }
最后一步,写一个u8g2的初始化函数,这里留意和软件i2c驱动之间的不同。u8g2_Setup_ssd1306_i2c_128x64_noname_f这个函数中,第三个参数“u8x8_byte_hw_i2c”,就是告诉u8g2去调用硬件i2c的方式来驱动OLED屏幕。
第三步:调用u8g2方法驱动OLED屏幕显示。
/***** Includes *****/ #include <stdio.h> #include <stdint.h> #include <string.h> #include "board.h" #include "mxc_device.h" #include "mxc_delay.h" #include "nvic_table.h" #include "i2c_regs.h" #include "i2c.h" #include "u8g2.h" #include "oled.h" /***** Definitions *****/ // //#define I2C_MASTER MXC_I2C1 // SCL P0_16; SDA P0_17 //#define I2C_SCL_PIN 16 //#define I2C_SDA_PIN 17 //#define I2C_FREQ 100000 // 100kHZ /***** Globals *****/ // ***************************************************************************** int main() { u8g2_t u8g2; // a structure which will contain all the data for one display u8g2_Init(&u8g2); u8g2_SendBuffer(&u8g2); u8g2_DrawBox(&u8g2, 0, 0, 20, 20); u8g2_DrawBox(&u8g2, 20, 20, 20, 20); u8g2_SendBuffer(&u8g2); u8g2_DrawFrame(&u8g2, 10, 40, 20, 20); u8g2_SendBuffer(&u8g2); u8g2_SetFont(&u8g2, u8g2_font_DigitalDiscoThin_tf); u8g2_DrawStr(&u8g2, 30, 10, "Hello World"); u8g2_SendBuffer(&u8g2); while (1) { MXC_Delay(5000000); } }
写完驱动后,在main方法中写个简单的调用,在屏幕上画出简单的图像来。
附上源码,请各位老师指正。I2C_SCAN.zip