STM32HAL库开发进阶实战(1)——手把手基于STM32移植U8g2图形库教程(0.96寸OLED)

前言:这个文章内容就是将及其牛的U8g2图形库移植到STM单片机上,这里主要用型号为STMF103C8T6的单片机演示,用于显示及其精美的UI,目前,GitHub上有着非常优秀的开源库,但是都不适合主流0.96寸OLED,相信大部分的单片机入门都是使用着0.96寸OLED,所以在这里介绍一下U8g2和演示移植一下目前最为主流0.96寸OLED的GUI图形库。后续还会发布利用U8g2图形库制作一个多级菜单教程。

这个是通过看很多篇CSDN大佬的文章和网上视频教学精减出来的教程,个人觉得写得特别好的就是这个大佬的文章,大家可以去看看:玩转U8g2,一篇就够

U8g2库开源网址:U8g2图形库

如果打不开网址的大佬们可以下载下面的链接!

图形库资源百度网盘链接:通过百度网盘分享的文件:00_u8g2-master.zip
链接:https://pan.baidu.com/s/1FhaTQFKZuthkbNYac0PWnA?pwd=arm6 
提取码:arm6 
--来自百度网盘超级会员V3的分享

有需要我个人整理的文件可以评论区留言:

c44f70ad19444d27a58fcded1ea669fb.png

所使硬件:STM32F103C8T6    0.96寸OLED

2292068f600a4ac79f5a1a106d9cf965.png

这里演示使用的时i2c2,对应的是

SCKL:PB10

SDA:  PB11

对应接线可参考:

ce60f04ef3004da0a9b3e5ac12d9d235.png

一、U8g2简介

1.1、U8g2是什么?

e80f4d2ce625438f9afc6fafed1fd3ce.png

U8g2是一个功能强大的C++图形库,专为嵌入式系统而设计,是一个开源图形库, 主要用来驱动小屏幕, 内部继承了I2C/SPI, 支持常用的小屏幕支持很多型号!

1.2、主要特点以及优势

主要特点:

       1、支持多种屏幕:U8g2支持近200种单色屏,包括LCD、OLED等,这使得开发者能够轻松地在不同的显示屏上实现图形界面。
        2、跨平台支持:除了Arduino平台外,U8g2还可以在其他嵌入式平台上使用,增强了其灵活性和可移植性。
        3、多种字体支持:U8g2支持多种字体,包括中文、韩文、日文、梵文等,满足了不同项目的需求。
        4、高效的内存管理:采用页缓冲策略,减少了对内存的需求,这在资源有限的嵌入式平台上尤为重要。
        5、丰富的图形功能:提供了绘制文本、线条、形状等基本图形操作,以及自动滚动、页面缓冲区管理等高级特性。

优势:

1、U8g2库平台支持性好,基本上支持绝大部分Arduino与STM32开发板,也包含物联网比较常用的esp8266;

2、U8g2库显示控制器支持性好,基本上市面上的OLED都完美支持;

3、U8g2库 API函数众多,特别支持了中文,支持了不同字体,这是一个对于开发者俩说不小的福利;

4、U8g2 库移植简单,容易使用;

二、移植U8g2库准备

移植的前提,我们需要有一个正常编译无错误的MDK,hal库的话可以使用CubeMX生成,使用标准库的可以自己准备一个(江科大的或者野火的,等等。。。),这里我使用的是hal库。下面我就来使用CubeMX生成一个MDK工程!相信各位大佬在学习STM32的时候都有写过OLED的驱动代码或者有着自己的驱动代码,这里U8g2图形库有着自己的驱动,所以在生成一个MDK工程的时候就不用把自己的OLED驱动添加进去了!

2.1、CubeMX配置

1、RCC配置外部高速晶振——HSE;

60ac597ca4664e78b508acfc77726ed6.png

2、SYS配置:Debug设置成Serial Wire

72259e6ccf354a2cac3a802ca088186c.png

3、I2C2配置;

2c7fe3489093471d94a3d1b591225f08.png

在移植U8g2过程中我们要使用到us级别的延时,所以需要配置定时器!(详细后面会说)

4、TIM1配置:U8g2图形库需要us级延迟e1463b2e3f684835b0b54f3a66d6b874.png

5、时钟树配置:

b3d0adc074b04e88889db42f1061e8d3.png

