> 文档中心 > OpenHarmony 标准系统HDF框架之I2C驱动开发

OpenHarmony 标准系统HDF框架之I2C驱动开发

OpenHarmony 标准系统HDF框架之I2C驱动开发

  • 主要内容
  • I2C 基础知识## I2C 基础知识 —— 概念和特性
    • I2C 基础知识 —— 协议、四种信号组合
  • I2C 调试手段## I2C 调试手段 —— 硬件
    • I2C 调试手段 —— 软件
  • HDF 框架下的 I2C 设备驱动## HDF 框架下的 I2C 设备驱动 —— 案例描述
    • HDF 框架下的 I2C 设备驱动 —— 用户态程序
    • HDF 框架下的 I2C 设备驱动 —— 驱动程序入口
    • HDF 框架下的 I2C 设备驱动 —— 设备初始化
    • HDF 框架下的 I2C 设备驱动 —— 驱动 Dispatch
    • HDF 框架下的 I2C 设备驱动 —— 驱动读写
  • 总结

主要内容

  • I2C 基础知识
  • I2C 调试手段
  • HDF 框架下的 I2C 设备驱动

I2C 基础知识## I2C 基础知识 —— 概念和特性

  • I2C(IIC、I2C)集成电路总线,由串行数据线 SDA 和串行时钟线 SCL 组成,对于一个 I2C 接口的器件,至少还需要电源和地线;
  • I2C 总线是双向、半双工传输
  • 支持多主机、多从机同时挂接在一条 I2C 总线上,多主机同时请求总线时,可以通过冲突检测和仲裁机制防止总线数据被破坏
  • 每一个从设备都有唯一的地址,从设备可被寻址(又称为被选中),只有被选中的从设备才能参与通信,每次通信只有一个主设备和一个从设备参与
  • 主设备发起一次通信,从设备响应:主从设备都可以发送和接收数据,SCL 时钟由主设备发出,在工程中常见 MCU 或 SOC 作为主设备,主从设备地位可以交换

OpenHarmony 标准系统HDF框架之I2C驱动开发

I2C 是串行低速总线,常见传输速度如下:

  • 标准模式(standard-mode):速率高达 100kbit/s
  • 快速模式(fast-mode):速率 400kbit/s
  • 快速模式+(fast-mode plus):速率 1Mbit/s
  • 高速模式(high-speed mode):速率 3.4Mbit/s

工程中常见兼容标准模式和快速模式的 I2C 从设备

OpenHarmony 标准系统HDF框架之I2C驱动开发

  • 一条 I2C 总线上的所有从设备都有一个唯一的设备地址,不能与总线上的其他设备地址重复;
  • 设备地址有 7 位和 10 位两种格式,常见 7 位格式
  • I2C 主设备对从设备可执行写操作和读操作,通过写地址和读地址区分写操作和读操作

设备地址 7 位:101000(0x50)写地址 8 位:设备地址左移一位i,末位补 0 :1010000 (0xA0)读地址 8 位:设备地址左移一位,末位补 1: 1010001 (0xA1)同一个 I2C 设备可能具有多个设备地址,通常可通过从设备的管脚配置,以 I2C 接口的 ROM 芯片 AT24C256 为例:

OpenHarmony 标准系统HDF框架之I2C驱动开发

  • 如果 A1 和 A0 两个管脚接地,则 7 位设备地址为:1010000(0x50),8 位写地址:1010000(0xA0),8 位读地址:1010001(0xA1)
  • 片内地址、片内偏移、字地址:从设备内部寻址,如内部寄存器地址或 ROM 读写地址等
  • 同一个 I2C 总线上挂载的设备数量受限于总线上最大电容不超过 400pF

I2C 基础知识 —— 协议、四种信号组合

  • I2C 起始信号和停止信号由主设备发出
  • S:时钟信号 SCL 保持高电平、数据信号 SDA 由高到低跳变
  • P:时钟信号 SCL 保持高电平、数据信号 SDA 由低到高跳变
  • 写信号:主或从设备在时钟信号 SCL 为低电平时将数据写到数据线 SDA,即数据线只能在 SCL 为低电平时发生高低跳变
  • 读数据:数据线需要在 SCL 为高电平时保持稳定,同时从或主设备也会在此时从 SDA 上读取数据

