移植u8g2(硬件SPI)

u8g2图形库介绍

U8g2是一个嵌入式图形库,主要用于单色的图形液晶显示屏。它由Oliver Kraus开发并在开源社区得到了广泛应用。以下是一些关于U8g2库的关键信息:

  • 显示支持:U8g2支持各种各样的LCD和OLED显示器,例如SSD1306、SSD1322、SSD1325、SSD1327、SSD1329、SSD1606、SSD1607等等。
  • 无缓冲模式:U8g2支持无缓冲模式,这意味着在有限的RAM中也可以运行复杂的图形任务。在无缓冲模式下,每次只需要一个“页”(page)的内存。这种模式的缺点是显示更新可能会更慢,因为它需要在CPU和显示器之间进行更多的数据交换。
  • 完整缓冲模式:U8g2也支持完整缓冲模式,这意味着它将在RAM中存储整个屏幕的图像。这使得显示更新更快,因为CPU只需要向显示器发送一次数据。但是,这种模式需要更多的RAM。
  • 功能丰富:U8g2库提供了多种图形操作,包括绘制线条、圆形、矩形、和文本等。它还支持多种字体,并提供了一个工具来从ttf或otf字体文件中生成字体。
  • 硬件接口:U8g2可以与各种硬件接口一起工作,包括SPI、I2C和并行接口。
  • 跨平台:U8g2库可以在多种嵌入式系统中使用,包括Arduino、ESP8266、STM32等。
  • 开源:U8g2库是开源的,可以在GitHub上获取代码。

移植平台

STM32F103C8t6,CUBEMX,keil

CUBEMX配置页 sys部分

在这里插入图片描述

CUBEMX配置页 SPI部分

在这里插入图片描述

时钟树部分不再赘述

移植u8g2

下载u8g2库

https://github.com/olikraus/u8g2

我这里使用git下载,读者如果不会使用git,可以直接在github项目页下载zip压缩包

在这里插入图片描述

在Drivers文件夹中新建u8g2文件夹

在这里插入图片描述

将得到的u8g2库文件夹中的csrc文件夹放入上面自己创建的u8g2文件夹中

在这里插入图片描述

打开keil,在魔术棒C/C++中添加头文件目录 …\Drivers\u8g2\csrc

在这里插入图片描述

添加u8g2文件

csrc文件夹里.c文件很多,我们只挑选自己需要的即可

在这里插入图片描述

这些.c文件中包含一些功能函数,例如画图描线之类的,

我们需要留意的是u8g2_d_xxx.c文件,有三个文件是必要的,u8g2_d_memory.c 和 u8g2_d_setup.c还有对应屏幕的型号的文件,如果使用的是ssd1306,则需要添加u8x8_d_ssd1306_128x64_noname.c这个文件

注:如果想要避免一些函数之间调用缺少定义的报错,建议将u8g2和u8x8_xxx.c文件全部添加,如笔者所示

在这里插入图片描述

修改u8g2_d_setup.c

如果对u8g2_d_setup.c文件进行修改直接编译的话,你可能将会看到如下错误

在这里插入图片描述

可以看到全是关于未定义标志的报错,这说明缺少了一些.c文件,但仔细查看报错信息,相关的函数不是我们使用的屏幕,所以直接注释,保留我们需要使用屏幕的函数即可(以ssd1306 128x64)为例

我这里只保留了SPI相关的初始化函数,如果使用IIC驱动的话,请不要注释掉相关的IIC函数

保留ssd1306_128x64相关的函数,我是用SPI驱动,所以IIC的函数也一并注释,函数名后缀_x表示一次传输多少字节的数据。f为1024个字节,2为256个字节,1为128个字节。为了简便刷屏的操作,选择后缀为f的函数最为合适(因为ssd1306 128x64的显存大小为128*64/8=1024 byte)。

编译可以看到0错误,剩下一些警告也是末行缺少一个空行的警告,keil自己的问题,这个就不管了

在这里插入图片描述

提供回调函数

