OpenHarmony 标准系统HDF框架之I2C驱动开发
原创
©著作权归作者所有:来自51CTO博客作者acbbghhgf的原创作品,请联系作者获取转载授权,否则将追究法律责任
OpenHarmony 标准系统HDF框架之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 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);
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 字节
读操作:
写操作:
其中参数说明:
- regAddr 和 regBuf 存放两个字节的字地址
- dataLen 表示读写数据的字节长度
- 读写操作的字地址作为数据写到从设备
- regData 存放读写的数据
- flags 区分读写操作
- I2cTransfer 的返回值表示成功发送的 i2cMsg 数据包数量
总结
- I2C 基础知识:概念和特性、4 个地址(设备地址、读地址、写地址、字地址)、波形(起始、结束、数据发送、数据接收)
- I2C 调试手段:电压、上拉电阻、/dev/i2c-x、i2c-tools
- HDF 框架 I2C 驱动:AT24C256 芯片按照字节寻址方式读写(按照页 64 字节寻址、连续读写)