U8g2图形库使用技巧记录
Posted TL_gone
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了U8g2图形库使用技巧记录相关的知识,希望对你有一定的参考价值。
~~呆萌的瓦力平衡机器人~~的显示UI我希望做得精致一些 所以寻觅了好久,最终寻来了U8g2这款精巧的图形库,这款ui图形库可以算得上是图形库里面的瑞士小军🔪,深得吾爱,所以该系列的文章既是我筛选图形库过程的心路历程,也是在选中U8g2这款图形库后的一些开发记录;
U8g2图形库简介:
1.U8g2 是用于嵌入式设备的单色图形库。
- 支持显示控制器:SSD1305、SSD1306、SSD1309、SSD1316、SSD1322、SSD1325、SSD1327、SSD1329、SSD1606、 SSD1607、 SH1106、SH1107、SH1108、SH1122、T6963、RA8835、LC7981、PCD8544、PCF8812、HX1230、UC1601、UC1604、UC1608、UC1610、 UC1611, UC1617, UC1701, ST7511, ST7528, ST7565, ST7567, ST7571, ST7586, ST7588, ST75256, ST75320, NT7 534, ST7920, IST3020, IST7920, LD7032, KS0108, KS0713, SED1520, SBN1661, IL3820, MAX7219 等。
2.U8g2 还包括 U8x8 库。U8g2 和 U8x8 的功能包括:
- U8g2
- 包括所有图形程序(线/框/圆画)。
- 支持很丰富的字体库。
- 需要微控制器中的一些内存来渲染显示屏(需要消耗较多的ram空间资源)。
- U8x8
- 仅文本输出(字符)设备。
- 仅允许使用每个字符固定大小(8x8 像素)的字体。
- 直接写到显示屏上,无需微控制器中的缓冲(需要消耗较少的ram空间资源)。
U8g2图形库使用技巧(硬件驱动接口部分的分析和选择):
U8g2图形库的驱动接口主要取决于所选用的lcd屏幕的驱动芯片方案,目前常用的驱动接口多为spi和i2c两种串行总线,如果需要较高的刷新帧率,spi的驱动方式是比较好的选择,spi的驱动时钟频率一般可以达到8Mbit,而i2c的方式一般只能达到400Kbit,但是使用spi方式驱动的时候,需要比较多的io管脚资源,一般最少需要3个io(三线spi方式),而i2c方式一般只需要2个io就可以满足,考虑到~~呆萌的瓦力平衡机器人~~包含了两路对时序要求很高的foc电机控制,所以果断选择了比较高效的spi方式(选用的oled模组的驱动方案采用的是SSD1306,该方案既支持i2c方式,也支持spi的方式);
U8g2图形库使用技巧(软件驱动程序分析和记录):
U8g2图形库的代码框架写得非常的完善,只需要实现底层几个读写接口函数,就可以把整个图形库驱动起来,它的几乎所有的初始化函数接口都定义在u8g2_d_setup.c源文件中,该源文件实现了u8g2图形库所有支持lcd驱动方案的初始化方法,通过函数名来区分,初始化函数的名字包含了很多硬件相关的信息,就拿我在使用的oled模组的初始化函数来作为例子,模组驱动方案为SSD1306,分辨率为128x64,驱动接口为三线spi方式,所以可选的初始化方法如下:
u8g2_Setup_ssd1306_128x64_noname_1 /*芯片SSD1306,分辨率128x64,128字节页大小*/
u8g2_Setup_ssd1306_128x64_noname_2 /*芯片SSD1306,分辨率128x64,256字节页大小*/
u8g2_Setup_ssd1306_128x64_noname_f /*芯片SSD1306,分辨率128x64,1024字节页大小*/
考虑到esp32的ram资源比较充足的,果断选择了u8g2_Setup_ssd1306_128x64_noname_f方法,到这里了就结束了!spi的驱动方式体现在哪里呢?
接着往里扒--->
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)
该函数需要传入四个参数--->
u8g2_t *u8g2; /*u8g2图形库结构体指针*/
const u8g2_cb_t *rotation; /*u8g2图形库ui旋转:U8G2_R0(无旋转),U8G2_R1(顺时针90°),U8G2_R2(顺时针180°)......*/
u8x8_msg_cb byte_cb; /*u8g2图形库字节交互回调函数*/
u8x8_msg_cb gpio_and_delay_cb; /*u8g2图形库gpio和延时回调函数*/
所以和spi部分相关的就是--->
u8x8_msg_cb byte_cb; /*u8g2图形库字节交互回调函数*/
这个callback函数需要我们自己实现如下的方法--->
uint8_t u8x8_byte_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
uint8_t max_transfer_sz = 0;
switch(msg)
{
case U8X8_MSG_BYTE_SEND:
//ESP_LOGI(TAG, "U8X8_MSG_BYTE_SEND:%d,%d", arg_int ,spi_device_max_transfer_sz(u8g2_spi));
max_transfer_sz = spi_device_max_transfer_sz(u8g2_spi);
if (arg_int <= max_transfer_sz) {
u8g2_tran.length = arg_int*8,
u8g2_tran.tx_buffer = (const void *)arg_ptr,
u8g2_tran.rxlength = 0,
u8g2_tran.rx_buffer = NULL,
spi_device_polling_transmit(u8g2_spi, &u8g2_tran);
} else {
int packet_num = arg_int/max_transfer_sz;
int packet_remain = arg_int%max_transfer_sz;
uint8_t *data_prt = (uint8_t *)arg_ptr;
if (packet_num > 0) {
for (int i=0;i<packet_num;i++) {
u8g2_tran.length = max_transfer_sz*8,
u8g2_tran.tx_buffer = (const void *)data_prt,
u8g2_tran.rxlength = 0,
u8g2_tran.rx_buffer = NULL,
spi_device_polling_transmit(u8g2_spi, &u8g2_tran);
data_prt += max_transfer_sz;
}
}
if (packet_remain > 0) {
u8g2_tran.length = packet_remain*8,
u8g2_tran.tx_buffer = (const void *)data_prt,
u8g2_tran.rxlength = 0,
u8g2_tran.rx_buffer = NULL,
spi_device_polling_transmit(u8g2_spi, &u8g2_tran);
}
}
break;
case U8X8_MSG_BYTE_SET_DC:
u8g2_ssd1306_set_dc(arg_int);
break;
default:
return 0;
}
return 1;
}
而剩下的u8x8_msg_cb gpio_and_delay_cb这个callback主要是用来做输入相关功能的回调接口(瓦力ui配合rc遥控器实现的输入交互就是通过该接口实现的),u8g2库可以通过该接口来读取硬件按键输入的状态信息然后进行处理--->
uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, U8X8_UNUSED void *arg_ptr)
{
case U8X8_MSG_GPIO_MENU_SELECT:
u8x8->gpio_result = u8g2_gpio_menu_select();
vTaskDelay(3);
break;
case U8X8_MSG_GPIO_MENU_NEXT:
u8x8->gpio_result = u8g2_gpio_menu_next();
vTaskDelay(3);
break;
case U8X8_MSG_GPIO_MENU_PREV:
u8x8->gpio_result = u8g2_gpio_menu_prev();
vTaskDelay(3);
break;
case U8X8_MSG_GPIO_MENU_HOME:
u8x8->gpio_result = u8g2_gpio_menu_home();
vTaskDelay(3);
break;
case U8X8_MSG_GPIO_MENU_UP:
u8x8->gpio_result = u8g2_gpio_menu_up();
vTaskDelay(3);
break;
case U8X8_MSG_GPIO_MENU_DOWN:
u8x8->gpio_result = u8g2_gpio_menu_down();
vTaskDelay(3);
break;
case U8X8_MSG_GPIO_RESET:
u8g2_ssd1306_set_rst(arg_int);
break;
default:
break;
}
return 1;
}
到此底层相关的工作基本做完,接下来就是一些基于上层API接口的花活了--->
void u8g2_ssd1306_init(spi_device_handle_t handle)
{
u8g2_spi = handle;
//Initialize non-SPI GPios
gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
//Reset the display
gpio_set_level(PIN_NUM_RST, 0);
vTaskDelay(100 / portTICK_RATE_MS);
gpio_set_level(PIN_NUM_RST, 1);
vTaskDelay(100 / portTICK_RATE_MS);
u8g2_Setup_ssd1306_128x64_noname_f(
&u8g2,
U8G2_R0,
u8x8_byte_hw_spi,
u8x8_gpio_and_delay); // init u8g2 structure
ESP_LOGI(TAG, "u8g2_InitDisplay");
u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
ESP_LOGI(TAG, "u8g2_SetPowerSave");
u8g2_SetPowerSave(&u8g2, 0); // wake up display
ESP_LOGI(TAG, "u8g2_ClearBuffer");
u8g2_ClearBuffer(&u8g2);
ESP_LOGI(TAG, "u8g2_SetFont");
u8g2_SetFont(&u8g2, u8g2_font_6x10_mf);
ESP_LOGI(TAG, "u8g2_DrawStr");
u8g2_DrawStr(&u8g2, 20, 14, "esp32 foc +-x%/");
ESP_LOGI(TAG, "u8g2 init all done!");
}
虽然是花活,其中也有一些需要注意的地方,例如在设置字库的时候,如果选择了一些精简字库,会导致字符刷新出现字符像素残留的现象,特别是在动态刷新一些数字的情景下,效果很差,十分影响显示效果;
小结:
这次主要记录了为~~呆萌的瓦力平衡机器人~~选择ui图像库的心路历程以及在开始使用该图形库时所需要做的一些前期分析和实操过程中需要注意的技术要点;
以下是一些和本文相关的文章链接和U8g2的wiki链接:
一、~~呆萌的瓦力平衡机器人~~链接:
二、U8g2 wiki链接:
1.U8g2_wiki;
后续的计划:
下一期准备记录在做U8g2界面刷新和两路foc电机驱动遇到的冲突问题,以及我的解决思路;
大家如果也感兴趣,可以加qun交流学习(群里提供了丰富的esp32资料):
燥起来吧,sao年!!!!!!
以上是关于U8g2图形库使用技巧记录的主要内容,如果未能解决你的问题,请参考以下文章
开源项目介绍STC32基于u8g2库DMA驱动IIC or SPI OLED屏幕显示
基于Lua框架下Air103使用U8g2库驱动I2C OLED图像显示
玩转 ESP32 + Arduino (八) U8G2驱动OLED