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驱动开发_驱动开发_02

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

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

OpenHarmony 标准系统HDF框架之I2C驱动开发_设备驱动_03

  • 如果 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驱动开发_linux_04

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

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

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

I2C 调试手段 —— 软件

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

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

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

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驱动开发_harmonyos_09

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

具体操作(读操作):

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

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

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