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 作为主设备,主从设备地位可以交换
I2C 是串行低速总线,常见传输速度如下:
- 标准模式(standard-mode):速率高达 100kbit/s
- 快速模式(fast-mode):速率 400kbit/s
- 快速模式+(fast-mode plus):速率 1Mbit/s
- 高速模式(high-speed mode):速率 3.4Mbit/s
工程中常见兼容标准模式和快速模式的 I2C 从设备
- 一条 I2C 总线上的所有从设备都有一个唯一的设备地址,不能与总线上的其他设备地址重复;
- 设备地址有 7 位和 10 位两种格式,常见 7 位格式
- I2C 主设备对从设备可执行写操作和读操作,通过写地址和读地址区分写操作和读操作
设备地址 7 位:101000(0x50)写地址 8 位:设备地址左移一位i,末位补 0 :1010000 (0xA0)读地址 8 位:设备地址左移一位,末位补 1: 1010001 (0xA1)同一个 I2C 设备可能具有多个设备地址,通常可通过从设备的管脚配置,以 I2C 接口的 ROM 芯片 AT24C256 为例:
- 如果 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 上读取数据
I2C 调试手段## I2C 调试手段 —— 硬件
- I2C 协议规定,在空闲状态下,总线为高电平:从设备工作电压 VDD,SDA 和 SCL 电压不低于 0.7VDD(低电平不高于 0.3VDD),常见的 VDD 有 1.8V、3.3V、5V 三种规格
- 高电平通过外挂上拉电阻实现,需要确保上拉电阻有效
I2C 调试手段 —— 软件
-
处理器支持多个 I2C 总线,确认 I2C 设备挂载的总线编号:Hi3516DV300 支持 8 路 I2C 总线,编号 0-7
-
开启内核选项:CONFIG_I2C_CHARDEV(make menuconfig)
-
使用 i2c_tools 工具包中的 i2c_detect 命令检测某条总线上挂载的所有设备
HDF 框架下的 I2C 设备驱动## HDF 框架下的 I2C 设备驱动 —— 案例描述
- I2C 从设备:AT24C256、EEPROM、256Kb
- A1 和 A2 两条管脚均接地,则 7 位设备地址为:1010000(0x50),8 位写地址:1010000(0xA0),8 位读地址:1010001(0xA1)
- 写操作:用户态程序将字地址和数据发送给驱动程序,驱动程序将数据写入设备的字地址
- 读操作:用户程序将字地址发送给驱动程序,驱动程序从指定的设备字地址读取数据,并将数据返回给用户态程序
具体操作(写操作):
- 写操作:32KByte 空间,按照字节寻址,需要 15bit 字地址(7bit 高位 + 8bit 低位),字地址占用两个字节
- 起始信号、设备地址(bit0 = 0)、字地址(高字节)、字地址(低字节)、数据
具体操作(读操作):
- 读操作:32KByte 空间,按照字节寻址,需要 15bit 字地址(7bit 高位 + 8bit 低位),字地址占用两个字节
- 起始信号、设备地址(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 字节寻址、连续读写)