根据官方给出的使用指南(Porting to new MCU platform · olikraus/u8g2 Wiki (github.com),为了让u8g2的硬件抽象层能与显示器通信,我们需要设置两个回调函数,这两个回调函数分别应用在u8g2_Setup_xxxx_xxxx_xxxxx_x的最后两个参数中

在这里插入图片描述

下面是官方提供的GPIO and Delay 回调函数示例

uint8_t u8x8_gpio_and_delay_template(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
  switch(msg)
  {
    case U8X8_MSG_GPIO_AND_DELAY_INIT:	// called once during init phase of u8g2/u8x8
      break;							// can be used to setup pins
    case U8X8_MSG_DELAY_NANO:			// delay arg_int * 1 nano second
      break;    
    case U8X8_MSG_DELAY_100NANO:		// delay arg_int * 100 nano seconds
      break;
    case U8X8_MSG_DELAY_10MICRO:		// delay arg_int * 10 micro seconds
      break;
    case U8X8_MSG_DELAY_MILLI:			// delay arg_int * 1 milli second
      break;
    case U8X8_MSG_DELAY_I2C:				// arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
      break;							// arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
    case U8X8_MSG_GPIO_D0:				// D0 or SPI clock pin: Output level in arg_int
    //case U8X8_MSG_GPIO_SPI_CLOCK:
      break;
    case U8X8_MSG_GPIO_D1:				// D1 or SPI data pin: Output level in arg_int
    //case U8X8_MSG_GPIO_SPI_DATA:
      break;
    case U8X8_MSG_GPIO_D2:				// D2 pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_D3:				// D3 pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_D4:				// D4 pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_D5:				// D5 pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_D6:				// D6 pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_D7:				// D7 pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_E:				// E/WR pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_CS:				// CS (chip select) pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_DC:				// DC (data/cmd, A0, register select) pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_RESET:			// Reset pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_CS1:				// CS1 (chip select) pin: Output level in arg_int
      break;
    case U8X8_MSG_GPIO_CS2:				// CS2 (chip select) pin: Output level in arg_int
      break;
    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;
}

这个回调函数是采用软件方式模拟SPI或IIC以及8080 和 6800通信协议

如果使用硬件SPI的通信,软件方式片选的话,我们只需要调用这个switch中的U8X8_MSG_GPIO_AND_DELAY_INIT,U8X8_MSG_DELAY_MILLI,U8X8_MSG_GPIO_CS,U8X8_MSG_GPIO_DC这几个分支,并给这几个分支分配好相应的函数即可

下面是我修改好的 u8x8_gpio_and_delay_template函数

uint8_t u8x8_stm32_gpio_and_delay(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:
    HAL_Delay(1);
    break;
  case U8X8_MSG_DELAY_MILLI:
    HAL_Delay(arg_int);
    break;
  case U8X8_MSG_GPIO_CS:				        // CS (chip select) pin: Output level in arg_int
    HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin, arg_int);
    break;
  case U8X8_MSG_GPIO_DC:                        //DC (data/cmd, A0, register select) pin: Output level in arg_int
    HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, arg_int);
    break;
  }
  return 1;
}

只需要调用u8g2.h和main.h这两个头文件即可。如果你采用软件通信的方式,则需要把相应的分支给补充了,可以参考下面这篇博文:

http://t.csdn.cn/SiQAE

接下来是倒数第二个参数的回调函数,这个回调函数只有当我们采用硬件通信如IIC,SPI的时候才需要提供,使用软件模拟方式通信时不需要提供,调用官方的函数即可,如下图官方说明所示

在这里插入图片描述

当我们采用硬件通信方式时,这个回调函数必须和官方的函数指针保持一致形式

uint8_t (*u8x8_msg_cb)(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);

根据官方提供的信息,我们需要在这个回调函数中编写一个switch函数,并补充相应的分支

在这里插入图片描述

上图我勾选的地方,如果嫌麻烦可以按官方给的示例去调用相应的函数即可,但毕竟是封装了一层回调函数,还是比较影响效率的,所以笔者这里直接使用HAL库函数。在U8X8_MSG_BYTE_SEND这个分支中,提供我们MCU的SPI库函数

下面是我编写的硬件回调函数

uint8_t u8x8_byte_4wire_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,
    void *arg_ptr)
{
  switch (msg)
  {
  case U8X8_MSG_BYTE_SEND:
    HAL_SPI_Transmit(&hspi1, (uint8_t *) arg_ptr, arg_int, 10000);
    break;
  case U8X8_MSG_BYTE_INIT:
    break;
  case U8X8_MSG_BYTE_SET_DC:
    u8x8_gpio_SetDC(u8x8,arg_int);
    break;
  case U8X8_MSG_BYTE_START_TRANSFER:
    HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin, 0);
    break;
  case U8X8_MSG_BYTE_END_TRANSFER:
    HAL_GPIO_WritePin(OLED_CS_GPIO_Port,OLED_CS_Pin, 1);
    break;
  default:
    return 0;
  }
  return 1;
}

验证

在主函数中调用u8g2头文件

#include "u8g2.h"

提供一个u8g2_t的全局变量,他的原型是

struct u8g2_struct
{
  u8x8_t u8x8;
  u8g2_draw_ll_hvline_cb ll_hvline;	/* low level hvline procedure */
  const u8g2_cb_t *cb;		/* callback drawprocedures, can be replaced for rotation */
  
