嵌入式Linux驱动开发(I2C专题)(五)

news2024/12/25 0:54:28

I2C系统驱动程序模型

参考资料:

  • Linux内核文档:
    • Documentation\i2c\instantiating-devices.rst
    • Documentation\i2c\writing-clients.rst
  • Linux内核驱动程序示例:
    • drivers/eeprom/at24.c

1. I2C驱动程序的层次

在这里插入图片描述
I2C Core就是I2C核心层,它的作用:

  • 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等
  • 实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)

2. I2C总线-设备-驱动模型

在这里插入图片描述

2.1 i2c_driver

i2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断
    • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
  • 使用id_table来判断
    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。

2.2 i2c_client

i2c_client表示一个I2C设备,创建i2c_client的方法有4种:

  • 方法1

    • 通过I2C bus number来创建

      int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len);
      
    • 通过设备树来创建

      	i2c1: i2c@400a0000 {
      		/* ... master properties skipped ... */
      		clock-frequency = <100000>;
      
      		flash@50 {
      			compatible = "atmel,24c256";
      			reg = <0x50>;
      		};
      
      		pca9532: gpio@60 {
      			compatible = "nxp,pca9532";
      			gpio-controller;
      			#gpio-cells = <2>;
      			reg = <0x60>;
      		};
      	};
      
  • 方法2
    有时候无法知道该设备挂载哪个I2C bus下,无法知道它对应的I2C bus number。
    但是可以通过其他方法知道对应的i2c_adapter结构体。
    可以使用下面两个函数来创建i2c_client:

    • i2c_new_device

        static struct i2c_board_info sfe4001_hwmon_info = {
      	I2C_BOARD_INFO("max6647", 0x4e),
        };
      
        int sfe4001_init(struct efx_nic *efx)
        {
      	(...)
      	efx->board_info.hwmon_client =
      		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);
      
      	(...)
        }
      
    • i2c_new_probed_device

        static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };
      
        static int usb_hcd_nxp_probe(struct platform_device *pdev)
        {
      	(...)
      	struct i2c_adapter *i2c_adap;
      	struct i2c_board_info i2c_info;
      
      	(...)
      	i2c_adap = i2c_get_adapter(2);
      	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
      	strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
      	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
      						   normal_i2c, NULL);
      	i2c_put_adapter(i2c_adap);
      	(...)
        }
      
    • 差别:

      • i2c_new_device:会创建i2c_client,即使该设备并不存在
      • i2c_new_probed_device:
        • 它成功的话,会创建i2c_client,并且表示这个设备肯定存在
        • I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样
        • 可以罗列出可能的地址
        • i2c_new_probed_device使用这些地址判断设备是否存在
  • 方法3(不推荐):由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client

  • 方法4:通过用户空间(user-space)生成
    调试时、或者不方便通过代码明确地生成i2c_client时,可以通过用户空间来生成。

  // 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
  # echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
  // 删除一个i2c_client
  # echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device

编写设备驱动之i2c_driver

参考资料:

Linux内核文档:
Documentation\i2c\instantiating-devices.rst
Documentation\i2c\writing-clients.rst
Linux内核驱动程序示例:
drivers/eeprom/at24.c

1. 套路

1.1 I2C总线-设备-驱动模型

在这里插入图片描述

1.2 示例

分配、设置、注册一个i2c_driver结构体,类似drivers/eeprom/at24.c:
在这里插入图片描述

2. 编写i2c_driver

2.1 先写一个框架

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>