OpenHarmony 标准系统HDF框架之I2C驱动开发

I2C 调试手段## I2C 调试手段 —— 硬件

  • I2C 协议规定,在空闲状态下,总线为高电平:从设备工作电压 VDD,SDA 和 SCL 电压不低于 0.7VDD(低电平不高于 0.3VDD),常见的 VDD 有 1.8V、3.3V、5V 三种规格
  • 高电平通过外挂上拉电阻实现,需要确保上拉电阻有效

OpenHarmony 标准系统HDF框架之I2C驱动开发

I2C 调试手段 —— 软件

  • 处理器支持多个 I2C 总线,确认 I2C 设备挂载的总线编号:Hi3516DV300 支持 8 路 I2C 总线,编号 0-7
    OpenHarmony 标准系统HDF框架之I2C驱动开发

  • 开启内核选项:CONFIG_I2C_CHARDEV(make menuconfig)
    OpenHarmony 标准系统HDF框架之I2C驱动开发

  • 使用 i2c_tools 工具包中的 i2c_detect 命令检测某条总线上挂载的所有设备
    OpenHarmony 标准系统HDF框架之I2C驱动开发

HDF 框架下的 I2C 设备驱动## HDF 框架下的 I2C 设备驱动 —— 案例描述

  • I2C 从设备:AT24C256、EEPROM、256Kb
  • A1 和 A2 两条管脚均接地,则 7 位设备地址为:1010000(0x50),8 位写地址:1010000(0xA0),8 位读地址:1010001(0xA1)
  • 写操作:用户态程序将字地址和数据发送给驱动程序,驱动程序将数据写入设备的字地址
  • 读操作:用户程序将字地址发送给驱动程序,驱动程序从指定的设备字地址读取数据,并将数据返回给用户态程序

具体操作(写操作):

  • 写操作:32KByte 空间,按照字节寻址,需要 15bit 字地址(7bit 高位 + 8bit 低位),字地址占用两个字节

OpenHarmony 标准系统HDF框架之I2C驱动开发

  • 起始信号、设备地址(bit0 = 0)、字地址(高字节)、字地址(低字节)、数据

具体操作(读操作):

  • 读操作:32KByte 空间,按照字节寻址,需要 15bit 字地址(7bit 高位 + 8bit 低位),字地址占用两个字节

OpenHarmony 标准系统HDF框架之I2C驱动开发

  • 起始信号、设备地址(bit0 = 0)、字地址(高字节)、字地址(低字节)
  • 起始信号、设备地址(bit0 = 1)、接收数据
  • 读操作中包含写操作

HDF 框架下的 I2C 设备驱动 —— 用户态程序

  • 应用程序通过服务名绑定驱动程序,和驱动建立联系
#define SAMPLE_SERVICE_NAME "at24_service"struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME);if(serv == NULL){    printf("fail to get service %s \n", SAMPLE_SERVICE_NAME);    return HDF_FAILURE;}

对应的 hcs 文件:

i2c_host :: host{    hostName = "my_i2c_test";    priority = 100;    device_i2c :: device { device0 :: deviceNode {     policy = 2;     priority = 100;     preload = 0;     permission = 0664;     moduleName = "at24_drv";     serviceName = "at24_service";     deviceMatchAttr = "at24_driver_attr"; }    }}
  • 用户态程序对驱动或设备的所有操作都基于服务
  • 用户态程序以字节为单位将数据写入设备
#define I2C_RD_CMD    456#define I2C_WR_CMD    789static int write _data (struct HdfIoService *serv, uint16_t addr, uint8_t value){    //用户态写操作    struct HdfSBuf *data = HdfSBufObtainDefault Size();    if(data == NULL){ HDF_LOGE("fail to obtain sbuf data"); ret = HDF_DEV_ERR_NO_MEMORY; goto out;    }    struct HdfSBuf *reply = HdfSBufObtainDefaultSize();    HdfSbufWriteUint16(data, addr);    HdfSbufWriteUint8(data, value);    serv->dispatcher->Dispatch(&serv->object, I2C_WR_CMD, data, reply);    HdfSbufReadString(reply);    printf("Get reply is : %s\n", str);}
  • 获取两个缓冲区 data 和 reply
  • 将字地址(15bit)和数据(8bit)写入 data 缓冲区
  • 调用 Dispatch 将字地址和数据发送给驱动
  • 读取驱动的返回值

