RT-Thread 设备驱动I2C浅析及使用
由于 I2C 可以控制多从机的属性,设备驱动模型分为 I2C总线设备(类似与Linux里面的I2C适配器) + I2C从设备;
系统I2C设备驱动主要实现 I2C 总线设备驱动,而具体的I2C 从设备的实现则调用I2C总线设备ops
访问 I2C 总线设备
一般情况下 MCU 的 I2C 器件都是作为主机和从机通讯,在 RT-Thread 中将 I2C 主机虚拟为 I2C总线设备,I2C 从机通过 I2C 设备接口和 I2C 总线通讯,相关接口如下所示:
函数 | 描述 |
---|---|
rt_device_find() | 根据 I2C 总线设备名称查找设备获取设备句柄 |
rt_i2c_transfer() | 传输数据 |
使用方式参考官方文档即可,在此不做赘述。
驱动源码分析
i2c_core.c i2c总线协议控制的核心实现
i2c_dev.c i2c总线设备框架输线
i2c-bit-ops.c I/O模拟I2C的驱动实现
drv_soft_i2c.c I/O模拟I2C的底层硬件支持
先分析 I2C 总线设备注册的流程
在 drv_soft_i2c.c 中
INIT_BOARD_EXPORT(rt_hw_i2c_init);
则 OS 运行时会自启动 rt_hw_i2c_init 进行 模拟I2C 相关硬件IO的初始化
rt_hw_i2c_init -> rt_i2c_bit_add_bus -> rt_i2c_bus_device_register -> rt_i2c_bus_device_device_init -> rt_device_register
初始的配置
#ifdef BSP_USING_I2C1
#define I2C1_BUS_CONFIG \
{ \
.scl = BSP_I2C1_SCL_PIN, \
.sda = BSP_I2C1_SDA_PIN, \
.bus_name = "i2c1", \
}
#endif
这样使用时就可以通过 "i2c1" 来控制从设备了
I2C传输功能源码分析
rt_i2c_transfer -> i2c_bit_xfer
static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg msgs[],
rt_uint32_t num)
{
struct rt_i2c_msg *msg;
struct rt_i2c_bit_ops *ops = bus->priv;
rt_int32_t i, ret;
rt_uint16_t ignore_nack; bit_dbg("send start condition\n");
i2c_start(ops);
for (i = ; i < num; i++)
{
msg = &msgs[i];
ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
if (!(msg->flags & RT_I2C_NO_START)) // 没有RT_I2C_NO_START
{
if (i) // 主要用于读操作
{
i2c_restart(ops);
}
ret = i2c_bit_send_address(bus, msg); //发送器件地址
if ((ret != RT_EOK) && !ignore_nack)
{
bit_dbg("receive NACK from device addr 0x%02x msg %d\n",
msgs[i].addr, i);
goto out;
}
}
if (msg->flags & RT_I2C_RD) //读取数据
{
ret = i2c_recv_bytes(bus, msg);
if (ret >= )
bit_dbg("read %d byte%s\n", ret, ret == ? "" : "s");
if (ret < msg->len)
{
if (ret >= )
ret = -RT_EIO;
goto out;
}
}
else //发送数据
{
ret = i2c_send_bytes(bus, msg);
if (ret >= )
bit_dbg("write %d byte%s\n", ret, ret == ? "" : "s");
if (ret < msg->len)
{
if (ret >= )
ret = -RT_ERROR;
goto out;
}
}
}
ret = i; out:
bit_dbg("send stop condition\n");
i2c_stop(ops); return ret;
}
我们以 24c02 的 读写 来分析 i2C驱动
static rt_size_t at24cxx_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
struct at24cxx_device *at24cxx;
const struct at24cxx_config *cfg;
struct rt_i2c_msg msg[];
rt_uint8_t mem_addr[] = {,};
rt_size_t ret = ;
RT_ASSERT(dev != ); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != );
cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos > cfg->size)
{
return ;
} if(pos + size > cfg->size)
{
size = cfg->size - pos;
} // 写入寻址地址
msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR;
if(cfg->size < ) // at24c01 at24c02, 一页8字节,寻址地址8位
{
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
}
else // at24c04/08/16 一页16字节,寻址地址9/10/11位
{
mem_addr[] = (pos >> );
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
} // 使用RT_I2C_NO_START,直接写入buffer数据
msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR | RT_I2C_NO_START;
msg[].buf = (rt_uint8_t *) buffer;
msg[].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, );
return (ret == ) ? size : ;
}
static rt_size_t at24cxx_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
struct at24cxx_device *at24cxx;
const struct at24cxx_config *cfg;
struct rt_i2c_msg msg[];
rt_uint8_t mem_addr[] = {,};
rt_size_t ret = ;
RT_ASSERT(dev != ); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != );
cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos >= cfg->size) //寻址地址超标
{
return ;
} if(pos + size > cfg->size) // size超标
{
size = cfg->size - pos;
} msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR;
if(cfg->size < ) // at24c01 at24c02, 一页8字节,寻址地址8位
{
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
}
else // at24c04/08/16 一页16字节,寻址地址9/10/11位
{
mem_addr[] = (pos >> );
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
} msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_RD;
msg[].buf = (rt_uint8_t *) buffer;
msg[].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, );
return (ret == ) ? size : ; }
可以看到 i2c 读写 EEPROM 通过发送多个 msg 来实现 写寻址地址在进行读写操作,同时通过 RT_I2C_NO_START 使用读写场景
i2C设备应用实例
24c02设备实例代码
#include <rtthread.h>
#include <rtdevice.h>
#include "at24cxx.h" /** at24cxx设备结构体 */
struct at24cxx_device
{
struct rt_device parent;
struct rt_i2c_bus_device *bus;
}; /* RT-Thread device interface */
static rt_err_t at24cxx_init(rt_device_t dev)
{
return RT_EOK;
} static rt_err_t at24cxx_open(rt_device_t dev, rt_uint16_t oflag)
{
return RT_EOK;
} static rt_err_t at24cxx_close(rt_device_t dev)
{
return RT_EOK;
} static rt_err_t at24cxx_control(rt_device_t dev, int cmd, void *args)
{
return RT_EOK;
} /**
* @brief at24cxx设备读操作
* @param[in] dev 设备句柄
* @param[in] pos i2c写寻址地址
* @param[in] *buffer 读出数据的指针
* @param[in] size 读出数据的长度
* @return 返回读出成功的字节数
* - 0 读出失败
* - Others 读出成功的字节数
*/
static rt_size_t at24cxx_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
struct at24cxx_device *at24cxx;
const struct at24cxx_config *cfg;
struct rt_i2c_msg msg[];
rt_uint8_t mem_addr[] = {,};
rt_size_t ret = ;
RT_ASSERT(dev != ); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != );
cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos >= cfg->size) //寻址地址超标
{
return ;
} if(pos + size > cfg->size) // size超标
{
size = cfg->size - pos;
} msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR;
if(cfg->size < ) // at24c01 at24c02, 一页8字节,寻址地址8位
{
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
}
else // at24c04/08/16 一页16字节,寻址地址9/10/11位
{
mem_addr[] = (pos >> );
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
} msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_RD;
msg[].buf = (rt_uint8_t *) buffer;
msg[].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, );
return (ret == ) ? size : ; } /**
* @brief at24cxx设备写操作
* @param[in] dev 设备句柄
* @param[in] pos i2c写寻址地址
* @param[in] *buffer 写入数据的指针
* @param[in] size 写入数据的长度
* @return 返回写入成功的字节数
* - 0 写入失败
* - Others 写入成功的字节数
*/
#if 0 // 连续页写测试
static rt_size_t at24cxx_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
struct at24cxx_device *at24cxx;
const struct at24cxx_config *cfg;
struct rt_i2c_msg msg[];
rt_uint8_t mem_addr[] = {,};
rt_size_t ret = ;
RT_ASSERT(dev != ); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != );
cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos > cfg->size)
{
return ;
} if(pos + size > cfg->size)
{
size = cfg->size - pos;
} /*计算出要写的页数和分页*/
rt_uint8_t NumOfPage = ; // 总页写数,包括写的不完整页
rt_uint8_t Addr = ; // 页写的寻址地址
rt_uint8_t count = ; // 页写入的字节数
rt_uint8_t i; if(size > (PAGE_SIZE - pos%PAGE_SIZE))
{
count = PAGE_SIZE - pos%PAGE_SIZE; // 写入的第一页长度
NumOfPage = (size - count)/PAGE_SIZE; // 剩余写入的完整页数
if((size - (count + NumOfPage*PAGE_SIZE)) > )
NumOfPage = NumOfPage + ;
else
NumOfPage = NumOfPage + ;
}
else
{
NumOfPage = ;
} for(i=; i<NumOfPage; i++)
{
if(i == )
{
Addr = pos;
if(NumOfPage == )
{
count = size;
}
else
{
count = PAGE_SIZE - pos%PAGE_SIZE;
}
}
else if((i == NumOfPage-) && (NumOfPage > ))
{
Addr = pos - pos%PAGE_SIZE + i*PAGE_SIZE;
count = pos + size - Addr;
}
else
{
Addr = pos - pos%PAGE_SIZE + i*PAGE_SIZE;
count = PAGE_SIZE;
} // 写入寻址地址
msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR;
if(cfg->size < ) // at24c01 at24c02, 一页8字节,寻址地址8位
{
mem_addr[] = (rt_uint8_t) Addr;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
}
else // at24c04/08/16 一页16字节,寻址地址9/10/11位
{
mem_addr[] = (Addr >> );
mem_addr[] = (rt_uint8_t) Addr;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
} // 使用RT_I2C_NO_START,直接写入buffer数据
msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR | RT_I2C_NO_START;
msg[].buf = (rt_uint8_t *) buffer+count;
msg[].len = count; ret = rt_i2c_transfer(at24cxx->bus, msg, );
if(ret != )
return ;
}
return count;
}
#endif #if 1 // 不支持连续页写
static rt_size_t at24cxx_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
struct at24cxx_device *at24cxx;
const struct at24cxx_config *cfg;
struct rt_i2c_msg msg[];
rt_uint8_t mem_addr[] = {,};
rt_size_t ret = ;
RT_ASSERT(dev != ); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != );
cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos > cfg->size)
{
return ;
} if(pos + size > cfg->size)
{
size = cfg->size - pos;
} // 写入寻址地址
msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR;
if(cfg->size < ) // at24c01 at24c02, 一页8字节,寻址地址8位
{
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
}
else // at24c04/08/16 一页16字节,寻址地址9/10/11位
{
mem_addr[] = (pos >> );
mem_addr[] = (rt_uint8_t) pos;
msg[].buf = (rt_uint8_t *) mem_addr;
msg[].len = ;
} // 使用RT_I2C_NO_START,直接写入buffer数据
msg[].addr = cfg->addr;
msg[].flags = cfg->flags | RT_I2C_WR | RT_I2C_NO_START;
msg[].buf = (rt_uint8_t *) buffer;
msg[].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, );
return (ret == ) ? size : ;
}
#endif #ifdef RT_USING_DEVICE_OPS
/** at24cxx设备操作ops */
const static struct rt_device_ops at24cxx_ops =
{
at24cxx_init,
at24cxx_open,
at24cxx_close,
at24cxx_read,
at24cxx_write,
at24cxx_control
};
#endif /**
* @brief at24cxx设备注册
* @param[in] *fm_device_name 设备名称
* @param[in] *i2c_bus i2c总线设备名称
* @param[in] *user_data 用户数据 at24cxx_config
* @return 函数执行结果
* - RT_EOK 执行成功
* - Others 失败
*/
rt_err_t at24cxx_register(const char *fm_device_name, const char *i2c_bus, void *user_data)
{
static struct at24cxx_device at24cxx_drv;
struct rt_i2c_bus_device *bus; bus = rt_i2c_bus_device_find(i2c_bus);
if (bus == RT_NULL)
{
return RT_ENOSYS;
} at24cxx_drv.bus = bus;
at24cxx_drv.parent.type = RT_Device_Class_Block;
#ifdef RT_USING_DEVICE_OPS
at24cxx_drv.parent.ops = &at24cxx_ops;
#else
at24cxx_drv.parent.init = at24cxx_init;
at24cxx_drv.parent.open = at24cxx_open;
at24cxx_drv.parent.close = at24cxx_close;
at24cxx_drv.parent.read = at24cxx_read;
at24cxx_drv.parent.write = at24cxx_write;
at24cxx_drv.parent.control = at24cxx_control;
#endif at24cxx_drv.parent.user_data = user_data; return rt_device_register(&at24cxx_drv.parent, fm_device_name, RT_DEVICE_FLAG_RDWR);
}
/** at24cxx设备用户操作配置结构体 */
struct at24cxx_config
{
rt_uint32_t size; //设备的总容量
rt_uint16_t addr; //设备地址
rt_uint16_t flags; //I2C操作标志
}; /**
* @brief at24cxx设备注册
* @param[in] *fm_device_name 设备名称
* @param[in] *i2c_bus i2c总线设备名称
* @param[in] *user_data 用户数据 at24cxx_config
* @return 函数执行结果
* - RT_EOK 执行成功
* - Others 失败
*/
extern rt_err_t at24cxx_register(const char *e2m_device_name, const char *i2c_bus, void *user_data);
24c02设备驱动使用示例
static struct at24cxx_config at24c02_config =
{
.size = , // 容量,单位字节
.addr = 0x50, // 注意该地址为没有移位之前的地址不是0xA0
.flags = ,
}; static void at24c02_sample(int argc, char *argv[])
{
rt_err_t ret;
rt_uint8_t test_data[] = {0x12, 0x34, 0x56, 0x78};
rt_uint8_t tmp_data[]; // memset(test_data, 0x55, 100); ret = at24cxx_register("at24c02", "i2c1", &at24c02_config); rt_device_t at24c02_dev = rt_device_find("at24c02");
if (at24c02_dev == RT_NULL)
{
rt_kprintf("at24c02 sample run failed! can't find %s device!\n", "at24c02");
return;
}
rt_device_open(at24c02_dev, RT_DEVICE_FLAG_RDWR); ret = rt_device_write(at24c02_dev, , test_data, );
if(ret != )
{
rt_kprintf("at24c02 write error %d\n", ret);
}
rt_thread_mdelay();
ret = rt_device_read(at24c02_dev, , tmp_data, );
if(ret != )
{
rt_kprintf("at24c02 read error %d\n", ret);
}
else
{
rt_kprintf("at24c02 read data %02x %02x %02x %02x\n", tmp_data[], tmp_data[], tmp_data[], tmp_data[]);
}
}
RT-Thread 设备驱动I2C浅析及使用的更多相关文章
- RT thread 设备驱动组件之USART设备
本文以stm32f4xx平台介绍串口驱动,主要目的是:1.RTT中如何编写中断处理程序:2.如何编写RTT设备驱动接口代码:3.了解串行设备的常见处理机制.所涉及的主要源码文件有:驱动框架文件(usa ...
- RT-Thread 设备驱动SPI浅析及使用
OS版本:RT-Thread 4.0.0 测试BSP:STM32F407 SPI简介 SPI总线框架其实和I2C差不多,可以说都是总线设备+从设备,但SPI设备的通信时序配置并不固定,也就是说控制特定 ...
- linux设备驱动模型-浅析-转
1. typeof typeof并非ISO C的关键字,而是gcc对C的一个扩展.typeof是一个关键字(类似sizeof),用于获取一个表达式的类型. 举个简单的例子: char tt; typ ...
- RT-Thread 设备驱动UART浅析
OS版本:RT-Thread 4.0.0 芯片:STM32F407 RT-Thread的串口驱动框架与Linux相识,分成 I/O设备框架 + 设备底层驱动: 1. serial设备初始化及使用 将配 ...
- RT-Thread 设备驱动ADC浅析与改进
OS版本:RT-Thread 4.0.0 芯片:STM32F407 下面时官方ADC提供的参考访问接口 访问 ADC 设备 应用程序通过 RT-Thread 提供的 ADC 设备管理接口来访问 ADC ...
- 乾坤合一~Linux设备驱动之I2C核心、总线以及设备驱动
我思念的城市已是黄昏 为何我总对你一往情深 曾经给我快乐 也给我创伤 曾经给我希望 也给我绝望 我在遥远的城市 陌生的人群 感觉着你遥远的忧伤 我的幻想 你的忧伤,像我的的绝望,那样漫长,,,,,这是 ...
- Linux I2C核心、总线和设备驱动
目录 更新记录 一.Linux I2C 体系结构 1.1 Linux I2C 体系结构的组成部分 1.2 内核源码文件 1.3 重要的数据结构 二.Linux I2C 核心 2.1 流程 2.2 主要 ...
- LinuxI2C核心、总线驱动与设备驱动
I2C体系结构分为三个部分:I2C核心.总线驱动.设备驱动 I2C核心: I2C核心提供了一组不依赖硬件的接口函数,I2C总线驱动和设备驱动之间依赖于I2C核心作为纽带 (1)增加/删除i2c_ada ...
- RT Thread的SPI设备驱动框架的使用以及内部机制分析
注释:这是19年初的博客,写得很一般,理解不到位也不全面.19年末得空时又重新看了RTThread的SPI和GPIO,这次理解得比较深刻.有时间时再整理上传. -------------------- ...
随机推荐
- ndk编译libpcap 1.7.4(最终解决方法)
具体方法和测试见上一篇. 后来才知道,LIBPCAP本来就写了几套获取网卡的函数,这样就简单了.和上篇一样,把那5个文件从LINUX下托下来. 上篇忘了说了,还要将LINUX中/usr/include ...
- 洛谷 4364 [九省联考2018]IIIDX
[题解] 一眼可以想到一个类似二叉树后序遍历的贪心做法,然而这个做法在有相同数字的情况下是错误的.最简单的反例就是n=4,d={1,1,1,2},正解是1,1,2,1,而贪心是1,1,1,2. 所以这 ...
- 【Codeforces 522B】Photo to Remember
[链接] 我是链接,点我呀:) [题意] 题意 [题解] 模拟题.用set模拟下就好 [代码] import java.io.*; import java.util.*; public class M ...
- Contemplation! Algebra 矩阵快速幂
Given the value of a+b and ab you will have to find the value of a n + b n Input The input file cont ...
- Eureka集群的那些坑
今天遇到一个Eureka集群的一个坑. 问题现场类似是这样的:两台Eureka组成的服务注册中心集群,两台服务提供方server1.server2,两个服务调用方client1.client2. 正常 ...
- Ubuntu 16.04安装PDF阅读器FoxitReader
下载: https://www.foxitsoftware.cn/downloads/ 安装: tar zxvf FoxitReader2.4.1.0609_Server_x64_enu_Setup. ...
- SQL Server memory – Internals
https://blogs.msdn.microsoft.com/karthick_pk/2013/03/16/sql-server-memory-internals/
- subclipse 和 eclipse结合遇到的问题
subclipse是eclipse的一个SVN插件.但是我在使用的时候不断的报出下面的错误: the applet is attempting to access the "exists&q ...
- Windows 注册表常用操作
1 添加一个主键(比如在HKEY_LOCAL_MACHINE\SOFTWARE\中添加一个ABCEDFGHIJKLMN主键) Windows Registry Editor Version 5.00 ...
- iOS常用的宏定义总结
字符串是否为空 1 #define kStringIsEmpty(str) ([str isKindOfClass:[NSNull class]] || str == nil || [str le ...