6、生成工程:

提示:工程名字里面不要有文字

090b440817b247d4b4fd4db60963adbc.png

0af6d24665584577b2a23fa55a85aafb.png现在就已经得到了一个没有错误的MDK工程了;

2.2、资料下载

移植U8g2图像库需要准备好,U8g2的源码是在GitHub上开源的。

下载地址:https://github.com/olikraus/u8g2

如果打不开网址的大佬们可以下载下面的链接!

百度网盘:通过百度网盘分享的文件:00_u8g2-master.zip
链接:https://pan.baidu.com/s/1FhaTQFKZuthkbNYac0PWnA?pwd=arm6 
提取码:arm6 
--来自百度网盘超级会员V3的分享

需要我学习过程中整理好的资料,可以评论区留言,我邮箱发送过去个你。

c44f70ad19444d27a58fcded1ea669fb.png

三、移植U8g2

现在我们已经万事俱备只欠东风了。下载好了我们就得到了一个压缩包!

25ae346116ff4ca49de0500e87442930.png

里面有很多东西,我们只关心里面的csrc文件,将其解压出来!里面有很多.C和头文件

df708236d26d4f94857bcbc59b852cc1.png

3.1、精简csrc文件

去掉无用的驱动文件,这里我使用的是i2c驱动,所以只留下u8x8_d_ssd1306_128x64_noname

u8x8_d开头的时驱动文件,只留下u8x8_d_ssd1306_128x64_noname即可!!!

3ece62190e2349bf9a2cea914286a389.png

3.2、将csrc文件加入工程中

根据自己的习惯将csrc文件加入到工程文件中,可以参考我的!

我将其加入到Drivers文件中,并命名为U8g2

092a2f1c3dd8403794c0b06e481580d9.png

c688b5d5f48c418f8ff1747a79206b75.png

我重建了一个组,专门存放U8g2库的文件,方便管理!

这个时候编译的话会有一堆错误,不用管,继续后面的操作即可!(不熟练的小白可以完全跟着我做)

3.3、在keil中精简u8g2_d_setup.c

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

打开u8g2_d_setup.c

b3cc1a1db095413e9d16c22562eb2c78.png

只留下这个函数         注意:有很多非常相似的函数,所以一定要看准一点!

眼神不好的建议CTRL+F搜索,避免删错

/* 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);
}

/* end of generated code */

3.4、keil中精简u8g2_d_memory.c

打开精简u8g2_d_memory.c,删除全部函数,只留下u8g2_m_16_8_f函数

84a4cc7aa95c4589a2ca11ee72ff155b.png

函数原型:

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
}

/* end of generated code */

还是那句话,眼神不好的建议CTRL+F搜索,避免删错!

到这里,移植工作基本已经做完了!

这个时候编译就是0错误0警告!!!看到就心情大好了!

后面我们用到里面的函数只需要包含头文件"u8g2.h"即可使用!所以我们需要添加这个文件的路径到keil工程中!

3.5、添加路径

我们前面将文件丢入的位置是C:\Users\86157\Desktop\U8g2\05_hal_U8g2显示U8G2_logo\Drivers\U8g2 每一个人的位置不一样,自己看自己的!

1d2e94a3a0254be2a1dadd5f3c55c290.png

复制路径添加到keil中

512bc933701f4328b38f3f0419a15efa.png

复制进去添加即可:

11249151fc604bb09c530a3c8ca47986.png

到这里我们就大功告成了!!!

四、简单使用U8g2图形库显示Logo

前面我们不是配置了定时器吗?为啥移植一个图形库要配置定时器呢?

因为里面用到了us级别的延时,hal库函数没有这种精度的延时函数,所以我们需要自己写!

4.1、编写us级别延时函数

#define DLY_TIM_Handle (&htim1)	//定义

/* USER CODE BEGIN 1 */
void Tims_delay_us(uint16_t nus)
{
	__HAL_TIM_SET_COUNTER(DLY_TIM_Handle, 0);
	__HAL_TIM_ENABLE(DLY_TIM_Handle);
	while (__HAL_TIM_GET_COUNTER(DLY_TIM_Handle) < nus)
	{
	}
	__HAL_TIM_DISABLE(DLY_TIM_Handle);
}
/* USER CODE END 1 */

这里我把它放在了tim.c文件里面,你们可以根据自己的爱好放置!