static const struct of_device_id of_match_ids_example[] = {
	{ .compatible = "com_name,chip_name",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id example_ids[] = {
	{ "chip_name",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static int i2c_driver_example_probe(struct i2c_client *client)
{
	return 0;
}

static int i2c_driver_example_remove(struct i2c_client *client)
{
	return 0;
}

static struct i2c_driver i2c_example_driver = {
	.driver = {
		.name = "example",
		.of_match_table = of_match_ids_example,
	},
	.probe_new = i2c_driver_example_probe,
	.remove = i2c_driver_example_remove,
	.id_table = example_ids,
};

static int __init i2c_driver_example_init(void)
{
	return i2c_add_driver(&i2c_example_driver);
}
module_init(i2c_driver_example_init);

static void __exit i2c_driver_example_exit(void)
{
	i2c_del_driver(&i2c_example_driver);
}

module_exit(i2c_driver_example_exit);
MODULE_LICENSE("GPL");

2.2 在为AP3216C编写代码

百问网的开发板上有光感芯片AP3216C:
在这里插入图片描述
AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:

复位:往寄存器0写入0x4
使能:往寄存器0写入0x3
读红外:读寄存器0xA、0xB得到2字节的红外数据
读光强:读寄存器0xC、0xD得到2字节的光强
读距离:读寄存器0xE、0xF得到2字节的距离值
AP3216C的设备地址是0x1E。

ap3216c_drv.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/uaccess.h>
#include <linux/fs.h>

static int major = 0;
static struct class *ap3216c_class;
static struct i2c_client *ap3216c_client;

static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char kernel_buf[6];
	int val;
	
	if (size != 6)
		return -EINVAL;

	val = i2c_smbus_read_word_data(ap3216c_client, 0xA); /* read IR */
	kernel_buf[0] = val & 0xff;
	kernel_buf[1] = (val>>8) & 0xff;
	
	val = i2c_smbus_read_word_data(ap3216c_client, 0xC); /* read 光强 */
	kernel_buf[2] = val & 0xff;
	kernel_buf[3] = (val>>8) & 0xff;

	val = i2c_smbus_read_word_data(ap3216c_client, 0xE); /* read 距离 */
	kernel_buf[4] = val & 0xff;
	kernel_buf[5] = (val>>8) & 0xff;
	
	err = copy_to_user(buf, kernel_buf, size);
	return size;
}


static int ap3216c_open (struct inode *node, struct file *file)
{
	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
	/* delay for reset */
	mdelay(20);
	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
	mdelay(250);
	return 0;
}


static struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open  = ap3216c_open,
	.read  = ap3216c_read,
};

static const struct of_device_id of_match_ids_ap3216c[] = {
	{ .compatible = "lite-on,ap3216c",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id ap3216c_ids[] = {
	{ "ap3216c",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	ap3216c_client = client;
	
	/* register_chrdev */
	major = register_chrdev(0, "ap3216c", &ap3216c_ops);

	ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");
	device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c"); /* /dev/ap3216c */

	return 0;
}

static int ap3216c_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(ap3216c_class, MKDEV(major, 0));
	class_destroy(ap3216c_class);
	
	/* unregister_chrdev */
	unregister_chrdev(major, "ap3216c");

	return 0;
}

static struct i2c_driver i2c_ap3216c_driver = {
	.driver = {
		.name = "ap3216c",
		.of_match_table = of_match_ids_ap3216c,
	},
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.id_table = ap3216c_ids,
};


static int __init i2c_driver_ap3216c_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return i2c_add_driver(&i2c_ap3216c_driver);
}
module_init(i2c_driver_ap3216c_init);

static void __exit i2c_driver_ap3216c_exit(void)
{
	i2c_del_driver(&i2c_ap3216c_driver);
}
module_exit(i2c_driver_ap3216c_exit);
MODULE_LICENSE("GPL");

ap3216c_client.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/uaccess.h>
#include <linux/fs.h>


#if 1
static struct i2c_client *ap3216c_client;

static int __init i2c_client_ap3216c_init(void)
{
	struct i2c_adapter *adapter;

	static struct i2c_board_info board_info = {
	  I2C_BOARD_INFO("ap3216c", 0x1e),
	};
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* register I2C device */
	adapter = i2c_get_adapter(0);
	ap3216c_client = i2c_new_device(adapter, &board_info);
	i2c_put_adapter(adapter);
	return 0;
}

#else

static struct i2c_client *ap3216c_client;

/* Addresses to scan */
static const unsigned short normal_i2c[] = {
	0x1e, I2C_CLIENT_END
};

static int __init i2c_client_ap3216c_init(void)
{
	struct i2c_adapter *adapter;
	struct i2c_board_info board_info;
	memset(&board_info, 0, sizeof(struct i2c_board_info));
	strscpy(board_info.type, "ap3216c", sizeof(board_info.type));
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* register I2C device */
	adapter = i2c_get_adapter(0);
	ap3216c_client = i2c_new_probed_device(adapter, &board_info,normal_i2c, NULL);
	i2c_put_adapter(adapter);
	return 0;
}
#endif

module_init(i2c_client_ap3216c_init);

static void __exit i2c_client_ap3216c_exit(void)
{
	i2c_unregister_device(ap3216c_client);
}
module_exit(i2c_client_ap3216c_exit);
MODULE_LICENSE("GPL");

APP

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[6];
	int len;
	

	/* 2. 打开文件 */
	fd = open("/dev/ap3216c", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	len = read(fd, buf, 6);		
	printf("APP read : ");
	for (len = 0; len < 6; len++)
		printf("%02x ", buf[len]);
	printf("\n");
	
	close(fd);
	
	return 0;
}

Makefile

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88/

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= ap3216c_drv.o
obj-m	+= ap3216c_client.o

I2C_Adapter驱动框架讲解与编写

分配、设置、注册一个i2c_adpater结构体:

  • i2c_adpater的核心是i2c_algorithm
  • i2c_algorithm的核心是master_xfer函数

1. 所涉及的函数

  • 分配

    struct i2c_adpater *adap = kzalloc(sizeof(struct i2c_adpater), GFP_KERNEL);
    
  • 设置

    adap->owner = THIS_MODULE;
    adap->algo = &stm32f7_i2c_algo;
    
  • 注册:i2c_add_adapter/i2c_add_numbered_adapter

    ret = i2c_add_adapter(adap);          // 不管adap->nr原来是什么,都动态设置adap->nr
    ret = i2c_add_numbered_adapter(adap); // 如果adap->nr == -1 则动态分配nr; 否则使用该nr   
    
  • 反注册

    i2c_del_adapter(adap);
    

2. i2c_algorithm示例

  • Linux-5.4中使用GPIO模拟I2C
    在这里插入图片描述
  • Linux-5.4中STM32F157的I2C驱动
    在这里插入图片描述
  • Linux-4.9.88中IMX6ULL的I2C驱动
    在这里插入图片描述

3. 编写一个框架程序

3.1 设备树

在设备树里构造I2C Bus节点:

	i2c-bus-virtual {
		 compatible = "100ask,i2c-bus-virtual";
	};

3.2 platform_driver

分配、设置、注册platform_driver结构体。

核心是probe函数,它要做这几件事:

  • 根据设备树信息设置硬件(引脚、时钟等)
  • 分配、设置、注册i2c_apdater

3.3 i2c_apdater

i2c_apdater核心是master_xfer函数,它的实现取决于硬件,大概代码如下:

static int xxx_master_xfer(struct i2c_adapter *adapter,
						struct i2c_msg *msgs, int num)
{
    for (i = 0; i < num; i++) {
        struct i2c_msg *msg = msgs[i];
        {
        	// 1. 发出S信号: 设置寄存器发出S信号
            CTLREG = S;
            
            // 2. 根据Flag发出设备地址和R/W位: 把这8位数据写入某个DATAREG即可发出信号
            //    判断是否有ACK
            
            if (!ACK)
                return ERROR;
            else {
	            // 3. read / write
	            if (read) {
                    STATUS = XXX; // 这决定读到一个数据后是否发出ACK给对方
                    val = DATAREG; // 这会发起I2C读操作
                } else if(write) {
                    DATAREG = val; // 这会发起I2C写操作
                    val = STATUS;  // 判断是否收到ACK
                    if (!ACK)
                        return ERROR;
                }                
            }
            // 4. 发出P信号
            CTLREG = P;
        }
    }   
    return i;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1018018.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20230916在WIN10的资源管理器里关闭最右边的预览窗口

20230916在WIN10的资源管理器里关闭最右边的预览窗口 百度搜索&#xff1a;WIN10 干掉预览模式 https://zhidao.baidu.com/question/1807564480928861867.html win10计算机预览窗口怎么关  我来答 首页 用户 合伙人 商城 法律 手机答题 我的 win10计算机预览窗口怎么关  …

知识库管理工具哪个好?我建议你可以试一下这个!

对于很多企业/用户来说&#xff0c;在职业成长和个人发展的过程中&#xff0c;是需要借助知识库管理工具来进行知识内容沉淀的。 随着工具市场的发展&#xff0c;各种知识库管理工具层出不穷&#xff0c;今天我就结合数据安全、知识管理体系、简单实用三个方面出发&#xff0c;…

flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能

flutter聊天界面-TextField输入框buildTextSpan实现功能展示高亮功能 最近有位朋友讨论的时候&#xff0c;提到了输入框的高亮展示。在flutter TextField中需要插入特殊样式的标签&#xff0c;比如&#xff1a;“请 张三 回答一下”&#xff0c;这一串字符在TextField中输入&a…

day23集合02

1.泛型 1.1泛型概述 泛型的介绍 ​ 泛型是JDK5中引入的特性&#xff0c;它提供了编译时类型安全检测机制 泛型的好处 把运行时期的问题提前到了编译期间避免了强制类型转换 泛型的定义格式 <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:…

多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出

多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出 目录 多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 Matlab实现PSO-BP粒子群优化BP神经网络多输入多输出预测 1.data为数据…

小程序引入vant-Weapp保姆级教程及安装过程的问题解决

小知识&#xff0c;大挑战&#xff01;本文正在参与“程序员必备小知识”创作活动。 本文同时参与 「掘力星计划」&#xff0c;赢取创作大礼包&#xff0c;挑战创作激励金 当你想在小程序里引入vant时&#xff0c;第一步&#xff1a;打开官方文档&#xff0c;第二步&#xff…

Linux C/C++实现SSL的应用层VPN (MiniVPN)

SSL协议和VPN&#xff08;虚拟私人网络&#xff09;原理是网络安全领域中的两个重要概念。 SSL协议&#xff0c;全称安全套接层&#xff08;Secure Sockets Layer&#xff09;&#xff0c;是一种广泛应用于互联网的安全协议&#xff0c;主要在两个通信端点之间建立安全连接&am…

深度解剖数据在栈中的应用

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 望小伙伴们点赞&#x1f44d;收藏✨加关注哟&#x1f495;&#x1…

Vue 2 组件间的通信方式总结

引言 组件间的关系有父子关系、兄弟关系、祖孙关系和远亲关系。 不同的关系间&#xff0c;组件的通信有不同的方式。 一、prop 和 $emit prop向下传递&#xff0c;emit向上传递。 父组件使用 prop 向子组件传递信息。 ParentComponent.vue <template><div><…

红心向阳 百鸟朝凤

背景 最近在玩 folium 模块&#xff0c;基于使用过程中的一些个人体验&#xff0c;对 folium 进行了二次封装&#xff0c;开源在 GpsAndMap.在使用的过程中&#xff0c;发现在地图上打图标是可以进行旋转的。遇到就发现了一些有意思的玩法。 隔海的相望 下面的代码在地图 厦…

GIS跟踪监管系统信息查询

GIS跟踪监管系统信息查询 GIS跟踪监管系统&#xff08;1&#xff09;物资查询与展示。① 几何查询。代码说明&#xff1a;② 物资定位。• 单个物资定位&#xff1a;• 多个物资定位&#xff1a;③ 物资统计。&#xff08;2&#xff09;物资信息更新① 新增物资。 GIS跟踪监管系…

【项目经验】:elementui表格中数字汉字排序问题及字符串方法localeCompare()

一.需求 表格中数字汉字排序&#xff0c;数字按大小排列&#xff0c;汉字按拼音首字母&#xff08;A-Z&#xff09;排序。 二.用到的方法 第一步&#xff1a;把el-table-column上加上sortable"custom" <el-table-column prop"date" label"序号…

第七章 查找 一、查找的基本概念

一、基本概念 查找——在数据集合中寻找满足某种条件的数据元素的过程称为查找。 查找表(查找结构)——用于查找的数据集合称为查找表&#xff0c;它由同一类型的数据元素(或记录)组成。 关键字——数据元素中唯一标识该元素的某个数据项的值&#xff0c;使用基于关键字的查…

2023年的深度学习入门指南(27) - CUDA的汇编语言PTX与SASS

通过前面的学习&#xff0c;我们了解了在深度学习和大模型中&#xff0c;GPU的广泛应用。可以说&#xff0c;不用说没有GPU&#xff0c;就算是没有大显存和足够先进的架构&#xff0c;也没法开发大模型。 有的同学表示GPU很神秘&#xff0c;不知道它是怎么工作的。其实&#x…

Vue2.7 封装 Router@4 的 hook

1、问题 在 Vue2.7 中&#xff0c;尤大大是支持大部分 Vue3 的功能&#xff0c;并且支持使用 CompositionAPI 的写法&#xff0c;也支持 script setup 的便捷语法&#xff0c;但是 Vue2 对应的 Vue-router3 库并没有提供 hook 对应的支持&#xff0c;所以需要我们自行封装 Vue…

代码对比工具,都在这了

Git Diff Git是一个流行的分布式版本控制系统&#xff0c;它内置了代码对比功能。使用git diff命令可以比较两个不同版本的代码文件&#xff0c;也可以使用图形化的Git客户端进行可视化对比。 git diff 命令 | 菜鸟教程www.runoob.com/git/git-diff.html Diff diff是一个Un…

NAND价格第4季度回暖,现在是SSD入手时机吗?

这两天有粉丝后台在咨询购买SSD相关的问题。小编也好奇的搜下当前业内SSD品牌。不搜不知道&#xff0c;一搜吓一跳&#xff0c;将近200多个品牌。 那么&#xff0c;买SSD应该买什么品牌&#xff1f;现在是否可以入手SSD呢&#xff1f; 1.固态硬盘SSD的原理 我们首先了解下固态…

兄弟DCP-7080激光打印机硒鼓清零方法

兄弟DCP-708打印机清零方法?兄弟DCP-7080打印机的硒鼓计数器是用来记录硒鼓使用寿命的&#xff0c;当硒鼓使用寿命达到一定程度时&#xff0c;打印机会提示更换硒鼓。如果用户更换了硒鼓&#xff0c;但打印机仍提示需要更换&#xff0c;这时需要进行清零操作&#xff0c;详细请…

xen-gic初始化流程

xen-gic初始化流程 调试平台使用的是gic-600&#xff0c;建议参考下面的文档来阅读代码&#xff0c;搞清楚相关寄存器的功能。 《corelink_gic600_generic_interrupt_controller_technical_reference_manual_100336_0106_00_en》 《IHI0069H_gic_architecture_specification》…

基于SSM的实验室开放管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…