嘿哈 · 2022年01月16日

【XR806开发板试用】软件模拟IIC驱动OLED显示图片&&自己遇到的坑

前言

首先,非常感谢极术社区和全志举办此次开发板申请试用活动。由于自己水平太低,拿到板子后不知道要干点啥,偶然看见一个大佬写的I2C外设使用及控制OLED屏显示,文章中使用了硬件IIC控制OLED,正好我手里也有一块OLED,那就直接试试模拟IIC(虽然有硬件IIC,软件IIC显得比较鸡肋[狗头])。同时感谢群里的好兄弟们,在我遇到坑的时候帮我解决了很多问题,非常感谢。(由于我水平比较低,可能会存在错误,希望各位大佬可以批评指正)

一、设备及其连接

1、设备

OLED:1.3寸四脚OLED(IIC)

 title=

驱动板:XR806(废话[狗头])

 title=

2、连接

OLED            XR806
VCC--------------3V3
GND--------------GND
SDA--------------PB07
SCL--------------PB06

二、文件结构及相关配置

1、文件结构

   my_oled
   ├── BUILD.gn
   ├── i2c
   │   ├── i2c.c
   │   └── i2c.h
   ├── oled
   │   ├── oled.c
   │   ├── oledfont.h
   │   └── oled.h
   └── src
       └── main.c

2、BUILD.gn修改

(1)device/xradio/xr806/ohosdemo/BUILD.gn配置

group("ohosdemo") {
    deps = [

        #"hello_demo:app_hello",
        #"iot_peripheral:app_peripheral",
        #"wlan_demo:app_WlanTest",
        #"led:app_led",  
        "my_oled:app_oled",
    ]
}

(2)device/xradio/xr806/ohosdemo/my\_oled/BUILD.gn配置

import("//device/xradio/xr806/liteos_m/config.gni")     #必须,config中定义了头文件>路径和关键宏定义


static_library("app_oled") {                             #必须,所有应用工程必须是app_打头
   configs = []

   sources = [
      "src/main.c",
      "oled/oled.c",
      "i2c/i2c.c",
   ]

   cflags = board_cflags                                #必须,board_cflags是在config.gni中定义的关键宏定义

   include_dirs = board_include_dirs                    #必须,board_include_dirs是>在config.gni中定义的文件路径
   include_dirs += [
      "./i2c",
      "./oled",
      "//utils/native/lite/include",
      "//kernel/liteos_m/kernel/arch/include",
      "//base/iot_hardware/peripheral/interfaces/kits", #根据实际情况添加头文件路径
   ]
}

三、代码

代码有点多,只能放出来一部分,完整的代码可以在下面的链接中下载QWQ.(代码有点乱,注释比较少,如果我写的不清楚,欢迎随时来问我嗷QWQ,文末有联系方式[狗头])
链接:https://pan.baidu.com/s/1LaegCeUGTuxnlHhk2lN4cw
提取码:1217

1、模拟IIC

(1)时序图

 title=
根据时序图控制SDA和SCL的高低,实现IIC通讯协议。

(2)模拟IIC代码(部分)

i2c.h

#ifndef __I2C_H
#define __I2C_H

#include "driver/chip/hal_gpio.h"

#define SDA_IN()  PB7_OUT_OR_IN_SELECT(2)    
#define SDA_OUT() PB7_OUT_OR_IN_SELECT(1) 

//IO操作函数
#define   IIC_SCL_HIGH   HAL_GPIO_WritePin(GPIO_PORT_B, GPIO_PIN_6, GPIO_PIN_HIGH)   
#define   IIC_SCL_LOW   HAL_GPIO_WritePin(GPIO_PORT_B, GPIO_PIN_6, GPIO_PIN_LOW)
#define   IIC_SDA_HIGH   HAL_GPIO_WritePin(GPIO_PORT_B, GPIO_PIN_7, GPIO_PIN_HIGH)   
#define   IIC_SDA_LOW   HAL_GPIO_WritePin(GPIO_PORT_B, GPIO_PIN_7, GPIO_PIN_LOW)
#define   READ_SDA    HAL_GPIO_ReadPin(GPIO_PORT_B, GPIO_PIN_7)

