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 作为主设备,主从设备地位可以交换

5d3b3482bab64b25bcfeed88b699eef5.png

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


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

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

ce04e26ab6814575978e2d1ebd867707.png

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

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

38c7cf8fe4194982bbcc3216edd3070e.png

  • 如果 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 上读取数据

3b1f4f7945ea435c88f51883999cf9d0.png

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

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

260cfce6e28d4dc483d387e7ed9b1528.png

I2C 调试手段 —— 软件

  • 处理器支持多个 I2C 总线,确认 I2C 设备挂载的总线编号:Hi3516DV300 支持 8 路 I2C 总线,编号 0-7

53707f118bc54c97b6eb911708869cfa.png

  • 开启内核选项:CONFIG_I2C_CHARDEV(make menuconfig)

7cd0f07eae7a47f3a29af9ba9053ccca.png

  • 使用 i2c_tools 工具包中的 i2c_detect 命令检测某条总线上挂载的所有设备

d6ecbafdcd474f7ab2ba9f5d764cae22.png

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

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

具体操作(写操作):


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

088af6946d8f4f8881b3c561a7496da4.png

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

具体操作(读操作):


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

6765cdaadbd14838ad7dcd6697f0cf3d.png

  • 起始信号、设备地址(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    789
static 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    789
static 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 字节寻址、连续读写)


相关文章
|
7月前
|
Linux C语言
Linux基础项目开发1:量产工具——程序框架(一)
Linux基础项目开发1:量产工具——程序框架(一)
112 0
Linux基础项目开发1:量产工具——程序框架(一)
|
编解码 人工智能 Linux
OpenHarmony 标准系统 HDF 框架音视频驱动开发
OpenHarmony 标准系统 HDF 框架音视频驱动开发
702 0
OpenHarmony 标准系统 HDF 框架音视频驱动开发
|
C语言 开发者 索引
OpenHarmony HDF 框架介绍
OpenHarmony HDF 框架介绍
755 0
OpenHarmony HDF 框架介绍
|
Linux 开发者
Linux嵌入式驱动开发——platform机制的使用(led驱动示例实现)
Linux嵌入式驱动开发——platform机制的使用(led驱动示例实现)
306 0
Linux嵌入式驱动开发——platform机制的使用(led驱动示例实现)
|
Linux 开发者
Linux设备驱动开发3-Kconfig构建系统
Linux设备驱动开发3-Kconfig构建系统
|
Linux 开发工具 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十八)驱动设计的思想:面向对象/分层/分离
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十八)驱动设计的思想:面向对象/分层/分离
169 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(二十八)驱动设计的思想:面向对象/分层/分离
|
存储 Ubuntu Unix
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(一) 嵌入式Linux开发基本概念以及开发流程介绍
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(一) 嵌入式Linux开发基本概念以及开发流程介绍
989 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(一) 嵌入式Linux开发基本概念以及开发流程介绍
|
缓存 移动开发 Linux
【从零开始的嵌入式生活】文件I/O1——标准I/O(1)
【从零开始的嵌入式生活】文件I/O1——标准I/O(1)
【从零开始的嵌入式生活】文件I/O1——标准I/O(1)
|
算法 Linux
【从零开始的嵌入式生活】文件I/O1——标准I/O(2)
【从零开始的嵌入式生活】文件I/O1——标准I/O(2)
【从零开始的嵌入式生活】文件I/O1——标准I/O(2)
|
编译器 C语言
QT应用编程: QtCreate编译部署开源音视频框架模块QtAV
QT应用编程: QtCreate编译部署开源音视频框架模块QtAV
213 0
QT应用编程: QtCreate编译部署开源音视频框架模块QtAV