STM32移植U8g2图形库的方法 实现OLED图形显示

发布时间:2023-07-26  

之前的文章,介绍过ESP8266在Arduino IDE环境中使用U8g2库,实现OLED上的各种图形显示。


本篇,介绍一下U8g2库如何移植到STM32上,进行OLED的图形显示。


本次的实验硬件为:


STM32:型号为最常见的STM32F103C8T6


OLED:0.96寸OLED,IIC接口(如果是SPI接口,文中也有对应的修改介绍)


1 U8g2简介

U8g2 是一个用于嵌入式设备的单色图形库。U8g2支持单色OLED和LCD,并支持如SSD1306等多种类型的OLED驱动。


U8g2源码的开源库地址:https://github.com/olikraus/u8g2

8db8e9f99ff98eeca64190d562595dfd_pYYBAGKeIkWAbyNYAADKIaGINn0614.png

2 移植步骤

首先下载U8g2的源码,因为STM32主要是使用C语言编程,所以只需关注源码中的C源码部分,即csrc文件夹下的文件。


2.1 精简c源码

U8g2支持多种显示驱动的屏幕,因为源码中也包含了各个驱动对应的文件,为了减小整个工程的代码体积,在移植U8g2时,可以删除一些无用的文件。


2.1.1 去掉无用的驱动文件

这些驱动文件通常是u8x8_d_xxx.c,xxx包括驱动的型号和屏幕分辨率。ssd1306驱动芯片的OLED,使用u8x8_ssd1306_128x64_noname.c这个文件,其它的屏幕驱动和分辨率的文件可以删掉。

fab1844fcceb7600a0d732659d98ed39_pYYBAGKeIk6Afkk8AAEKVfzfGYc168.png

2.1.2 精简u8g2_d_setup.c

由于我的OLED是IIC接口,只留一个本次要用到的u8g2_Setup_ssd1306_i2c_128x64_noname_f就好(如果是SPI接口,需要使用u8g2_Setup_ssd1306_128x64_noname_f这个函数),其它的可以删掉或注释掉。


#include "u8g2.h"


/* ssd1306 f */

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)

{

  uint8_t tile_buf_height;

  uint8_t *buf;

  u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb);

  buf = u8g2_m_16_8_f(&tile_buf_height);

  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);

}

注意,与这个函数看起来十分相似的函数的有:


u8g2_Setup_ssd1306_128x64_noname_1


u8g2_Setup_ssd1306_128x64_noname_2


u8g2_Setup_ssd1306_128x64_noname_f


u8g2_Setup_ssd1306_i2c_128x64_noname_1


u8g2_Setup_ssd1306_i2c_128x64_noname_2


u8g2_Setup_ssd1306_i2c_128x64_noname_f


其中,前面3个,是给SPI接口的OLED用的,函数最后的数字或字母,代表显示时的buf大小:


1:128字节


2:256字节


f:1024字节


2.1.3 精简u8g2_d_memory.c

由于用到的u8g2_Setup_ssd1306_i2c_128x64_noname_f函数中,只调用了u8g2_m_16_8_f这个函数,所以留下这个函数,其它的函数一定要删掉或注释掉,否则编译时很可能会提示内存不足!!!


#include "u8g2.h"


uint8_t *u8g2_m_16_8_f(uint8_t *page_cnt)

{

  #ifdef U8G2_USE_DYNAMIC_ALLOC

  *page_cnt = 8;

  return 0;

  #else

  static uint8_t buf[1024];

  *page_cnt = 8;

  return buf;

  #endif

}

2.2 编写移植函数

精简源码之后,还需要编写如下的配置函数。


2.2.1 GPIO初始化

对OLED用到的IIC接口进行GPIO的初始化配置:


#define SCL_Pin GPIO_Pin_6

#define SDA_Pin GPIO_Pin_7

#define IIC_GPIO_Port GPIOB

void IIC_Init(void)

{     

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );

   

GPIO_InitStructure.GPIO_Pin = SCL_Pin|SDA_Pin;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(IIC_GPIO_Port, &GPIO_InitStructure);

}

如果是SPI接口,则初始化对应的SPI接口即可。


2.2.2 u8x8_gpio_and_delay

这个函数也需要自己写,主要的修改包括:


赋予U8g2相应的延时函数,比如下面的delay_ms和delay_us


为U8g2提供IIC接口的高低电平调用:


U8X8_MSG_GPIO_I2C_CLOCK:IIC的SCL


U8X8_MSG_GPIO_I2C_DATA:IIC的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_SetBits(IIC_GPIO_Port, SCL_Pin);

}

else if(arg_int == 0)

