ARM嵌入式学习(二十三)--- I2C总线和SPI总线

目录

一、IIC

1、总体框架

2、核心概念详解

(1)Adapter(适配器)

(2)Client(客户端)

[(3) Driver(设备驱动)](#(3) Driver(设备驱动))

[(4) master_xfer()](#(4) master_xfer())

[(5) IIC Core(核心层)](#(5) IIC Core(核心层))

3、设备与驱动分离原理

4、总线驱动与设备驱动分层原理

分层架构

通信流程示例

5.代码为:

6、与Platform总线对比

二、SPI总线

和IIC相同,定义一个全局变量

结构体中的变量赋值为:

初始化和退出也和IIC一样的:

最终代码为:

三、总结


一、IIC

1、总体框架

IIC子系统是Linux内核为了统一管理IIC(I²C)总线设备而设计的一套驱动框架。其核心目标有两个:

(1)设备与驱动分离:硬件设备(如温度传感器LM75)的描述与功能驱动代码分离,通过总线匹配。

(2)总线驱动与设备驱动分层:底层IIC控制器(硬件时序)与上层具体设备业务逻辑解耦。

结合图片,整个框架从下至上分为:

  • 硬件层:IIC控制器硬件(IIC0、IIC1)

  • 内核层

    • 适配器驱动(adapter):实现master_xfer(),负责IIC协议时序

    • IIC核心层(core):提供通用API,管理设备/驱动匹配

    • 设备驱动层(如lm75驱动):实现具体设备功能(如lm75_read()

  • 用户空间 :可通过/dev/i2c-0(适配器节点)或/dev/lm75(具体设备节点)访问


2、核心概念详解

(1)Adapter(适配器)
  • 本质:代表一个IIC控制器(即一条IIC总线)。

  • 对应内核结构struct i2c_adapter

  • 关键成员master_xfer()函数指针 ------ 真正执行IIC传输的方法(产生起始、停止、时钟、数据收发)。

  • 关联硬件 :每个SoC的IIC接口(如i2c0)对应一个adapter。

(2)Client(客户端)
  • 本质:代表挂接在IIC总线上的一个从设备。

  • 对应内核结构struct i2c_client

  • 关键信息 :从设备地址(slave addr)、所在的adapter。

  • 来源:通常由设备树(dts)中的IIC子节点解析生成。例如:

    dts

    复制代码
    &i2c0 {
        lm75@48 {
            compatible = "national,lm75";
            reg = <0x48>;   // 从机地址
        };
    };

    内核解析后创建i2c_client,绑定到adapter0,地址为0x48。

(3) Driver(设备驱动)
  • 本质:驱动具体IIC设备(如LM75温度传感器)的程序。

  • 对应内核结构struct i2c_driver

  • 核心函数probe()(匹配成功后调用)、remove()

  • 匹配方式 :通过id_tableof_match_tablei2c_clientcompatible匹配。

  • 通信方法 :调用i2c_transfer()或SMBus函数,最终由适配器的master_xfer()完成硬件传输。

(4) master_xfer()
  • 这是适配器驱动必须实现的底层函数。

  • 功能:按照IIC协议,在总线上执行真正的数据传输(包括发送从机地址、读写标志、数据字节、应答位等)。

  • 上层设备驱动完全不关心时序细节,只需调用i2c_transfer()

(5) IIC Core(核心层)
  • 承上启下:提供i2c_transfer()i2c_add_adapter()i2c_add_driver()等统一接口。

  • 负责管理adapterclient的注册/注销、设备与驱动的匹配、锁定、sysfs节点创建等。

  • 使得adapter驱动和client驱动可以独立编译和加载。


3、设备与驱动分离原理

这是Linux设备模型的通用思想,在IIC子系统中具体表现为:

(1)静态描述(设备) :通过设备树或ACPI描述IIC设备的硬件属性(挂载在哪个adapter、从机地址、中断等)。内核启动时自动生成i2c_client对象。

(2)动态代码(驱动) :开发者编写i2c_driver,只关心设备功能逻辑(如读取温度、转换数值)。

(3)匹配与绑定 :当注册一个i2c_driver时,IIC Core会遍历所有已有的i2c_client,检查是否匹配(通常看compatible或地址)。匹配成功则调用驱动的probe(),驱动获得i2c_client指针,之后可通过它进行通信。

优点

  • 同一设备(如LM75)的驱动可以支持多个不同IIC地址或不同硬件版本,无需修改驱动。

  • 多个不同设备(LM75、AT24C08)可以共用同一个adapter,而互不干扰。


4、总线驱动与设备驱动分层原理

分层架构
层次 职责 示例
用户空间 通过文件接口访问设备 open("/dev/i2c-0")open("/dev/lm75")
设备驱动层 实现具体设备的业务逻辑(如温度换算) lm75_read() 调用 i2c_transfer()
IIC核心层 提供传输API、管理适配器和客户端、匹配机制 i2c_transfer() → 找到对应adapter → 调用其master_xfer
适配器驱动层 实现硬件相关的IIC时序 master_xfer() 操作寄存器产生SCL/SDA信号
硬件层 实际IIC控制器 IIC0, IIC1
通信流程示例

用户读取LM75温度:

  1. 应用调用read(fd_lm75, ...)

  2. LM75设备驱动的read函数构造一个读命令,调用i2c_transfer(client, ...)

  3. IIC Core根据client->adapter找到对应的适配器,调用其master_xfer()

  4. 适配器驱动操作硬件寄存器,产生IIC起始信号、发送地址0x48+读位、读取两个字节温度值、发送停止信号

  5. 数据原路返回至用户空间

分层的好处

  • 更换SoC的IIC控制器时,只需重写适配器驱动,所有设备驱动完全不动。

  • 新增一个IIC设备时,只需实现设备驱动,无需关心底层硬件差异。

5.代码为:

复制代码
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/i2c.h>

#define DEV_NAME "lm75"
static struct i2c_client * pclient;

static int open(struct inode * node, struct file * file)
{
	printk("lm75  open...\n");
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	int ret = 0;
	unsigned char data[2] = {0};
	unsigned short temp = 0;
	struct i2c_msg msg = 
	{
		.addr = pclient->addr,
		.flags = I2C_M_RD,
		.len = 2,
		.buf = data 
	};

	ret = pclient->adapter->algo->master_xfer(pclient->adapter, &msg, 1);
	if(ret < 0)
		return ret;
	temp = 	((data[0]<<8) | data[1]);
	temp = temp >> 5 ;
	ret = copy_to_user(buf, &temp, sizeof(temp));
	printk("lm75  read...\n");
	return ret;
}

static int close(struct inode * node, struct file * file)
{
	printk("lm75  close...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.release = close
};

static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_NAME,
	.fops = &fops
};

static int probe(struct i2c_client * client, const struct i2c_device_id * id)
{
	int ret = misc_register(&misc);
	if(IS_ERR_VALUE(ret))
		goto err_misc;

	pclient = client;	

	printk("lm75 probe misc_deregister   slave addr = 0x%02x...\n", pclient->addr);
	return 0;

err_misc:
	printk("lm75  probe err  ret = %d\n", ret);
	misc_deregister(&misc);
	return ret;
}

static int remove(struct i2c_client * client)
{
	misc_deregister(&misc);
    printk("remove  lm75 misc_deregister    ##############\n");

	return 0;
}
	
static const struct of_device_id match_table[] = 
{
	[0] = {.compatible = "ti,lm75"}
};

static const struct i2c_device_id lm75_id_table[] = 
{
	[0]	= {.name = "ti,lm75"}
};

static struct i2c_driver lm75_driver = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME,
		.of_match_table = match_table
	},
	.id_table = lm75_id_table
};

static int __init lm75_init(void)
{
	int ret = i2c_add_driver(&lm75_driver);
	if(ret < 0)
		goto err_reg;

	printk("lm75  i2c_add_driver  ...\n");
	return 0;

err_reg:
	printk("lm75  i2c_add_driver  err  ...\n");
	i2c_del_driver(&lm75_driver);
	return ret;
}

static void __exit lm75_exit(void)
{
	i2c_del_driver(&lm75_driver);
	printk("lm75  i2c_del_driver  ...\n");
}

module_init(lm75_init);
module_exit(lm75_exit);
MODULE_LICENSE("GPL");

6、与Platform总线对比

特性 IIC子系统 Platform总线
物理存在 真实物理总线(SCL/SDA) 虚拟总线,无物理对应
典型设备 IIC从设备(传感器、EEPROM) 片上控制器(UART、SPI、IIC控制器本身)
核心数据结构 i2c_adapter, i2c_client, i2c_driver platform_device, platform_driver
传输方式 必须通过master_xfer()遵循IIC协议 直接读写内存映射寄存器(readl/writel
地址概念 每个设备有从机地址(7/10位) 无设备地址,只有物理地址/中断号
分层情况 严格分层(设备驱动/核心/适配器驱动) 通常不分层,驱动直接操作硬件
典型应用场景 挂载在IIC总线上的外设 CPU集成的IP核(如IIC控制器、GPIO控制器)

二、SPI总线

和IIC相同,定义一个全局变量

结构体中的变量赋值为:

初始化和退出也和IIC一样的:

最终代码为:

复制代码
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spi/spi.h>

#define DEV_NAME "adxl345"
static struct spi_device * pspi;

static int open(struct inode * node, struct file * file)
{
	printk("adxl345  open...\n");
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	printk("adxl345  read...\n");
	return 0;
}

static int close(struct inode * node, struct file * file)
{
	printk("adxl345  close...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.release = close
};

static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_NAME,
	.fops = &fops
};

static int probe(struct spi_device * spi)
{
	int ret;
	unsigned char buf[2];

	unsigned char tx_id[2] = {0x80 | 0x00, 0x00};
	unsigned char rx_id[2] = {0};
	// 1. 设置 SPI 模式 (ADXL345 通常使用 Mode 3)
	spi->mode = SPI_MODE_3;          // CPOL=1, CPHA=1
	spi->bits_per_word = 8;

	ret = misc_register(&misc);
	if(IS_ERR_VALUE(ret))
		goto err_misc;

	pspi = spi;	

	printk("adxl345 probe misc_deregister#\n");
	return 0;

err_misc:
	printk("adxl345  probe err  ret = %d\n", ret);
	misc_deregister(&misc);
	return ret;
}

static int remove(struct spi_device * spi)
{
	misc_deregister(&misc);
	printk("remove  adxl345 misc_deregister    ##############\n");

	return 0;
}

static const struct of_device_id match_table[] = 
{
	[0] = {.compatible = "ti,adxl345"}
};

static const struct spi_device_id adxl345_id_table[] = 
{
	[0]	= {.name = "adxl345"}
};


static struct spi_driver adxl345_driver = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME,
		.of_match_table = match_table
	},
	.id_table = adxl345_id_table
};

static int __init adxl345_init(void)
{
	int ret = spi_register_driver(&adxl345_driver);
	if(ret < 0)
		goto err_reg;

	printk("adxl345  spi_register_driver  ...\n");
	return 0;

err_reg:
	printk("adxl345  spi_register_driver  err  ...\n");
	spi_unregister_driver(&adxl345_driver);
	return ret;
}

static void __exit adxl345_exit(void)
{
	spi_unregister_driver(&adxl345_driver);
	printk("adxl345  spi_del_driver  ...\n");
}

module_init(adxl345_init);
module_exit(adxl345_exit);
MODULE_LICENSE("GPL");

三、总结

掌握IIC和SPI的写法就可以了

相关推荐
饭后一颗花生米2 小时前
2026 AI加持下前端学习路线:从入门到进阶,高效突破核心竞争力
前端·人工智能·学习
北山有鸟2 小时前
【学习笔记】MIPI CSI-2 协议全解析:从底层封包到像素解析
linux·驱动开发·笔记·学习·相机
YCY^v^3 小时前
PSW、PFW、SPSW、SPFW 是信捷TouchWin
学习
Engineer邓祥浩4 小时前
JVM学习笔记(13) 第五部分 高效并发 第12章 Java内存模型与线程
jvm·笔记·学习
我命由我123454 小时前
程序员的心理学学习笔记 - 反刍思维
经验分享·笔记·学习·职场和发展·求职招聘·职场发展·学习方法
GISer_Jing4 小时前
AI知识学习
人工智能·redis·学习
星辰_mya4 小时前
PV之系统与并发的核心wu器
java·开发语言·后端·学习·面试·架构师
暗夜猎手-大魔王4 小时前
learn-claude-code项目学习总结
学习
热爱生活的五柒6 小时前
度量学习-Radar Signal Deinterleaving Using Transformer Encoder and HDBSCAN 论文解析
深度学习·学习·transformer