用户态程序读操作:

  • 用户态程序以字节为单位从设备读取数据
#define I2C_RD_CMD    456#define I2C_WR_CMD    789static int write _data (struct HdfIoService *serv, uint16_t addr, uint8_t value){    //用户态读操作    struct HdfSBuf *data = HdfSBufObtainDefault Size();    if(data == NULL){ HDF_LOGE("fail to obtain sbuf data"); ret = HDF_DEV_ERR_NO_MEMORY; goto out;    }    struct HdfSBuf *reply = HdfSBufObtainDefaultSize();    HdfSbufWriteUint16(data, addr);    serv->dispatcher->Dispatch(&serv->object,I2C_RD_CMD, data, reply);    HdfSbufReadUint8(reply, pval);    HdfSbufReadString(reply);    printf("Get reply is : data 0x%hhx, str :%s\n", *pval,  str);}
  • 获取两个缓冲区 data 和 reply
  • 将字地址(15bit)写入 data 缓冲区
  • 调用 Dispatch 将字地址发送到驱动
  • 读取驱动的返回值

HDF 框架下的 I2C 设备驱动 —— 驱动程序入口

  • 驱动程序入口:
struct HdfDriverEntry g_SensorDriverEntry = {    .moduleVersion = 1,    .moduleName = "at24_drv",    .Bind = HdfSensorDriverBind,    .Init = HdfSensorDriverInit,    .Release = HdfSensorDriverRelease,}HDF_INIT(g_SensorDriverEntry);
  • device_info.hcs 定义设备节点
i2c_host :: host{    hostName = "my_i2c_test";    priority = 100;    device_i2c :: device { device0 :: deviceNode {     policy = 2;     priority = 100;     preload = 0;     permission = 0664;     moduleName = "at24_drv";     serviceName = "at24_service";     deviceMatchAttr = "at24_driver_attr"; }    }}

hcs 设备节点中定义了一个设备私有属性:deviceMatchAttr = “at24_driver_attr”;

static int32_t GetAT24ConfigData(const struct DeviceResourceNode *node){    struct DeviceResourceIface *parser = NULL;    const struct DeviceResourceNode *at24 = NULL;    parser = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);    at24 = parser->GetChildNode(node, "at24Attr");    parser->GetUint16(at24, "busId", &(tpDevice.busId), 0);    parser->GetUint16(at24, "addr", &(tpDevice.addr), 0);    parser->GetUint16(at24, "regLen", &(tpDevice.regLen), 0);    return HDF_SUCCESS;}int32_t HdfSensorDriverInit(struct HdfDeviceObject *deviceObject){    if(GetAT24ConfigData(deviceObject->property) != HDF_SUCCESS){ HDF_LOGE("%s: get at24 config fail!", __func__); return HDF_FAILURE;    }    if(at24_init() != HDF_SUCCESS){ HDF_LOGE("i2c at24 driver init failed!"); return -1;    }    HDF_LOGD("i2c at24 driver init success.");    return 0;}
  • 解析 hcs 配置文件中定义的属性 at24_driver_attr, 获取设备的私有属性的值
  • 初始化 i2c 从设备

设备私有属性(i2c_test_config.hcs)

