u8g2库stm32移植记录(硬件IIC)

这是用stm32cubemx,hal库,硬件IIC的方式移植u8g2的教程:

0.移植其实非常简单,文中代码较多只是因为我把官方的模板放上来了,实际上要写的代码只有三部分,代码量非常小的!如果不想看那么多,那么跟着前7步更改完库文件,然后精确定位到红色字加粗的三段代码处,把他们复制粘贴到main.c里就可以了。

1.cubemx创建一个IIC工程,速率最好设置为400K,这样更新得快一点。然后连好线。

2.下载u8g2库,选择csrc文件夹下的代码进行移植。

3.将csrc文件夹内除了自己器件外的其他"u8x8_d_器件名.c"的文件都删除,比如0.96寸oled一般都是SSD1306,所以除了"u8x8_d_ssd1306_128x64_noname.c"其他都删除。

u8g2库stm32移植记录(硬件IIC)_第1张图片

4.修改"u8g2_d_setup.c"这个文件,删除其他函数只留下自己器件相关的。由于我用的硬件IIC,而且因为自己单片机空间较大所以选择后缀为f的函数使用,即我选择的是"u8g2_Setup_ssd1306_i2c_128x64_noname_f"这个函数,其他都删除了。当然也可以使用后缀为1或者2的函数,比较省空间,但是自然的会比较耗时。具体差距百度也有。该文件最后如下:

u8g2库stm32移植记录(硬件IIC)_第2张图片

5.注意到上面截图里那个函数只用到了"u8g2_m_16_8_f"这个函数来生成buf空间,因此我们去"u8g2_d_memory.c"里面,把除了"u8g2_m_16_8_f"之外的函数都注释或者删除掉,不这样做的话可能会消耗大量空间。文件最后如下:

u8g2库stm32移植记录(硬件IIC)_第3张图片

6.因为我们刚刚删除了一些函数,所以头文件里面的声明也要删除,打开“u8g2.h”,找到刚刚我们删除的函数,删除掉这些函数声明,这一步好像不做也行,问题不大。修改后如下:

u8g2库stm32移植记录(硬件IIC)_第4张图片

7.文件的修改到此为止,然后就可以将这个修改后的文件夹的全部C文件导入keil5,并且设置头文件路径。然后需要写两个回调函数。写法可以参考官方教程,也可以看我下面说的官方移植教程https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platformhttps://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform

8.u8g2_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;
}
  • INIT是引脚和Delay的初始化,由于用的是HAL库,这些都自动完成了,这里直接break就好。
  • 带有Delay字样的是延时相关东西,在case后面补充一些相应的延时函数,这些延时函数是用来模拟时序的,硬件IIC的话时序不需要软件控制,不管也行。
  • 带有GPIO的是操控设备可能需要的一些引脚,根据自己实际的引脚去选择地写一些就好了。我们要写的内容就是根据arg_int这个传入参数去调用设置电平的函数,是1则为高,是0则为低。后面如果用u8g2库里自带的软件模拟IIC或者软件模拟SPI函数去写设备的话,这些库自带的函数就会调用这里设置的电平函数,可以认为我们就是在这里告诉了u8g2库要怎么操作这个单片机的引脚或者延时。由于我们用的是硬件IIC,所以这里不用管也行。
  • 带有MENU的是一些操控菜单的按键引脚啥的,如果用到菜单控制才去设置。

