嵌入式驱动学习第七周——I2C子系统

前言

   I2C子系统详解,本篇博客从内核源码的角度来看I2C子系统。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

I2C介绍

   I2C支持一主多从,各设备地址独立,标准模式传输速率为100kbit/s,快速模式为400kbit/s。总线通过上拉电阻连接到电源,当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把电线拉成高电平。

在这里插入图片描述

   I2C物理总线使用两条总线线路,SCL(时钟线,数据收发同步)和SDA(数据线,传输具体数据)。

I2C通信协议

起始信号和停止信号

   在 SCL 为高电平的时候,SDA 出现下降沿就表示为起始位。

在这里插入图片描述

   和起始位的功能相反。在 SCL 位高电平的时候,SDA出现上升沿就表示为停止位。

在这里插入图片描述

应答信号

   I2C主机发送完8位数据后会将SDA设置为输入状态,等待I2C从机应答,即等到I2C从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完 8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信号,表示通信成功,否则表示通信失败。

数据传输

   I2C的数据字节定义为8-bits长度,对每次传送的总字节数量没有限制,但对每一次传输必须伴有应答信号。I2C 总线在数据传输的时候要保证在 SCL 高电平期间,SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生。

在这里插入图片描述

主机与从机通信

  • 开始标志(S)发出后,主设备会传送一个7位的Slave地址,并且后面跟着一个第8位,称为Read/Write位。R/W位表示主设备是在接受从设备的数据还是在向其写数据。
  • 然后,主设备释放SDA线,等待从设备的应答信号(ACK)每个字节的传输都要跟随有一个应答位。应答产生时,从设备将SDA线拉低并且在SCL为高电平时保持低。
  • 数据传输总是以停止标志(P)结束,然后释放通信线路。 然而,主设备也可以产生重复的开始信号去操作另一台从设备,而不发出结束标志。综上可知,所有的SDA信号变化都要在SCL时钟为低电平时进行,除了开始和结束标志

在这里插入图片描述

   先介绍几个缩写:

简写含义
S起始信号
P结束信号
AD地址数据
W写标志
R读标志
ACK应答信号
NACK拒绝应答信号
RA要写入或读取的寄存器地址
DATA传输的数据

   单字节写数据:

在这里插入图片描述

   多字节写数据:
在这里插入图片描述

   举个通俗的例子来理解这个过程,现在老师和学生要打电话取得沟通

老师开始通信——主机发送S,表示开始
老师找到学生的微信,并点击音频通话按钮——主机发送AD+W
学生接通电话——从机发送ACK
老师问:是xxx嘛——主机发送RA
学生说:是我——从机发送ACK
然后老师开始说:两件事情,第一件事xxxx——主机发送DATA
学生说:好的老师——从机发送ACK
然后老师说:第二件事xxx——主机发送DATA
学生说:好的老师——从机发送ACK
老师挂断电话——主机发送停止信号P

   单字节读数据(第一个是W是因为后面需要向设备写入我要读取哪个寄存器):

在这里插入图片描述

   多字节读数据:

在这里插入图片描述

   这个再举老师的例子不合适了,因为老师作为主机举出来的例子很奇怪,但是也不能把学生当做主机,因为在I2C中,只有主机能发S信号,如果把学生作为主机,就会产生一种错觉,就是设备与主机之间能相互做主机,显然是很不对的。因此换一个里子,现在王先生去参加一个学术会议,会议的会务要找他获取一些报名信息

会务想要找王先生沟通——主机发送S信号
会务找到王先生的电话并拨打过去——主机发送AD+W信号
王先生接到电话——从机发送ACK
会务说:您好,我们这边需要向您获取一些报名信息——主机发送RA,表示要读取信号的位置
王先生说:好的——从机发送ACK
会务说:那我们开始了哈——主机发送开始信号
会务说:再次确认,您是王xx先生吗,是的话请您告诉我们所在单位,发票抬头——主机发送地址、读取标志
王先生:好的——从机发送ACK
王先生:我的单位是xxx——从机发送data
会务说:好的——主机发送ACK
王先生:发票抬头是xxx——从机发送data
会务说:好的,我们这边登记完成——主机发送NACK,表示数据读取完成
会务挂断电话——主机发送P

