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,这次理解得比较深刻.有时间时再整理上传. -------------------- ...
随机推荐
- 关于vuex自己理解的三幅图
- 洛谷 2176 [USACO14FEB]路障Roadblock
[题意概述] 修改图中任一一条边的边权,使其加倍,问怎样使修改后图中的1~n的最短路最大.输出最短路的增量. [题解] 先跑一遍dijkstra求出1~n的路径长度,记录下经过的边.枚举这些边进行修改 ...
- 洛谷 1071 潜伏者(NOIp2009提高组)
[题意概述] 给出三行字符串,前两行代表密码与明文的对应关系,第三行为待翻译的文本.要求按照对应关系翻译文本. [题解] 直接模拟即可. 注意判断Failed的情况. #include<cstd ...
- idea中找不到maven projects的集中解决办法
今天正常打开idea,却发现maven窗口找不到了:试了这些方法 首先idea自带了maven控件,不像Eclipse还需要下载控件,如果你以前有maven在右边,出于某种原因,消失找不到 了,你可以 ...
- python之MD5、base64\base32解密
# -*- coding:utf-8 -*- import hashlib import base64 # 求最大公约数gys # def gys(m, n): # c = 1 # while(c ! ...
- hdu 5015 矩阵快速幂(可用作模板)
转载:http://blog.csdn.net/wdcjdtc/article/details/39318847 之前各种犯傻 推了好久这个东西.. 后来灵关一闪 就搞定了.. 矩阵的题目,就是构造 ...
- jQuery对象是怎么创建的
一.jQuery源码 在jQuery中,$是jQuery的别名,执行“$()”就是执行“jQuery()”,执行“$()”返回的是一个jQuery对象,在源码中,它是这样定义的: ... var jQ ...
- Java使用JNative调用DLL库
编写测试DLL文件,源文件参照JNA调用用的DLL文件.地址:http://www.cnblogs.com/vevy/p/9076941.html (很重要)右键项目 --> 属性 --> ...
- SVM学习(续)核函数 & 松弛变量和惩罚因子
SVM的文章可以看:http://www.cnblogs.com/charlesblc/p/6193867.html 有写的最好的文章来自:http://www.blogjava.net/zhenan ...
- ExtJs--13-- Ext.apply(src,apply) 和 Ext.applyIf(src,apply) 两个方法的使用和差别比較
Ext.onReady(function(){ /* * Ext.apply(src,apply) 和 Ext.applyIf(src,apply) 两个方法的使用和差别比較 */ //Ext.app ...