  /* the following variables must be assigned during u8g2 setup */
  uint8_t *tile_buf_ptr;	/* ptr to memory area with u8x8.display_info->tile_width * 8 * tile_buf_height bytes */
  uint8_t tile_buf_height;	/* height of the tile memory area in tile rows */
  uint8_t tile_curr_row;	/* current row for picture loop */
  
  /* dimension of the buffer in pixel */
  u8g2_uint_t pixel_buf_width;		/* equal to tile_buf_width*8 */
  u8g2_uint_t pixel_buf_height;		/* tile_buf_height*8 */
  u8g2_uint_t pixel_curr_row;		/* u8g2.tile_curr_row*8 */
  
  /* the following variables are set by the update dimension callback */
  /* this is the clipbox after rotation for the hvline procedures */
  //u8g2_uint_t buf_x0;	/* left corner of the buffer */
  //u8g2_uint_t buf_x1;	/* right corner of the buffer (excluded) */
  u8g2_uint_t buf_y0;
  u8g2_uint_t buf_y1;
  
  /* display dimensions in pixel for the user, calculated in u8g2_update_dimension_common()  */
  u8g2_uint_t width;
  u8g2_uint_t height;
  
  /* this is the clip box for the user to check if a specific box has an intersection */
  /* use u8g2_IsIntersection from u8g2_intersection.c to test against this intersection */
  /* actually, this window describes the position of the current page */
  u8g2_uint_t user_x0;	/* left corner of the buffer */
  u8g2_uint_t user_x1;	/* right corner of the buffer (excluded) */
  u8g2_uint_t user_y0;	/* upper edge of the buffer */
  u8g2_uint_t user_y1;	/* lower edge of the buffer (excluded) */
  
#ifdef U8G2_WITH_CLIP_WINDOW_SUPPORT
  /* clip window */
  u8g2_uint_t clip_x0;	/* left corner of the clip window */
  u8g2_uint_t clip_x1;	/* right corner of the clip window (excluded) */
  u8g2_uint_t clip_y0;	/* upper edge of the clip window */
  u8g2_uint_t clip_y1;	/* lower edge of the clip window (excluded) */
#endif /* U8G2_WITH_CLIP_WINDOW_SUPPORT */
  
  
  /* information about the current font */
  const uint8_t *font;             /* current font for all text procedures */
  // removed: const u8g2_kerning_t *kerning;		/* can be NULL */
  // removed: u8g2_get_kerning_cb get_kerning_cb;
  
  u8g2_font_calc_vref_fnptr font_calc_vref;
  u8g2_font_decode_t font_decode;		/* new font decode structure */
  u8g2_font_info_t font_info;			/* new font info structure */

#ifdef U8G2_WITH_CLIP_WINDOW_SUPPORT
  /* 1 of there is an intersection between user_?? and clip_?? box */
  uint8_t is_page_clip_window_intersection;
#endif /* U8G2_WITH_CLIP_WINDOW_SUPPORT */

  uint8_t font_height_mode;
  int8_t font_ref_ascent;
  int8_t font_ref_descent;
  
  int8_t glyph_x_offset;		/* set by u8g2_GetGlyphWidth as a side effect */
  
  uint8_t bitmap_transparency;	/* black pixels will be treated as transparent (not drawn) */

  uint8_t draw_color;		/* 0: clear pixel, 1: set pixel, modified and restored by font procedures */
					/* draw_color can be used also directly by the user API */
					
	// the following variable should be renamed to is_buffer_auto_clear
  uint8_t is_auto_page_clear; 		/* set to 0 to disable automatic clear of the buffer in firstPage() and nextPage() */
  
};

这个原型想了解的话就自己看看吧,笔者就不作解释了

定义u8g2_t 的全局变量

static u8g2_t u8g2;

初始化设置

u8g2_Setup_ssd1306_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi, u8x8_stm32_gpio_and_delay);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);

u8g2_Setup_ssd1306_128x64_noname_f中第1个参数指定了u8g2结构体,用以保存各种数据,第2个参数为选择的角度,可选择U8G2_R0-R3,第3个则指定了通信的方式,这里我们使用硬件SPI,提供我们定义好的函数名,第4个也是同理,将我们设计好的回调函数地址传给它

  while (1)
  {
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 0, 15, "Hello World!");
u8g2_DrawCircle(&u8g2, 64, 40, 10, U8G2_DRAW_ALL);
// 刷新屏幕
u8g2_SendBuffer(&u8g2);
  }

注意:如果你是在淘宝上买的SPI OLED模块,请将OLED模块的RES脚接一个高电平,不能将它悬空,不然就无法工作。

实物演示

在这里插入图片描述

参考博文:u8g2 library usage with STM32 MCU (elastic-notes.blogspot.com)

转载请标明出处 By QDU_jiongsheng

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值