void i2c_init(void);
void IIC_Start(void);                        //发送IIC开始信号
void IIC_Stop(void);                         //发送IIC停止信号
void IIC_Send_Byte(uint8_t txd);             //IIC发送一个字节
uint8_t IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
uint8_t IIC_Wait_Ack(void);                  //IIC等待ACK信号
void IIC_Ack(void);                          //IIC发送ACK信号
void IIC_NAck(void);                        //IIC不发送ACK信号

#endif

i2c.c(部分)

#include <stdio.h>
#include "ohos_init.h"
#include "kernel/os/os.h"
#include "driver/chip/hal_gpio.h"
#include "i2c.h"
GPIO_InitParam param;
void gpio_output_init(void)
{
        param.driving = GPIO_DRIVING_LEVEL_1;
        param.mode = GPIOx_Pn_F1_OUTPUT;
        param.pull = GPIO_PULL_NONE;
        HAL_GPIO_Init(GPIO_PORT_B, GPIO_PIN_6, &param);
        HAL_GPIO_Init(GPIO_PORT_B, GPIO_PIN_7, &param);
}
void PB6_output_ctl(uint8_t level)
{
        HAL_GPIO_WritePin(GPIO_PORT_B, GPIO_PIN_6, level ? GPIO_PIN_HIGH : GPIO_PIN_LOW);
}
void PB7_output_ctl(uint8_t level)
{
        HAL_GPIO_WritePin(GPIO_PORT_B, GPIO_PIN_7, level ? GPIO_PIN_HIGH : GPIO_PIN_LOW);
}
void PB7_OUT_OR_IN_SELECT(uint8_t mode ) // 1输出2输入
{
        if(mode == 1)
        {
                param.mode = GPIOx_Pn_F1_OUTPUT;
                HAL_GPIO_Init(GPIO_PORT_B, GPIO_PIN_7, &param);
        }
 else if(mode == 2)
        {
                param.mode = GPIOx_Pn_F0_INPUT;
                HAL_GPIO_Init(GPIO_PORT_B, GPIO_PIN_7, &param);
        }
}
void i2c_init()
{
        gpio_output_init();
        PB6_output_ctl(1);  //输出高
        PB7_output_ctl(1);
}
//产生起始信号
void IIC_Start(void)
{
        SDA_OUT();     //sda线输出
        IIC_SDA_HIGH;
        IIC_SCL_HIGH;
        OS_MSleep(0.004);;
        IIC_SDA_LOW;
        OS_MSleep(0.004);
        IIC_SCL_LOW;//钳住I2C总线,准备发送或接收数据 
}
//产生IIC停止信号
void IIC_Stop(void)
{
        SDA_OUT();//sda线输出
        IIC_SCL_LOW;
        IIC_SDA_LOW;
        OS_MSleep(0.004);
        IIC_SCL_HIGH;
        IIC_SDA_HIGH;//发送I2C总线结束信号
        OS_MSleep(0.004);
}
...

2、OLED代码

OLED部分是直接修改了一下我以前在stm32上用的代码。

oled.h

#ifndef __OLED_H
#define __OLED_H
#include "i2c.h"

#define u8 unsigned char
#define u32 unsigned int
#define OLED_CMD  0     //写命令
#define OLED_DATA 1     //写数据

void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);
void OLED_Clear(void);
void OLED_Init(void);
void OLED_Refresh_Gram(void);
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
void OLED_ShowChinese(u8 x,u8 y,u8 chr,u8 len,u8 size,u8 mode);
void OLED_Showpic(u8 x,u8 y,u8 chr,u8 size,u8 mode);
#endif

oledfont.h里是字库,还有图片
oled.c(部分)