I2C驱动框架

   在裸机开发I2C驱动时,需要根据I2C协议手动配置I2C控制寄存器能够输出起始信号、停止信号、数据信息等。但是Linux中采用的是总线——设备——驱动模型。基于此想法,我们需要两个驱动,一个是I2C驱动,一个是设备驱动,比如用I2C读取mpu6050数据的时候,就需要一个mpu6050驱动。

在这里插入图片描述

   i2c驱动框架包括i2c总线驱动、具体某个设备的驱动。

   I2C总线又包括I2C设备和I2C驱动,当向Linux中注册设备或驱动的时候,按照I2C总线匹配规则进行匹配,配对成功的话,就可以通过I2C_driver中的probe函数创建具体的设备驱动。I2C设备不需要手动创建,而是利用设备树,设备树节点与platform总线相配合使用,所以需要先对I2C总线包装一层platform总线,当设备树节点转换为平台总线设备时,再进一步将其转换为I2C设备,注册到I2C总线中。

关键数据结构

i2c_adapter

   这是一个I2C控制器,用于标识物理I2C总线以及访问它所需的访问算法的结构,其定义在 include/linux/i2c.h文件内

struct i2c_adapter {
	struct module *owner;
	unsigned int class;		  /* classes to allow probing for */
	const struct i2c_algorithm *algo; /* the algorithm to access the bus */
	void *algo_data;

	/* data fields that are valid for all devices	*/
	struct rt_mutex bus_lock;

	int timeout;			/* in jiffies */
	int retries;
	struct device dev;		/* the adapter device */

	int nr;
	char name[48];
	struct completion dev_released;

	struct mutex userspace_clients_lock;
	struct list_head userspace_clients;

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;
};

   这里面有两个很重要:

  • const struct i2c_algorithm *algo:这是一个i2c_algorithm结构体,访问总线的算法;
  • struct device dev:表明这是一个设备

struct i2c_algorithm

   这是一个硬件解决方案的接口,这些解决方案可以使用相同的总线算法(碰撞检测等)来解决。下面来看看这个结构体的定义,路径在:include/linux/i2c.h

struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
	int (*reg_slave)(struct i2c_client *client);
	int (*unreg_slave)(struct i2c_client *client);
#endif
};
  • master_xfer:该函数表示作为主设备时,应该返回成功处理的消息数,或者在出错时,返回负数值。
  • smbus_xfer:该函数表示作为从设备时的发送函数

struct i2c_client

   该结构体表示I2C从设备,即MPU6050等,其同样定义在include/linux/i2c.h 中:

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};
  • flags:I2C_CLIENT_TEN表示设备使用10位芯片地址,I2C_CLIENT_PEC表示它使用SMBus数据包错误检查;
  • addr:连接到I2C总线上的地址
  • name:表示设备的类型,通常是芯片名
  • adapter:struct i2c_adapter 结构体,管理托管这个I2C设备的总线段
  • dev:Driver model设备节点
  • init_irq:作为从设备时的发送函数
  • irq:表示该设备生成的中断号
  • detected:一个列表,因为I2C是多从机,这些从机的管理可以用一个链表来管理
  • slave_cb:使用适配器的I2C从模式时回调。适配器调用它来将从属事件传递给从属驱动程序。i2c_client识别连接到i2c总线的单个设备(即芯片)。

struct i2c_driver

   I2C设备驱动,其在内核中的定义同样在include/linux/i2c.h 中:

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared. You should avoid
	 * using this, it will be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

   来看以下几个成员变量:

  • probe:i2c设备和i2c驱动匹配后,回调该函数指针
  • id_table:struct i2c_device_id *类型,是要匹配的从设备信息
  • address_list:设备地址
  • clients:设备链表
  • detect:设备探测函数

