关于0.96吋OLED的详细操作,可查看一文彻底了解SSD1306驱动0.96寸OLED,本文移植u8g2图形库来驱动0.96吋OLED。
u8g2是单色显示库的第二个版本。支持lcd和oled,支持众多驱动芯片,比如SSD1305, SSD1306, SSD1309, SSD1312, SSD1316, SSD1320, SSD1322, SSD1325, SSD1327, SSD1329, SSD1606, SSD1607, SH1106, SH1107, SH1108, SH1122, T6963, RA8835, LC7981, PCD8544, PCF8812, HX1230, UC1601, UC1604, UC1608, UC1610, UC1611, UC1617, UC1638, UC1701, ST7511, ST7528, ST7565, ST7567, ST7571, ST7586, ST7588, ST75256, ST75320, NT7534, ST7920, IST3020, IST7920, LD7032, KS0108, KS0713, HD44102, T7932, SED1520, SBN1661, IL3820, MAX7219
等。
另外,u8g2是开源的,可直接下载代码移植:https://github.com/olikraus/u8g2。
(1)直接从github下载代码,u8g2支持c/c++ ,cppsrc是c++的,csrc文件夹下面是c的源码,在单片机上移植就只需关注csrc里面的文件:
(2)u8g2里面支持多种驱动芯片,以u8x8_d_xxx.c
命名的就是驱动文件,本文使用的是0.96吋oled,芯片是ssd1306,因此只需将u8x8_d_ssd1312_128x64_noname.c
这个驱动文件添加到工程中:
(3)修改"u8g2_d_setup.c
"这个文件,里面有各种驱动芯片的初始化函数,删除其他函数,只留下与使用的驱动芯片相关的函数。
本文使用的ssd1306,但是与ssd1306相关的有多个函数,例如:
u8g2_Setup_ssd1306_128x64_noname_1
、
u8g2_Setup_ssd1306_128x64_noname_2
、
u8g2_Setup_ssd1306_128x64_noname_f
,
这些都是spi接口的;
u8g2_Setup_ssd1306_i2c_128x64_noname_1
、
u8g2_Setup_ssd1306_i2c_128x64_noname_2
、
u8g2_Setup_ssd1306_i2c_128x64_noname_f
,
这些都是i2c接口的;
后缀1、2、f代表缓冲区大小的不同:
1代表128字节,
2代表256字节,
f代表1024字节;
根据单片机空间的大小选择合适的接口,缓冲区小的,刷新lcd/oled的时候就比较耗时,反之。
本文使用u8g2_Setup_ssd1306_i2c_128x64_noname_f
这个接口:
(4)修改“u8g2_d_memory.c”文件,这个文件里面其实就是“u8g2_d_setup.c”文件对应的缓冲区,同上面一样,屏蔽掉没用到的,留下用到的:
(5)关于字库
“u8g2_fonts.c
”文件中定义了各种字库,这些字库比较占用空间,根据使用情况屏蔽掉没有使用的。
(6)两个回调函数
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)
;
参数byte_cb
和gpio_and_delay_cb
是需要编写的两个回调函数。
byte_cb
:是通信相关的函数,比如i2c写数据,
gpio_and_delay_cb
:是延时相关的函数。
关于回调函数的写法,官方也给出了参考例子:https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform
通信函数分为硬件接口和软件模拟方式,软件模拟方式官方基本写好了,只需要简单的指定io口即可。
(7)软件模拟i2c接口
通信函数:直接使用官方的这个u8x8_byte_sw_i2c
uint8_t u8x8_byte_sw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
uint8_t *data;
switch(msg)
{
case U8X8_MSG_BYTE_SEND:
data = (uint8_t *)arg_ptr;
while( arg_int > 0 )
{
i2c_write_byte(u8x8, *data);
data++;
arg_int--;
}
break;
case U8X8_MSG_BYTE_INIT:
i2c_init(u8x8);
break;
case U8X8_MSG_BYTE_SET_DC:
break;
case U8X8_MSG_BYTE_START_TRANSFER:
i2c_start(u8x8);
i2c_write_byte(u8x8, u8x8_GetI2CAddress(u8x8));
//i2c_write_byte(u8x8, 0x078);
break;
case U8X8_MSG_BYTE_END_TRANSFER:
i2c_stop(u8x8);
break;
default:
return 0;
}
return 1;
}
延时函数:在这个函数里面,根据传递的参数,拉低或者拉高SCL以及SDA。
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
if(arg_int == 1)
{
gpio_bits_set(SCL_PORT,SCL_PIN);//SCL=1
}
else if(arg_int == 0)
{
gpio_bits_reset(SCL_PORT,SCL_PIN);//SCL=0
}
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
if(arg_int == 1)
{
gpio_bits_set(SDA_PORT,SDA_PIN); //SDA=1
}
else if(arg_int == 0)
{
gpio_bits_reset(SDA_PORT,SDA_PIN); //SDA=0
}
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;
}
(8)硬件i2c接口 通讯函数:
uint8_t u8x8_byte_at32f425_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
static uint8_t buffer[32]; /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and 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:
/* add your custom code to init i2c subsystem */
break;
case U8X8_MSG_BYTE_START_TRANSFER:
buf_idx = 0;
break;
case U8X8_MSG_BYTE_END_TRANSFER:
HW_I2cWrite(buffer,buf_idx);
break;
default:
return 0;
}
return 1;
}
这里面主要的函数接口是HW_I2cWrite
,具体实现如下:
i2c写一个缓存区数据,缓冲区为buf,数据长度为len
void HW_I2cWrite(uint8_t *buf,uint8_t len)
{
if(len<=0)
return ;
/* wait for the busy falg to be reset */
while(i2c_flag_get(I2C1, I2C_BUSYF_FLAG) );
/* start transfer */
i2c_transmit_set(I2C1, I2C_SLAVE_ADDRESS7, len, I2C_SOFT_STOP_MODE, I2C_GEN_START_WRITE);
i2c_start_generate(I2C1);
while(i2c_flag_get(I2C1, I2C_ADDRF_FLAG) );
for(uint8_t i=0;i<len;i++)
{
while(!i2c_flag_get(I2C1, I2C_TDIS_FLAG) );
i2c_data_send(I2C1, buf[i]);
}
i2c_stop_generate(I2C1);
while(!i2c_flag_get(I2C1, I2C_STOPF_FLAG) );
i2c_flag_clear(I2C1, I2C_STOPF_FLAG);
}
延时函数:
uint8_t u8g2_gpio_and_delay_at32f425(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:
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; // command processed successfully.
}
代码中使用了两种方式来测试,可选择软件或硬件i2c方式。
static u8g2_t u8g2;
void U8g2Init(void)
{
#if 0
SW_I2cInit();
u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0,u8x8_byte_sw_i2c,u8x8_gpio_and_delay);
#else
HW_I2cInit();
u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0,u8x8_byte_at32f425_hw_i2c,u8g2_gpio_and_delay_at32f425);
#endif
u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
u8g2_SetPowerSave(&u8g2, 0); // wake up display
u8g2_ClearBuffer(&u8g2);
DrawLogo(&u8g2);
delay_ms(500);
delay_ms(500);
}
void U8g2Test(void)
{
DrawProcess(&u8g2);
DrawPoint(&u8g2);
DrawLine(&u8g2);
DrawBox(&u8g2);
DrawCircle(&u8g2);
DrawEllipse(&u8g2);
}