前面介绍了一些关于在Hi3861上使用OpenHarmony的系统框架,别看Hi3861的IO口数量不多,但是其资源是相当丰富的,单片机有的复用功能他都有I2C、SPI、UART、ADC、PWM都支持,本文将在之前的框架基础上用Hi3861使用I2C协议驱动0.96寸的OLED屏幕显示图片、字符、数字以及汉字。
I2C是南向开发中使用频率很高的一个协议,有关它的简介笔者在之前的文章中有过介绍,大家可以去参考——树莓派4B学习笔记——IO通信篇(I2C)。在这里只需要知道以下几点:
1.I2C有一个时钟线SCL,用来连接主从,使得主从机的时钟同步;还有一个数据线SDA,用来串行传输数据;
2.I2C通信中没有片选,选中从机是通过地址来判断的;
由于Hi3861提供了I2C的API接口,所以底层的数据传输方式、起始信号、结束信号、应答信号这些东西不需要去纠结,在操作器件时使用API的接口格式即可。
Hi3861的IO口中有两组I2C,分别是I2C0和I2C1,笔者在此使用的是I2C0,需要确定好自己使用的是1还是0,后面编程要用。
然后这两组又可以分别复用到两组IO,也就是说有四组I2C接口,复用引脚是GPIO9(I2C0 SCL)和GPIO10(I2C0 SDA)。
所以笔者的硬件连接如下:
结合前面两篇介绍的工程建立,首先在工程系统目录下新建一个文件夹,然后添加一个c文件和一个BUILD.gn的编译构建文件;新建完成后,开始搭建代码框架,本文借用上一篇的任务创建的框架,采用线程创建的方式来实现。
创建代码如下:
//任务实现函数
static void OLEDTask(void)
{
}
//任务创建函数
static void OLEDExampleEntry(void)
{
osThreadAttr_t attr;
attr.name = "OLEDTask";//任务名
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = I2C_TASK_STACK_SIZE;//堆栈大小
attr.priority = I2C_TASK_PRIO;
if (osThreadNew((osThreadFunc_t)OLEDTask, NULL, &attr) == NULL)
{
printf("Falied to create OLEDTask!\n");
}
printf("Create I2CTask Successful!!\n");
}
APP_FEATURE_INIT(OLEDExampleEntry);
完成了代码框架的搭建后,需要根据硬件的GPIO接线方式对GPIO9以及GPIO10进行初始化,参考前面的点灯流程可以知道,Hi3861的IO初始化和STM32类似,首先初始化GPIO,然后复用为I2CSCL和SDA,初始化的命令已经在gpio_ex.h中标明,每个IO的复用功能,选择自己所需的即可。
GPIO9与GPIO10的初始化代码如下:
在任务函数中添加此代码,初始化GPIO口
GpioInit();
//GPIO_10复用为I2C0_SDA
IoSetFunc(WIFI_IOT_IO_NAME_GPIO_10, WIFI_IOT_IO_FUNC_GPIO_10_I2C0_SDA);
//GPIO_9复用为I2C0_SCL
IoSetFunc(WIFI_IOT_IO_NAME_GPIO_9, WIFI_IOT_IO_FUNC_GPIO_9_I2C0_SCL);
//baudrate: 400kbps
上面提到过Hi3861中有两组I2C,这里笔者使用的I2C0,根据使用STM32的经验,肯定是需要指向和初始化的。这里官方已经封装好了API接口函数,调用“wifiiot_i2c.h”和“wifiiot_i2c_ex.h”即可看见里面的API函数,可以看见里面一共有六个函数。
对比STM32的I2C可以发现,这四个函数STM32的底层函数及其相似。使用方式也基本是套娃。
不同的是Hi3861的I2C接口初始化时需要设定传输速率,这里笔者选择了400Kbps,初始化代码如下:
//baudrate: 400kbps
I2cInit(WIFI_IOT_I2C_IDX_0, 400000);
I2cSetBaudrate(WIFI_IOT_I2C_IDX_0, 400000);
printf("I2C Test Start\n");
截止到上一步,Hi3861作为主机的初始化已经完成了,接下来需要主机利用总线发送指令初始化从机用来实现功能。
如下图所示,笔者使用的OLED地址是0X78,I2C的地址最后一位是读写标志位,由于OLED作为从机,主机不需要读取数据,只需要向OLED写入指令和数据即可,所以我们在编程时,可以直接使用这个地址0x78即可。(写操作是最低位是0)。
整个初始化过程需要配置的指令数据很多,笔者在此不做赘述,直接使用了中景园的代码套娃,注意源代码中GPIO初始化的部分需要删掉。
初始化代码如下:
//OLED的初始化
void OLED_Init(void)
{
OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00,OLED_CMD);//-not offset
OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WR_Byte(0x02,OLED_CMD);//
OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
OLED_Clear();
OLED_WR_Byte(0xAF,OLED_CMD);
}
完成上述初始化后,屏幕已经可以正常显示了,接下来需要编写执行函数,显示图片,汉字字符串这些。这些功能说到底还是调用I2C写API,给从机数据,让它显示内容。这里笔者还是用了中景园的代码,由于I2C底层代码的改动,需要将源代码中的IIC接收和发送部分修改成API函数的模式才能正常使用。也就是说需要将下面左边的发送字节的函数修改为右边的API接口发送函数。
右边的API函数的三个参数分别是:1.I2C通道号,这个和前面初始化的通道号一致;2.从机的地址,笔者此处是0X78;3.发送的数据,这里需要注意,接口韩式定义的是一个结构体指针,在使用过程中需要将发送的数据做一个处理。
API调用举例如下:
#define NT3H1X_SLAVE_ADDRESS 0x55 //器件地址
static bool writeTimeout( uint8_t *data, uint8_t dataSend) {
uint32_t status = 0;
WifiIotI2cData nt3h1101_i2c_data1 = {0};//定义结构体变量
nt3h1101_i2c_data1.sendBuf = data;//赋值
nt3h1101_i2c_data1.sendLen = dataSend;//数据长度
status = I2cWrite(WIFI_IOT_I2C_IDX_1, (NT3H1X_SLAVE_ADDRESS<<1)|0x00, &nt3h1101_i2c_data1);
if (status != 0)
{
printf("===== Error: I2C write status1 = 0x%x! =====\r\n", status);
return 0;
}
usleep(300000);
return 1;
}
参考这个例子,可以修改出是和此OLED的代码,这里笔者参考了此篇博文——Hispark-3861_oled显示。
代码如下:
u32 my_i2c_write(WifiIotI2cIdx id, u16 device_addr, u32 send_len)
{
u32 status;
WifiIotI2cData oled_i2c_data = { 0 };
oled_i2c_data.sendBuf = g_send_data;
oled_i2c_data.sendLen = send_len;
status = I2cWrite(id, device_addr, &oled_i2c_data);
if (status != 0) {
printf("===== Error: I2C write status = 0x%x! =====\r\n", status);
return status;
}
return 0;
}
/**********************************************
// IIC Write Command
**********************************************/
void Write_IIC_Command(unsigned char IIC_Command)
{
g_send_data[0] = 0x00;
g_send_data[1] = IIC_Command;
my_i2c_write(WIFI_IOT_I2C_IDX_0, 0x78, 2);
}
/**********************************************
// IIC Write Data
**********************************************/
void Write_IIC_Data(unsigned char IIC_Data)
{
g_send_data[0] = 0x40;
g_send_data[1] = IIC_Data;
my_i2c_write(WIFI_IOT_I2C_IDX_0, 0x78, 2);
}
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
if(cmd)
{
Write_IIC_Data(dat);
}
else
{
Write_IIC_Command(dat);
}
}
修改这个接口函数之后,其他的功能代码直接copy中景园的即可。
笔者的最后的任务函数如下:
static void OLEDTask(void)
{
GpioInit();
//GPIO_10复用为I2C0_SDA
IoSetFunc(WIFI_IOT_IO_NAME_GPIO_10, WIFI_IOT_IO_FUNC_GPIO_10_I2C0_SDA);
//GPIO_9复用为I2C0_SCL
IoSetFunc(WIFI_IOT_IO_NAME_GPIO_9, WIFI_IOT_IO_FUNC_GPIO_9_I2C0_SCL);
//baudrate: 400kbps
I2cInit(WIFI_IOT_I2C_IDX_0, 400000);
I2cSetBaudrate(WIFI_IOT_I2C_IDX_0, 400000);
printf("I2C Test Start\n");
OLED_Init();
OLED_ColorTurn(0);//0正常显示,1 反色显示
OLED_DisplayTurn(0);//0正常显示 1 屏幕翻转显示
usleep(500000);
while (1)
{
OLED_Clear();
OLED_ShowString(1,0,"ABC",8);//6*8 “ABC”
OLED_ShowString(1,8,"ABC",12);//6*12 “ABC”
OLED_ShowString(1,20,"ABC",16);//8*16 “ABC”
OLED_ShowString(1,36,"openharmony",16);
OLED_Refresh();
usleep(500000);
OLED_ShowPicture(1,0,128,64,BMP1);
OLED_Refresh();
usleep(500000);
OLED_ShowPicture(1,0,128,64,BMP2);
OLED_Refresh();
usleep(500000);
OLED_ShowPicture(1,0,128,64,BMP3);
OLED_Refresh();
usleep(500000);
OLED_ShowPicture(1,0,128,64,BMP4);
OLED_Refresh();
usleep(500000);
}
}
然后是编译,下载,这些就步骤不做记载了。
最后来看看笔者移植后的效果:
有关Hi3861使用I2C驱动0.96寸OLED的介绍就记录到此,文中如有不妥之处欢迎指出。需要代码的同学可以私信笔者获取。