I2C总线驱动

   现在来看看I2C总线的运行机制:

  1. 注册I2C总线
  2. 将I2C驱动添加到I2C总线的驱动链表中
  3. 遍历I2C总线上的链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数
  4. i2c_device_probe函数会调用I2C驱动的probe函数

   I2C总线定义在drivers/i2c/i2c-core.c中:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

   i2c总线维护着两个链表(I2C驱动、I2C设备),管理I2C设备和I2C驱动的匹配和删除等

i2c总线注册

   linux启动之后,默认执行i2c_init,其函数定义在drivers/i2c/i2c-core.c中:

static int __init i2c_init(void)
{
	int retval;
	...
	retval = bus_register(&i2c_bus_type);				// 注册总线I2C_bus_type
	if (retval)
		return retval;
#ifdef CONFIG_I2C_COMPAT
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");
	if (!i2c_adapter_compat_class) {
		retval = -ENOMEM;
		goto bus_err;
	}
#endif
	retval = i2c_add_driver(&dummy_driver);				// 注册I2C驱动dummy_driver
	if (retval)
		goto class_err;

	if (IS_ENABLED(CONFIG_OF_DYNAMIC))
		WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));

	return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
	class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
	bus_unregister(&i2c_bus_type);
	return retval;
}

i2c设备和i2c驱动匹配规则

   其定义在drivers/i2c/i2c-core.c

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}
  • of_driver_match_device: 设备树匹配方式,比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性
  • acpi_driver_match_device: ACPI 匹配方式
  • i2c_match_id: i2c总线传统匹配方式,比较 I2C设备名字和 i2c驱动的id_table->name 字段是否相等

   I2C驱动入口函数定义在drivers/i2c/busses/i2c-imx.c中:

static int __init i2c_adap_imx_init(void)
{
	return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);

static void __exit i2c_adap_imx_exit(void)
{
	platform_driver_unregister(&i2c_imx_driver);
}
module_exit(i2c_adap_imx_exit);

   入口函数和出口函数都在外面封了一层platform驱动,接下来来看这个platform驱动结构体i2c_imx_driver

static struct platform_driver i2c_imx_driver = {
	.probe = i2c_imx_probe,
	.remove = i2c_imx_remove,
	.driver	= {
		.name = DRIVER_NAME,
		.owner = THIS_MODULE,
		.of_match_table = i2c_imx_dt_ids,
		.pm = IMX_I2C_PM,
	},
	.id_table	= imx_i2c_devtype,
};

   匹配列表为i2c_imx_dt_ids,用于和设备树节点匹配:

