这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【分享开发笔记,赚取电动螺丝刀】再次移植U8g2到MAX78000开发板,使用硬

共8条 1/1 1 跳转至

【分享开发笔记,赚取电动螺丝刀】再次移植U8g2到MAX78000开发板,使用硬件i2c

助工
2025-09-15 12:55:00     打赏

上次写了一篇u8g2的移植,使用的是软件模拟I2C来驱动OLED屏幕。今天看论坛有小伙伴使用硬件I2C移植u8g2,没驱动起来,于是自己来试试。


不得不称赞一下u8g2的库,设计的非常好,将OLED显示与底层驱动解耦的很彻底,无论底层的硬件是何种芯片,如果使用硬件i2c驱动oled,只需要在软件层面实现i2c初始化、i2c数据传输,和系统延时这样三个基本功能即可。这样就可以将注意力集中到OLED屏幕图像开发上来,并且写好的显示内容,可以在不同的MCU上使用,同时也可以在不同主控的OLED上使用了。

FpLMR_nSSri_4C1uNR7OJ3jud4xy

看图可知,MAX78000的硬件I2C管脚是P0_16-SCL , P0_17-SDA。将OLED屏幕管脚用杜邦线接到这两个GPIO上。接下来进行代码移植。

image.png


移植第一步:拷贝u8g2的文件,与软件模拟i2c步骤一样,将u8g2下csrc里部分文件拷贝过来,新建文件夹,这里新建文件夹名为U8g2。

image.png


第二步:修改U8g2下的u8g2_d_setup.c和u8g2_d_memory.c两个文件。这里就不再赘述了。这里需要留意一下在 u8g2_d_setup.c 中,只需要保留 u8g2_Setup_ssd1306_i2c_128x64_noname_f() 这一个函数。注意,该文件内有几个命名类似的函数:命名中需要根据接口选择;以 1 结尾的函数代表使用的缓存空间为 128 字节,以 2 结尾的函数代表使用的缓存为 256字节,类似以 f 结尾的函数代表使用的缓存为 1024 字节。按自己的硬件情况进行选择。同样地还需要修改Makefile文件,保证编译路径和搜索路径。

第三步:写回调函数。

#define I2C_MASTER  MXC_I2C1 // SCL P0_16; SDA P0_17
#define I2C_SCL_PIN 16
#define I2C_SDA_PIN 17
#define I2C_FREQ 100000 // 100kHZ

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[32];
	static uint8_t buf_idx;
	uint8_t *data;
	mxc_i2c_req_t reqMaster;
	reqMaster.i2c = I2C_MASTER;
	reqMaster.addr = 0;
	reqMaster.tx_buf = NULL;
	reqMaster.tx_len = 0;
	reqMaster.rx_buf = NULL;
	reqMaster.rx_len = 0;
	reqMaster.restart = 0;
	reqMaster.callback = NULL;

	switch (msg) {
	case U8X8_MSG_BYTE_INIT: {
		/* add your custom code to init i2c subsystem */
//		I2C初始化
		MXC_I2C_Init(I2C_MASTER, 1, 0);
		MXC_I2C_SetFrequency(I2C_MASTER, I2C_FREQ);
	}
		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: {
//		数据传输
		reqMaster.addr = OLED_ADDRESS;
		reqMaster.tx_buf = buffer;
		reqMaster.tx_len = buf_idx;
		if ((MXC_I2C_MasterTransaction(&reqMaster)) == 0)
			return 0;
	}
		break;

	case U8X8_MSG_BYTE_SET_DC:
		break;

	default:
		return 0;
	}
	return 1;
}

回调函数中,需要完成I2C初始化,和I2C的数据传输。参考着官方例程I2C_SCAN的写法

image.png

硬件i2c初始化很简单,这里初始化i2c1硬件接口,然后设置了一个传输速率(100000)。硬件i2c的传输,需要填写的内容有地址、数据、数据长度。这些内容都是用一个结构体传输。只需要将OLED的地址填入,将需要传输的数据交给tx_buf,注明数据长度,即可调用系统函数给i2c写数据。

image.png

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
//		MXC_Delay(1);
		break;
	case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds
		MXC_Delay(10);
		break;
	case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second
		MXC_Delay(1000);
		break;
	case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
		MXC_Delay(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;
}

这个基本上是一个系统延时的设置,官方提供了MXC_Delay方法,能实现微秒级的延时,用起来很方便。

void u8g2_Init(u8g2_t *u8g2) {
	u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c,
			u8x8_gpio_and_delay);
	u8g2_InitDisplay(u8g2);
	u8g2_SetPowerSave(u8g2, 0);
	u8g2_ClearBuffer(u8g2);
}

最后一步,写一个u8g2的初始化函数,这里留意和软件i2c驱动之间的不同。u8g2_Setup_ssd1306_i2c_128x64_noname_f这个函数中,第三个参数“u8x8_byte_hw_i2c”,就是告诉u8g2去调用硬件i2c的方式来驱动OLED屏幕。

第三步:调用u8g2方法驱动OLED屏幕显示。

/***** Includes *****/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "board.h"
#include "mxc_device.h"
#include "mxc_delay.h"
#include "nvic_table.h"
#include "i2c_regs.h"
#include "i2c.h"
#include "u8g2.h"
#include "oled.h"

/***** Definitions *****/
//
//#define I2C_MASTER  MXC_I2C1 // SCL P0_16; SDA P0_17
//#define I2C_SCL_PIN 16
//#define I2C_SDA_PIN 17
//#define I2C_FREQ 100000 // 100kHZ

/***** Globals *****/


// *****************************************************************************
int main() {
	u8g2_t u8g2; // a structure which will contain all the data for one display
	u8g2_Init(&u8g2);

	u8g2_SendBuffer(&u8g2);
	u8g2_DrawBox(&u8g2, 0, 0, 20, 20);
	u8g2_DrawBox(&u8g2, 20, 20, 20, 20);
	u8g2_SendBuffer(&u8g2);
	u8g2_DrawFrame(&u8g2, 10, 40, 20, 20);
	u8g2_SendBuffer(&u8g2);
	u8g2_SetFont(&u8g2, u8g2_font_DigitalDiscoThin_tf);
	u8g2_DrawStr(&u8g2, 30, 10, "Hello World");
	u8g2_SendBuffer(&u8g2);

	while (1) {
		MXC_Delay(5000000);
	}

}

写完驱动后,在main方法中写个简单的调用,在屏幕上画出简单的图像来。

89f7dfd8fa3950042885c19f39643462.jpg

附上源码,请各位老师指正。I2C_SCAN.zip



院士
2025-09-15 17:59:41     打赏
2楼

谢谢分享,学习了。


院士
2025-09-15 18:32:22     打赏
3楼

楼主,这帖子里面也没有体现出来您使用硬件I2C外设的内容啊


专家
2025-09-18 21:28:10     打赏
4楼

楼主可以,硬件和软件模拟都搞定!创客!



助工
2025-09-19 10:33:36     打赏
5楼

出鬼了,用大佬的源码烧进去,我的oled就是不亮,电源3.3和5都用了,示波器量了一下,也没锁死


助工
2025-09-19 10:44:05     打赏
6楼

微信图片_20250919103845_37_702.jpg

接线


助工
2025-09-19 12:17:27     打赏
7楼

调试时候总是卡再iic驱动函数里,示波器量信号最后都能拉高,也没什么异常出现。

大佬有什么建议吗,

image.png


高工
2025-09-20 08:34:39     打赏
8楼

这个硬件IIC的初始化在哪里啊


共8条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]