{

GPIO_ResetBits(IIC_GPIO_Port, SCL_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

        if(arg_int == 1) 

{

GPIO_SetBits(IIC_GPIO_Port, SDA_Pin);

}

else if(arg_int == 0)

{

GPIO_ResetBits(IIC_GPIO_Port, SDA_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;

}

如果是SPI接口,可以参考如下写法:


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_SPI_DATA:

            lcd_sdin((uint8_t)arg_int); //SPI - MOSI

            break;

        case U8X8_MSG_GPIO_SPI_CLOCK: //SPI - CLK

            lcd_sclk(arg_int);

            break;

        case U8X8_MSG_GPIO_AND_DELAY_INIT:

            oled_init(); //OLED初始化

            Delay(1);

            break;

        case U8X8_MSG_DELAY_MILLI:

            Delay(arg_int); //延时

            break;

        case U8X8_MSG_GPIO_CS: //SPI - CS

            lcd_cs((uint8_t)arg_int);

        case U8X8_MSG_GPIO_DC:

            lcd_dc((uint8_t)arg_int); //SPI - MISO

            break;

        case U8X8_MSG_GPIO_RESET:

            break;

    }

    return 1;

}

可以看出,对于IIC与SPI接口,只有分别进行对应的配置即可。


2.2.3 u8g2Init

U8g2的初始化,需要调用下面这个u8g2_Setup_ssd1306_128x64_noname_f函数,该函数的4个参数含义:


u8g2:传入的U8g2结构体


U8G2_R0:默认使用U8G2_R0即可(用于配置屏幕是否要旋转)


u8x8_byte_sw_i2c:使用软件IIC驱动,该函数由U8g2源码提供


u8x8_gpio_and_delay:就是上面我们写的配置函数


void u8g2Init(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);

}

2.2.4 显示测试函数

使用U8g2提供的测试函数,用于查看显示效果


void draw(u8g2_t *u8g2)

{

    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");

}

2.3 源码加入到MDK编译

在一个STM32的基础例程上进行修改。


2.3.1添加u8g2源码到工程

左侧工程目录添加U8g2源码,然后再添加U8g2的头文件搜寻目录,如下:

378409d9eb6c6e3880ca59a3fef79701_pYYBAGKeIlqAS42_AAG_sD-BLpY254.png

2.3.2 主函数

主函数中,首先是IIC的初始化和U8g2的初始化,然后就可以测试U8g2的图形显示功能了:


#include "delay.h"

#include "sys.h"

#include "u8g2.h"


int main(void)

{

delay_init();

IIC_Init();

 

    u8g2_t u8g2;

u8g2Init(&u8g2);


while(1)

{

       u8g2_FirstPage(&u8g2);

       do

       {

draw(&u8g2);

       } while (u8g2_NextPage(&u8g2));

    }

}

3 测试效果

fbab692055c62864ccac3ba11cb7cc70_pYYBAGKeImaAbklYAAES8Ep9QH8703.png

4 总结

本篇介绍了如何将U8g2图形库移植到STM32中,其中主要的修改包括:


精简源码中的u8g2_d_setup.c和u8g2_d_memory.c


OLED所用IIC接口的GPIO初始化


编写u8x8_gpio_and_delay和u8g2Init


其中,u8g2_d_memory.c文件一定要去掉无用的函数,否则编译时会提示内存不足;对于SPI接口的OLED,参考IIC接口进行类似的修改即可。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    ,要清楚,移植程序不等于学习单片机,最重要的是知道例程是怎样的框架及实现方法。初始化了哪些寄存器,做了哪些引脚配置,调用了哪些函数,那些函数又是怎么实现的,设置了哪些中断,用到了哪些片上资源(UART......
    荐: 梳理单片机学习方法、产品开发流程 。 如下图是封装好的外设驱动,有必要学习一下是怎么实现的......
    MCSPTE1AK144_PMSM_FOC_2Sh为例 如果是非计算机相关专业的人,没有接触过嵌入式开发的,要弄懂这些工程是如何运行起来的,可能需要花很长一段的时间。相比而言,MBDT就简单很多,能读懂模型就可以知道电机控制算法是怎么实现的......
    存储在内部),从而进入ISP模式.此时便可以通过串口下载程序了. 最后再来看看一键下载是怎么实现的呢? 从原理图可知:DB9串口的RTS控制BOOT0,DTR控制RST. 我们要达到下载的目的,就必......
    把明暗交界处变成硬朗的线条。 这种滤镜处理是怎么实现的呢?时光相册的“新海诚风”滤镜和之前 Prisma 这款免费应用软件有相似之处。 Prisma 与常见的照片美化工具的不同在于,它不......
    成功后,将 /lib下的库文件移植到目标板上的 /lib目录下,这样就完成了图形库Qte及触摸屏支持的配置和移植。 3 、Qte平台上用户应用程序模块的实现 3.1 本系统应用程序模块分析 本系......
      MOV TL0,#9FH ;重置定时常数   POP PSW   POP ACC   RETI   END   先自己分析一下,看看是怎么实现的?这里采用了软件计数器的概念,思路是这样的,先用......
    扫地机器人为何能做到如此智能?智能避障是怎么实现的?;在家中,要论硬核程度,扫地机器人可一点也不比其他高科技产品低,虽然很多人都觉得扫地机器人只是个带轮子会自己跑的吸尘器,但是......
    stm32cube怎么安装 STM32CubeMX安装步骤教程;STM32CubeMX是STM32芯片图形化配置工具,通过简单的操作便能实现相关配置,省去了我们配置各种外设的时间,支持MDK、IAR......
    你可能还不能自己独立去编写整个项目的程序,这是正常的。 我那个时候是先参考商家提供的源代码,看看他们是怎么实现的,然后基于他们的代码再修改修改,最后烧录进去测试一下反应。 改多了,测多了,基本也把他们的代码实现......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>