static const struct of_device_id i2c_imx_dt_ids[] = {
	{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
	{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
	{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
	{ /* sentinel */ }
};

   接下来看probe函数:

static int i2c_imx_probe(struct platform_device *pdev)
{
	const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids,
							   &pdev->dev);
	struct imx_i2c_struct *i2c_imx;
	struct resource *res;
	struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);
	void __iomem *base;
	int irq, ret;
	dma_addr_t phy_addr;

	dev_dbg(&pdev->dev, "<%s>\n", __func__);

	irq = platform_get_irq(pdev, 0);							// 获取中断号
	if (irq < 0) {
		dev_err(&pdev->dev, "can't get irq number\n");
		return irq;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);		// 获取reg属性,该函数实现的功能与of函数获取reg属性相同
	base = devm_ioremap_resource(&pdev->dev, res);				// 获取I2C的基地址后,用该函数转为虚拟地址
	if (IS_ERR(base))
		return PTR_ERR(base);

	phy_addr = (dma_addr_t)res->start;
	i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);	// 为i2c_imx申请内存空间
	if (!i2c_imx)
		return -ENOMEM;

	if (of_id)
		i2c_imx->hwdata = of_id->data;
	else
		i2c_imx->hwdata = (struct imx_i2c_hwdata *)
				platform_get_device_id(pdev)->driver_data;

	/* Setup i2c_imx driver structure */
	strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));			// 这部分是初始化i2c_imx结构体,在程序中代表一个实际的i2c总线。
	i2c_imx->adapter.owner		= THIS_MODULE;
	i2c_imx->adapter.algo		= &i2c_imx_algo;			// 特别需要关心的是该成员,其代表了访问总线的算法
	i2c_imx->adapter.dev.parent	= &pdev->dev;
	i2c_imx->adapter.nr		= pdev->id;
	i2c_imx->adapter.dev.of_node	= pdev->dev.of_node;
	i2c_imx->base			= base;

	/* Get I2C clock */
	i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(i2c_imx->clk)) {
		dev_err(&pdev->dev, "can't get I2C clock\n");
		return PTR_ERR(i2c_imx->clk);
	}

	ret = clk_prepare_enable(i2c_imx->clk);
	if (ret) {
		dev_err(&pdev->dev, "can't enable I2C clock\n");
		return ret;
	}
	/* Request IRQ */
	ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,
			       IRQF_NO_SUSPEND, pdev->name, i2c_imx);
	if (ret) {
		dev_err(&pdev->dev, "can't claim irq %d\n", irq);
		goto clk_disable;
	}

	/* Init queue */
	init_waitqueue_head(&i2c_imx->queue);

	/* Set up adapter data */
	i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);

	/* Set up clock divider */
	i2c_imx->bitrate = IMX_I2C_BIT_RATE;
	ret = of_property_read_u32(pdev->dev.of_node,
				   "clock-frequency", &i2c_imx->bitrate);
	if (ret < 0 && pdata && pdata->bitrate)
		i2c_imx->bitrate = pdata->bitrate;

	/* Set up chip registers to defaults */
	imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,
			i2c_imx, IMX_I2C_I2CR);
	imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR);

	/* Add I2C adapter */
	ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
	if (ret < 0) {
		dev_err(&pdev->dev, "registration failed\n");
		goto clk_disable;
	}

	/* Set up platform driver data */
	platform_set_drvdata(pdev, i2c_imx);
	clk_disable_unprepare(i2c_imx->clk);

	dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq);
	dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res);
	dev_dbg(&i2c_imx->adapter.dev, "adapter name: \"%s\"\n",
		i2c_imx->adapter.name);
	dev_info(&i2c_imx->adapter.dev, "IMX I2C adapter registered\n");

	/* Init DMA config if supported */
	i2c_imx_dma_request(i2c_imx, phy_addr);

	return 0;   /* Return OK */

clk_disable:
	clk_disable_unprepare(i2c_imx->clk);
	return ret;
}

   接下来看probe函数,整个.prob函数完成了两个主要工作:

  • 第一,初始化i2c1硬件
  • 第二,初始化一个“代表i2c1”的i2c_adapter结构体,并将其添加到系统中。

   .probe函数完成了i2c的基本初始化并将其添加到了系统中。i2c总线驱动的另外一个重要工作就是实现i2c对外接口函数。 我们在初始化i2c_adapter结构体时已经初始化了访问总线算法结构体i2c_adapter->i2c_algorithm。 具体代码为 i2c_imx->adapter.algo = &i2c_imx_algo

   i2c_imx是一个imx_i2c_struct类型的结构体,该结构体大多数是用于保存i2c硬件信息,其定义如下:

struct imx_i2c_struct {
	struct i2c_adapter	adapter;
	struct clk		*clk;
	void __iomem		*base;
	wait_queue_head_t	queue;
	unsigned long		i2csr;
	unsigned int		disable_delay;
	int			stopped;
	unsigned int		ifdr; /* IMX_I2C_IFDR */
	unsigned int		cur_clk;
	unsigned int		bitrate;
	const struct imx_i2c_hwdata	*hwdata;