4.2、驱动初始化,缓存区初始化


#define u8         unsigned char  // ?unsigned char ????
#define MAX_LEN    128  //
#define OLED_ADDRESS  0x78 // oled
#define OLED_CMD   0x00  // 
#define OLED_DATA  0x40  // 

/* 填空初始化u8x8_byte_hw_i2c函数 */
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[128];
    static uint8_t buf_idx;
    uint8_t *data;
 
    switch (msg)
    {
    case U8X8_MSG_BYTE_INIT:
    {
        /* add your custom code to init i2c subsystem */
        MX_I2C2_Init(); //I2C初始化
    }
    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:
    {
        if (HAL_I2C_Master_Transmit(&hi2c2, OLED_ADDRESS, buffer, buf_idx, 1000) != HAL_OK)
            return 0;
    }
    break;
 
    case U8X8_MSG_BYTE_SET_DC:
        break;
 
    default:
        return 0;
    }
 
    return 1;
}


/* 填空初始化u8x8_gpio_and_delay函数 */
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
        HAL_Delay(1);
        break;
    case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
        Tims_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
        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;
}
void u8g2Init(u8g2_t *u8g2)
{
	u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8x8_gpio_and_delay); // 初始化u8g2 结构体
	u8g2_InitDisplay(u8g2);   	 //对缓存进行初始化                                                // 初始化u8x8_gpio_and_delay函数
	u8g2_SetPowerSave(u8g2, 0);   //wake up 屏幕                                                      // 初始化u8x8_byte_hw_i2c函数
	u8g2_ClearBuffer(u8g2);   //清除缓存区                                                        //对驱动进行初始化
}

到这里使用U8g2图形库的编程前提工作就初始化好了!

4.3、使用U8g2图形库显示两个线

前面我们编写好了U8g2的驱动初始化函数,只需要在main函数里面使用即可:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C2_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN   */
  //初始化U8G2
  u8g2_t u8g2;     
  u8g2Init(&u8g2);      //初始化
	
//	draw(&u8g2);          //显示U8G2logo
	u8g2_DrawLine(&u8g2, 0,0, 127, 63);
	u8g2_DrawLine(&u8g2, 127,0, 0, 63);//显示两个对角线
	u8g2_SendBuffer(&u8g2);
	u8g2_ClearBuffer(&u8g2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

显示效果:

03579efbe40646a6a593dc87f3d6f918.png

4.4、使用U8g2图形库实例显示U8g2Logo

实例代码:

void draw(u8g2_t *u8g2)
{
	u8g2_ClearBuffer(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");
		
	u8g2_SendBuffer(u8g2);
	HAL_Delay(1000);
}

main函数:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C2_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN   */
  //初始化U8G2
  u8g2_t u8g2;     
  u8g2Init(&u8g2);      //初始化
	
	draw(&u8g2);          //显示U8G2logo
//	u8g2_DrawLine(&u8g2, 0,0, 127, 63);
//	u8g2_DrawLine(&u8g2, 127,0, 0, 63);//显示两个对角线
	u8g2_SendBuffer(&u8g2);
	u8g2_ClearBuffer(&u8g2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

显示效果:

eacceb879f0846c59e065f1cddc619ee.png

五、总结

U8g2图形库可以说目前小尺寸OLED首选的GUI,其可以呈现出的图形远不止上述测试中的图形,更多的功能还需要读者朋友们自己去好好发掘。优秀GUI的移植可以达到大大缩短开发周期,优化UI界面等目的。感兴趣的读者朋友可以点波关注,感谢!!!

后面我会更新更多的实战使用教程,如果这个文章对各位大佬有帮助的话,可以点点赞!

六、关于spi协议U8g2移植

这个文章主要是移植i2c协议的U8g2库,因为OLED屏幕不仅可以支持i2c协议,还支持spi协议,其移植过程大同小异,只不过是删除的文件的不同,驱动初始化函数参数使用不同!后续有空会继续更新的!关于spi的U8g2移植可以参考官方示例!、

spi移植U8g2库官方示例:00_u8g2_template_stm32f103c8t6-mast...
链接:https://pan.baidu.com/s/1voi1xffxcQt8lcQt4Ipqfg?pwd=arm6 
提取码:arm6 
--来自百度网盘超级会员V3的分享

评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值