root {    match_attr = "at24_driver_attr";    at24Attr {    //节点名字 at24Attr busId = 5;    //总线编号    5 addr = 0x50;    //设备地址    0x50 regLen = 2; //地址宽度    2字节    }}

全局配置文件(device_info.hcs)

i2c_host :: host{    hostName = "my_i2c_test";    priority = 100;    device_i2c :: device { device0 :: deviceNode {     policy = 2;     priority = 100;     preload = 0;     permission = 0664;     moduleName = "at24_drv";     serviceName = "at24_service";     deviceMatchAttr = "at24_driver_attr"; }    }}

HDF 框架下的 I2C 设备驱动 —— 设备初始化

  • 设备初始化
static int32_t at24_init(void){    tpDevice.i2cHandle = i2cOpen(tpDevice.busId);    return HDF_SUCCESS;}
功能分类 接口名 描述
I2C 控制器管理接口 I2cOpen 打开 I2C 控制器
I2cClose 关闭 I2C 控制器
i2c 消息传输接口 I2cTransfer 自定义传输

HDF 框架下的 I2C 设备驱动 —— 驱动 Dispatch

int32_t HdfSensorDriverDispatch(struct HdfDeviceIoClient *client, int id, struct HdfSBuf *data, struct HdfSBuf *reply){    uint16_t addr = 0;    uint8_t value = 0;    if(id == I2C_WR_CMD){ HdfSbufReadUint16(data, &addr); HdfSbufReadUint8(data, &value); TpI2cWriteReg(&tpDevice, addr, &value, 1); HdfSbufWriteString(reply, "write success");    }    else if(id == I2C_RD_CMD){HdfSbufReadUint16(data, &addr); TpI2cWriteReg(&tpDevice, addr, &value, 1); HdfSbufWriteUint8(reply, value); HdfSbufWriteString(reply, "read success");    }}

写数据:

  • 读取两个字节的字地址
  • 读取要写到字地址的数据
  • 执行写操作,参数 1 表示写一个字节数据
  • 返回值给用户程序

读数据:

  • 读取两个字节的字地址
  • 执行读操作,参数 1 表示读一个字节数据
  • 返回值给用户程序

HDF 框架下的 I2C 设备驱动 —— 驱动读写

struct TpI2cDevice{    uint16_t busId;    uint16_t addr;    uint16_t regLen;    DevHandle i2cHandle;}struct I2cMsg{    uin16_t addr;    //i2c 设备地址    uintt8_t *buf;    //缓存区    uint16_t len; //数据传输长度    uint16_t flags; //传输模式 flags,区分读写。}static struct TpI2cDevice tpDevice;static inline int TpI2cReadReg(struct TpI2cDevice *tpDevice, uint16_t regAddr, uint8_t *regData, uint32_t dataLen){    return TpI2cReadWrite()tpDevice, regAddr, regData, dataLen, 1);}static inline int TpI2cWriteReg(struct TpI2cDevice *tpDevice, uint16_t regAddr, uint8_t *regData, uint32_t dataLen){    return TpI2cReadWrite()tpDevice, regAddr, regData, dataLen, 0);}static int TpI2cReadWrite(struct TpI2cDevice *tpDevice, uint16_t regAddr, uint8_t *regData, uint32_t dataLen, uint8_t flaag){    int index = 0;    unsigned char regBuf[2] = {0};    struct I2cMsg msgs[2] = {0};    if(tpDevice->regLen == 1){ regBuf[index++] = regAddr & 0xFF;    }    else { regBuf[index++] = (regAddr >> 8 ) & 0xFF; regBuf[index++] = regAddr & 0xFF;    }    msgs[0].addr = tpDevice->addr;    msgs[0].flags = 0;    msgs[0].len = tpDevice->regLen;    msgs[0].buf = regBuf;    msgs[1].addr = tpDevice->addr;    msgs[1].flags = (flag == 1) ? I2C_FLAG_READ : 0;    msgs[1].len = dataLen;    msgs[1].buf = regData;    if(I2cTransfer(tpDevice->i2cHandle, msgs, 2) != 2) return HDF_FAILURE;    return HDF_SUCCESS;}
  • 总线编号:busId = 5
  • 设备地址:addr = 0x50
  • 地址宽度:2 字节

读操作:

  • 1 为读标志
  • 设备地址最低有效位为 1

写操作:

  • 0 为写标志
  • 设备地址最低有效位为 0

其中参数说明:

  • regAddr 和 regBuf 存放两个字节的字地址
  • dataLen 表示读写数据的字节长度
  • 读写操作的字地址作为数据写到从设备
  • regData 存放读写的数据
  • flags 区分读写操作
  • I2cTransfer 的返回值表示成功发送的 i2cMsg 数据包数量

总结

  • I2C 基础知识:概念和特性、4 个地址(设备地址、读地址、写地址、字地址)、波形(起始、结束、数据发送、数据接收)
  • I2C 调试手段:电压、上拉电阻、/dev/i2c-x、i2c-tools
  • HDF 框架 I2C 驱动:AT24C256 芯片按照字节寻址方式读写(按照页 64 字节寻址、连续读写)

影搜视频