void OLED_Init(void)
{
    i2c_init();
    OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
    OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率
    OLED_WR_Byte(80,OLED_CMD);   //[3:0],分频因子;[7:4],震荡频率
    OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数
    OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) 
    OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移
    OLED_WR_Byte(0X00,OLED_CMD); //默认为0
    OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数.
    OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置
    OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭
    OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式
    OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
    OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;
    OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
    OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置
    OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置
    OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
    OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
    OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期
    OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
    OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率
    OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
    OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
    OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示                                                     
    OLED_WR_Byte(0xAF,OLED_CMD); //开启显示  
    OLED_Clear();
}
/*********************************/
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
        u8 pos,bx,temp=0;
        if(x>131||y>63)return;//超出范围了.
        pos=7-y/8;
        bx=y%8;
        temp=1<<(7-bx);
        if(t)OLED_GRAM[x][pos]|=temp;
        else OLED_GRAM[x][pos]&=~temp;
}
/********* 在指定位置显示一个字符,包括部分字符节  *************/
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示                             
//size:选择字体 12/16/24
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{
        u8 temp,t,t1;
        u8 y0=y;
        u8 csize=(size/8+((size%8)?1:0))*(size/2);              //得到字体一个字符对应点阵集所占的字节数
        chr=chr-' ';//得到偏移后的值             
    for(t=0;t<csize;t++)
    {
       if(size==12)temp=asc2_1206[chr][t];             //调用1206字体
       else if(size==16)temp=asc2_1608[chr][t];        //调用1608字体
        else if(size==24)temp=asc2_2412[chr][t];        //调用2412字体
        else return;                                                            //没有的字库
    for(t1=0;t1<8;t1++)
                {
                    if(temp&0x80)OLED_DrawPoint(x,y,mode);
                    else OLED_DrawPoint(x,y,!mode);
                    temp<<=1;
                    y++;
                     if((y-y0)==size)
                        {
                                y=y0;
                                 x++;
                                break;
                        }
                }
    }
}

3、主函数代码

main.c

#include <stdio.h>
#include "ohos_init.h"                                                          //(7)
#include "kernel/os/os.h"
#include "iot_gpio.h"                                                           //(8)
#include "i2c.h"
#include "oled.h"
static OS_Thread_t g_main_thread;
#define GPIO_ID_PA21 21
static void MainThread(void *arg)
{
  printf("delay test start\r\n");
  IoTGpioInit(GPIO_ID_PA21);                                                    //(3)
  IoTGpioSetDir(GPIO_ID_PA21, IOT_GPIO_DIR_OUT);                                //(4)
  OLED_Init();
  OLED_Showpic(1,1,0,60,1);
OLED_Refresh_Gram();
  while (1) {
    IoTGpioSetOutputVal(GPIO_ID_PA21, 1);                                      //(5)
    OS_MSleep(500);
    IoTGpioSetOutputVal(GPIO_ID_PA21, 0);                                      //(6)
    OS_MSleep(500);
  }
}
void LEDMain(void)                                                              //(2)
{
  printf("i2c Test Start\n");
  if (OS_ThreadCreate(&g_main_thread, "MainThread", MainThread, NULL,
OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {
    printf("[ERR] Create MainThread Failed\n");
  }
}
SYS_RUN(LEDMain);

四、运行结果

编译下载后运行如图:
 title=

原图是这个恐龙QIQ。
 title=
虽然最后显示的有点恐怖,但也算成功了,哈哈哈哈。

五、踩过的坑

说起来配置环境那直接痛苦面具,我遇到了很多错误,但当时就想着赶快配置好,大部分都没记录来(抱头痛哭T\_T)。但下面的印象非常深刻。

make menuconfig错误

我当时的错误是这样的:
 title=
后来查了一下是因为少装了点啥。
解决:
 title=
(如果你装了当我没说...)

hb build -f 时报错

我当时的错误是这样的:
 title=
解决:
当时我问了群里的一个兄弟,昵称:家星。再次万分感谢。
后来我发现在官方配置里也提到过:

 title=.jpg")

xshell串口格式乱

解决:文件 - 属性 - 终端 - 高级 - 用CR-LF接受LF(R)

六、结语

最后,再次感谢极术社区和全志平台,感谢各位群友给我的帮助,这也是我第一次写关于技术的文章,写的不好,希望大佬多多批评指正。如果有错误可以联系我嗷QQ:2403057219(24小时在线[狗头])

推荐阅读
关注数
13820
内容数
140
全志XR806开发板相关的知识介绍以及应用专栏。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息