	struct imx_i2c_dma	*dma;
};
  • clk: clk结构体保存时钟相关信息
  • bitrate: 保存i2c的波特率
  • dma: struct imx_i2c_dma 结构体 dam相关信息等等

   上面probe函数中,定义i2c_imx结构体的时候,也定义了访问i2c的算法,其定义如下:

static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer	= i2c_imx_xfer,
	.functionality	= i2c_imx_func,
};

   i2c_imx_algo结构体内指定了两个函数,它们就是外部访问i2c总线的接口:

  • 函数i2c_imx_func只是用于返回当前所处状态。
  • 函数i2c_imx_xfer真正实现外部访问i2c总线。
/*
 * @description  : 实现具体的收发工作
 */
static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)

   至此,我们知道i2c总线驱动完成了i2c的硬件初始化、将i2c总线添加到系统、并提供外界访问i2c总线的接口函数。 我们的i2c设备驱动只需要根据特定设备使用这些接口函数即可。

i2c设备驱动核心函数

#define i2c_add_driver(driver)   // 这个宏调用的下面这个函数
/*
 * @description : 注册一个i2c驱动
 * @param-owner : 一般是THIS_MODULE
 * @param-driver: 要注册的i2c_driver
 * @return      : 0表示成功,负数表示失败
 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
/*
 * @description : 注册一个i2c驱动
 * @param-adap  : 收发消息所使用的i2c适配器
 * @param-msgs  : struct i2c_msg 结构体,i2c要发送的一个或多个消息
 * @param-num   : 消息数量,也就是msgs的数量
 * @return      : 成功的话返回发送msgs的数量,失败的话返回负数
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

   i2c_msg 定义在include/uapi/linux/i2c.h中:

struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RD		0x0001	/* read data, from slave to master */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};
  • addr: iic设备地址
  • flags: 消息传输方向和特性。I2C_M_RD:表示读取消息;0:表示发送消息。
  • len: 消息数据的长度
  • buf: 字符数组存放消息,作为消息的缓冲区
// 发送一个i2c消息
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
// 接收一个i2c消息
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

总结

在这里插入图片描述

   简单来说,i2c_client设备有一个属性是name,i2c_driver中有个id_table,然后将client中的name与table中的值进行对比匹配,匹配成功则进入probe函数。而I2C_adapter在Linux系统中起着桥梁作用,连接主机系统与I2C总线。

参考资料

[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第二十六章、第六十一章
[2] I2C子系统–mpu6050驱动实验

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嵌入式驱动是指针对特定硬件平台,开发设备驱动程序的技术。这些驱动程序可以控制各种外围设备,例如传感器、执行器、通信接口等。嵌入式驱动程序通常需要直接操作硬件,因此需要深入了解硬件结构和编程技术。 以下是一个嵌入式驱动学习实战的教程: 1. 首先,了解嵌入式系统的基本概念和发展历史。可以学习嵌入式系统的硬件、软件、通信接口、操作系统等方面的知识。 2. 掌握嵌入式系统的基础编程语言,例如C语言和汇编语言。这些语言是嵌入式系统开发的基础,需要熟练掌握。 3. 学习硬件体系结构和编程技术。这包括处理器架构、内存管理、中断处理、GPIO控制等方面的知识。 4. 学习嵌入式系统的设备驱动程序开发技术。这包括字符设备驱动、块设备驱动、网络设备驱动等方面的知识。 5. 掌握常见的嵌入式系统开发工具,例如编译器、调试器、仿真器等。这些工具可以帮助开发人员调试和测试驱动程序。 6. 进行实战练习,例如使用开发开发一个简单的设备驱动程序。可以从最基础的GPIO控制开始,逐步扩展到其他设备驱动程序的开发。 7. 学习嵌入式系统的调试技术,例如使用调试器进行单步调试和断点调试。这些技术可以帮助开发人员快速定位和解决问题。 总之,嵌入式驱动学习需要系统化的知识结构和实战经验。通过不断学习和实践,可以逐步掌握嵌入式驱动开发的技术和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值