综上,我自己写的回调函数只有下面一点点内容(其实那两个I2C的引脚删掉应该也行:

uint8_t u8g2_gpio_and_delay_stm32(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:
		HAL_Delay(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.
}

9.u8x8_byte_xxxx_xxxx_xxxx函数的编写方法

        这个回调函数就是你要用的通信协议啦,如果你想用模拟IIC或者模拟SPI的话,其实官方是有写好给你直接用的,官方是这么说的:u8g2库stm32移植记录(硬件IIC)_第5张图片

        就是说,如果想用软件模拟时序的话,官方已经把模拟时序的相关函数写好了,这些函数里面相关的引脚电平设置函数和延时函数用的就是我们在第八步里面告诉它的。可以直接用上面表格第一列的几个函数作为你的回调函数,这样就不用再写了,因而用软件模拟的话可以跳到下一步也行。

        不过本篇主要是介绍硬件IIC的方式啦,那么就需要我们自己来写这个回调函数。这个回调函数是这种形式的:typedef uint8_t (*u8x8_msg_cb)(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)。一样的,函数名字可以随意,但输入参数没错就好了。官方给出了硬件SPI和硬件IIC的模板,分别如下:

extern "C" uint8_t u8x8_byte_arduino_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
  uint8_t *data;
  uint8_t internal_spi_mode; 
  switch(msg) {
    case U8X8_MSG_BYTE_SEND:
      data = (uint8_t *)arg_ptr;
      while( arg_int > 0 ) {
        SPI.transfer((uint8_t)*data);
        data++;
        arg_int--;
      }  
      break;
    case U8X8_MSG_BYTE_INIT:
      u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
      SPI.begin();
      break;
    case U8X8_MSG_BYTE_SET_DC:
      u8x8_gpio_SetDC(u8x8, arg_int);
      break;
    case U8X8_MSG_BYTE_START_TRANSFER:
      /* SPI mode has to be mapped to the mode of the current controller, at least Uno, Due, 101 have different SPI_MODEx values */
      internal_spi_mode =  0;
      switch(u8x8->display_info->spi_mode) {
        case 0: internal_spi_mode = SPI_MODE0; break;
        case 1: internal_spi_mode = SPI_MODE1; break;
        case 2: internal_spi_mode = SPI_MODE2; break;
        case 3: internal_spi_mode = SPI_MODE3; break;
      }
      SPI.beginTransaction(SPISettings(u8x8->display_info->sck_clock_hz, MSBFIRST, internal_spi_mode));
      u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level);  
      u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->post_chip_enable_wait_ns, NULL);
      break;
    case U8X8_MSG_BYTE_END_TRANSFER:      
      u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->pre_chip_disable_wait_ns, NULL);
      u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
      SPI.endTransaction();
      break;
    default:
      return 0;
  }  
  return 1;
}
uint8_t u8x8_byte_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_SET_DC:
      /* ignored for i2c */
      break;
    case U8X8_MSG_BYTE_START_TRANSFER:
      buf_idx = 0;
      break;
    case U8X8_MSG_BYTE_END_TRANSFER:
      i2c_transfer(u8x8_GetI2CAddress(u8x8) >> 1, buf_idx, buffer);
      break;
    default:
      return 0;
  }
  return 1;
}

        接下来就照猫画虎,跟着硬件IIC的模板稍微改一下就好了。主要是两个地方:

  • 一个是IIC初始化,我们的HAL库自动生成了初始化代码并且在主函数调用了,这里我们不填也行
  • 另外一个是那个i2c_transfer()函数要换成HAL库的。HAL库的IIC写函数有两个,一个是HAL_I2C_Master_Transmit(),另外一个是HAL_I2C_Mem_Write(),后者一般用于器件中还有内存或者寄存器地址的情况,比如EEPROM等。所以我们用前者。
  • 还有一个需要注意的事情,大坑,就是u8x8_GetI2CAddress(u8x8)这里返回来的是已经右移了的器件地址,0.96寸oled一般是0x78,然后HAL库里面的也是要我们填右移了的器件地址,所以不要像模板那样再往左移动一位了(可能别的啥库函数需要左移后的原始地址吧,为啥不统一呢,太坑了!!!)

        over,综上,我们的代码如下:

uint8_t u8x8_byte_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:
      HAL_I2C_Master_Transmit(&hi2c1,u8x8_GetI2CAddress(u8x8), buffer, buf_idx,1000);
    break;
		
    default:
      return 0;
  }
  return 1;
}

10.初始化

        调用第四步剩下的u8g2_Setup_ssd1306_i2c_128x64_noname_f()这个函数来初始化,第一个参数是一个空的结构体地址,第二个参数表示是否旋转,第四个参数是我们写的gpio的那个回调函数名字,第三个参数是我们写的另外一个回调函数名或者我截图表格里那几个模拟时序的函数名。

代码如下:

#include "u8g2.h"

uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
uint8_t u8g2_gpio_and_delay_stm32(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr);

u8g2_t u8g2;

u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0,u8x8_byte_hw_i2c,u8g2_gpio_and_delay_stm32);
u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
u8g2_SetPowerSave(&u8g2, 0); // wake up display
u8g2_ClearDisplay(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);
u8g2_DrawCircle(&u8g2,60,30,20,U8G2_DRAW_ALL);
u8g2_DrawUTF8(&u8g2,10,50,"你好,world");
u8g2_SendBuffer(&u8g2);
HAL_Delay(3000);

        上面的代码记得按照cubemx的要求放到代码中的相应部分,不然一更新用户代码就没了。

        要显示中文的话,一定要把编译器的编码格式改成UTF-8,不然可能啥都显示不了。另外好像能显示的中文有限,改成俺的名字就直接编译错误了,应该是字库没有这些字的原因,后面再研究下咋整。

        一次可以设置多个图形,这个图形数据会以叠加的方式更新到原有的buf里面,然后调用u8g2_SendBuffer(&u8g2)这个函数把buf一下子全写入到oled里面进行更新。也就是说不调用这个函数做再多操作也只是再更新buf而不会更新oled显示。、

11.没了

你可能感兴趣的:(stm32,单片机,arm)