MB85RC铁电 FRAM驱动(全志平台linux)

news2024/9/24 15:25:07

测试几天发现一个bug,就是无法一次读取32个字节的数据,1-31,33,128,512都试过了,唯独无法读取32个字节,驱动未报错,但是读取的都是0,找不到原因,估计应该是全志iic驱动的问题,暂时没有折腾,尽量避开32字节读取吧,32字节写入是没问题的。

使用的字符驱动,可以读写任意字节(32字节读取除外),可以使用lseek设置读写地址,首先设置内核设备数,在对应的iic节点下添加fram支持。

fram: fram@50 {
		compatible = "general,iic_fram";
		reg = <0x50>;
	};

驱动代码如下

// SPDX-License-Identifier: GPL-2.0+
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/mutex.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/uaccess.h>
#include <linux/device.h>			//自动创建/dev设备节点需要
#include <linux/kdev_t.h>           //设备号用到的头文件和宏函数
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>


typedef unsigned int  u32;
typedef unsigned short u16;
typedef unsigned char  u8;

typedef volatile unsigned int  vu32;
typedef volatile unsigned short vu16;
typedef volatile unsigned char  vu8;

typedef unsigned int  const uc32;  /* Read Only */
typedef unsigned short const uc16;  /* Read Only */
typedef unsigned char  const uc8;   /* Read Only */



#define DEVICE_NAME			"iic_fram" 	// 设备名字
#define DTS_COMPATIBLE		"general,iic_fram"	//设备树中对应的COMPATIBLE信息名称

//注意:fram使用的是 MB85RC16PNF-G-JNERE1 进行测试2KB容量,FRAM的通讯有点不一样,芯片地址为4bit,然后3bit寄存器高地址,1bit读写标识,最终依旧使用的是8bit寄存器模式,但是地址范围0-7FF

//设备驱动私有结构体数据定义
struct fram_type {
	int fram_init_finished;
	struct i2c_client* fram_client;
	struct mutex mutex_lock;//定义互斥锁
	struct class* device_class;		//注册后的设备节点class
	struct device* device;	//注册的设备
	dev_t devno;			//设备号
	struct cdev cd;
	int size;				//容量信息
};
static struct fram_type* sg_fram = NULL;	//再iic设备注册后进行初始化,非NULL意味着硬件初始化正常,在probe中进行初始化

//注册驱动时传入参数,参数为farm容量信息,默认为2KB,单位字节
static int SIZE = -1;				//通过注册驱动的时候传入参数,如 insmode iic_fram SIZE=4096 实现容量设置
module_param(SIZE, int, S_IRUSR);	//S_IRUSR在include/linux/stat.h




/*************************************************************************************************************************
*函数        	:	int fram_read_data(struct i2c_client* client, u16 addr, u16 ByteCount, u8* pData, bool isKernel)
*功能        	:	iic驱动读取寄存器数据
*参数        	:	client:句柄;addr:寄存器地址;ByteCount:要读取的数据数量;pData:数据缓冲区;isKernel:是否是内核读取,如果是内核读取将不需要进行内核数据与用户空间数据转换
*返回        	:	<0 错误,其它:读取的字节数
*依赖			: 	底层宏定义
*作者				:	cp1300@139.com
*时间     		:	2024-03-04
*最后修改时间		:	2024-03-04
*说明        	:	
*************************************************************************************************************************/
int fram_read_data(struct i2c_client* client, u16 addr, u16 ByteCount, u8 __user* pData, bool isKernel)
{
	int ret;
	u8 addr_buff[1];
	u8* pkbuff;
	struct i2c_msg msgs[] = {
		//写命令
		{
				.addr = client->addr | ((addr >> 8) & 0x07),
				.flags = 0,
				.len = 1,				//数据长度为1字节的地址
				.buf = addr_buff,		//寄存器地址
		},
		//读取数据
		{
				.addr = client->addr | ((addr >> 8) & 0x07),
				.flags = I2C_M_RD,
				.len = ByteCount,		//数据长度为n字节的数据
				.buf = NULL,			//buf-等会设置
		}
	};

	//dev_info(&client->dev, "i2c_smbus_read_16bit_i2c_block_data addr=%d ByteCount=%d client->addr=%d\r\n", addr, ByteCount, client->addr);


	//申请内核内存,准备读取数据
	pkbuff = (u8*)kmalloc(ByteCount, GFP_KERNEL | GFP_DMA | __GFP_ZERO);	//申请内存-iic会用到DMA,内存要连续
	if (pkbuff == NULL)
	{
		printk("fram read out of memory! kmalloc(%dB)\r\n", ByteCount);
		return -1;
	}
	msgs[1].buf = pkbuff;	//读取的数据存放到缓冲区
	//准备寄存器地址数据
	addr_buff[0] = addr & 0xFF;					//地址低位


	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));	//写入命令,读取数据
	if (ret <= 0)
	{
		dev_err(&client->dev, "i2c_transfer read addr=%d ByteCount=%d failed!\r\n", addr, ByteCount);
		ret = -1;
	}
	else
	{
		if (isKernel == true) //当前处于内核空间
		{
			memcpy(pData, pkbuff, ByteCount);
			ret = ByteCount;	//返回数据长度
		}
		else
		{
			ret = copy_to_user(pData, pkbuff, ByteCount);  //读取成功,将数据拷贝到用户空间
			if (ret != 0)
			{
				printk("fram copy_to_user error(%dB)\r\n", ret);
				ret = -1;
			}
			else
			{
				ret = ByteCount;	//返回数据长度
			}
		}
		
	}
	

	kfree(pkbuff);	//释放申请的内存


	return ret;
}


/*************************************************************************************************************************
*函数        	:	int fram_write_data(struct i2c_client* client, u16 addr, u16 ByteCount, u8* pData)
*功能        	:	iic驱动写入寄存器数据
*参数        	:	client:句柄;addr:寄存器地址;ByteCount:要读取的数据数量;pData:数据缓冲区
*返回        	:	<0 错误,其它:读取的字节数
*依赖			: 	底层宏定义
*作者				:	cp1300@139.com
*时间     		:	2024-03-04
*最后修改时间		:	2024-03-04
*说明        	:	
*************************************************************************************************************************/
int fram_write_data(struct i2c_client* client, u16 addr, u16 ByteCount, const u8 __user* pData)
{
	int ret;
	u8* buf;
	struct i2c_msg msgs[] = {
		//写数据命令
		{
				.addr = client->addr | ((addr >> 8) & 0x07),
				.flags = 0,
				.len = ByteCount + 1,	//数据长度为2字节的地址,n字节的数据
				//.buf = &reg,			//buf等会赋值,需要申请内存
		}
	};

	//dev_info(&client->dev, "i2c_smbus_write_16bit_i2c_block_data write addr=%d ByteCount=%d\r\n", addr, ByteCount);

	buf = (u8*)kmalloc(ByteCount + 1, GFP_KERNEL | GFP_DMA | __GFP_ZERO);	//申请内存-iic会用到DMA,内存要连续
	if (buf == NULL)
	{
		dev_err(&client->dev, "i2c_smbus_write_16bit_i2c_block_data out of memory! kmalloc(%dB)\r\n", ByteCount + 1);
		return -1;
	}
	msgs[0].buf = buf;						//记录申请的内存

	//准备数据
	buf[0] = addr & 0xFF;					//地址低位

	ret = copy_from_user(&buf[1], pData, ByteCount);	//用户空间数据拷贝到内核空间
	if (ret != 0) {
		printk("fram copy_from_user error(%dB)\r\n", ret);
		ret = -EFAULT;
	}
	else
	{
		ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));	//写入数据
		if (ret == ARRAY_SIZE(msgs))
		{
			ret = ByteCount;	//返回写入的数据长度
		}
		else
		{
			dev_err(&client->dev, "i2c_transfer write addr=%d ByteCount=%d failed!\r\n", addr, ByteCount);
			ret = -1;
		}
	}
	kfree(buf);	//释放申请的内存

	return ret;
}



/*************************************************************************************************************************
*函数        	:	int fram_Init(struct i2c_client* client)
*功能        	:	fram初始化
*参数        	:	client:iic句柄
*返回        	:	0:初始化成功;其它:初始化失败
*依赖			: 	底层宏定义
*作者				:	cp1300@139.com
*时间     		:	2024-03-03
*最后修改时间		:	2024-03-03
*说明        	:
*************************************************************************************************************************/
int fram_Init(struct i2c_client* client)
{
	u8 i;
	struct fram_type* fram = i2c_get_clientdata(client);	//获取私有数据
	int ret;
	u8 temp;

	if (fram == NULL)
	{
		dev_err(&client->dev, "i2c_get_clientdata(client) null\r\n");

		return -1;
	}

	//读取地址0,只要能读取到就认为初始化成功
	for (i = 0; i < 3; i++)
	{
		ret = fram_read_data(client, 0, 1, &temp, true);
		if (ret < 0)
		{
			dev_err(&client->dev, "Failed to read 0x00\r\n");
			msleep(5);
		}
		else
		{
			dev_info(&client->dev, "addr 0x00:0x%X\r\n", temp);
			break;
		}
	}
	if (ret < 0) return -1;

	return 0;
}




//===========================================================================================
//标准文件接口相关
/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int device_open(struct inode* inode, struct file* filp)
{
	if (sg_fram == NULL)
	{
		printk("fram not initialized\r\n");

		return -1;
	}

	//dev_info(&sg_fram->fram_client->dev, "device_open\n");

	return 0;
}



/*
 * @description		: 从设备读取数据
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t device_read(struct file* filp, char __user* buf, size_t cnt, loff_t* offt)
{
	u32 offset = (u32)*offt;
	int ret;

	if (sg_fram == NULL)
	{
		printk("fram not initialized\r\n");

		return -1;
	}
	if (buf == NULL)
	{
		printk("buf is NULL!\r\n");
		return -EFAULT;
	}
	if (cnt == 0 || cnt > sg_fram->size || (cnt + offset) > sg_fram->size)
	{
		printk("fram Read out of range cnt=%d offt=%d(max size:%dB)\r\n", cnt, offset, sg_fram->size);

		return 0;
	}

	//读取数据
	mutex_lock(&sg_fram->mutex_lock);	//阻塞式上互斥锁,抢不到就一直阻塞
	ret = fram_read_data(sg_fram->fram_client, (u16)offset, (u16)cnt, buf, false);
	mutex_unlock(&sg_fram->mutex_lock);	//解锁
	

	return ret;
}



/*
 * @description		: 向设备写数据
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t device_write(struct file* filp, const char __user* buf, size_t cnt, loff_t* offt)
{
	u32 offset = (u32)*offt;
	int ret;

	if (sg_fram == NULL)
	{
		printk("fram not initialized\r\n");

		return -1;
	}
	if (buf == NULL)
	{
		printk("buf is NULL!\r\n");
		return -EFAULT;
	}

	if (cnt == 0 || cnt > sg_fram->size || (cnt + offset) > sg_fram->size)
	{
		printk("fram write out of range cnt=%d offt=%d(max size:%dB)\r\n", cnt, offset, sg_fram->size);

		return 0;
	}
	
	mutex_lock(&sg_fram->mutex_lock);	//阻塞式上互斥锁,抢不到就一直阻塞
	ret = fram_write_data(sg_fram->fram_client, (u16)offset, (u16)cnt, buf);
	mutex_unlock(&sg_fram->mutex_lock);	//解锁

	return ret;
}




/*
 * @description		: 设置文件读写偏移
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - off 	: 读写偏移
 * @param - whence 	: 光标参考位置
 * @return 			: 当前文件的纸质位置,如果为负值,表示写入失败
 */
static loff_t device_llseek(struct file* filp, loff_t off, int whence)
{
	//struct scull_dev* dev = filp->private_data;
	loff_t newpos;

	//printk("device_llseek off=%u whence=%d\r\n", (u32)off, whence);
	switch (whence)
	{
		case 0: //SEEK_SET 从开始的偏移
		{
			newpos = off;
			if (newpos >= sg_fram->size) newpos = sg_fram->size - 1;	//防止超出范围
		}break;
		case 1:	//SEEK_CUR 在当前位置加上偏移
		{
			newpos = filp->f_pos + off;
			if (newpos >= sg_fram->size) newpos = sg_fram->size - 1;	//防止超出范围
		}break;
		case 2: //SEEK_END 偏移位置文件结尾 之外
		{
			newpos = sg_fram->size - 1;	//不允许超出文件
		}break;
		default: return -EINVAL;
	}
	if (newpos < 0) return -EINVAL;
	filp->f_pos = newpos;

	return newpos;
}






/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int device_release(struct inode* inode, struct file* filp)
{
	//printk("fram_release\r\n");
	return 0;
}






//申请i2c资源,顺便进行初始化
static int device_probe(struct i2c_client *client,
			     const struct i2c_device_id *id)
{
	int ret;
	struct fram_type* fram;

	dev_info(&client->dev, "fram_probe\n");
	//判断iic适配器是否正常
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
	{
		return -ENODEV;
	}
	
	//申请内核内存资源
	fram = devm_kzalloc(&client->dev, sizeof(struct fram_type), GFP_KERNEL);
	if (!fram)
	{
		return -ENOMEM;
	}

	fram->fram_init_finished = 0;			//初始化未完成
	fram->fram_client = client;			//记录i2c接口指针
	fram->device_class = NULL;						//设备节点无效
	fram->device = NULL;						//设备节点无效


	//需要提前设置,否则在 fram_Init 中需要调用 i2c_get_clientdata () 将返回空
	i2c_set_clientdata(client, fram);		// 将fram作为i2c次设备的私有数据区中的设备驱动私有数据

	if (SIZE > 0)
	{
		dev_info(&client->dev, "SIZE=%dB\n", SIZE &0xFFFF);
	}
	else
	{
		SIZE = 2048;	//默认为2KB
	}

	if (SIZE < 128) SIZE = 128;
	if (SIZE > 0x7FF) SIZE = 0x7FF;	//最大大小限制为0x7FF
	fram->size = SIZE;		//初始化FRAM大小
	dev_info(&client->dev, "FRAM SIZE:%dB\n", fram->size);

	//开始芯片硬件探测与初始化
	ret = fram_Init(client);	//初始化并设置初值,也可以通过注册驱动的时候传入参数实现初始化
	if (ret < 0)
	{
		dev_err(&client->dev, "invalid init fram\n");
		return -1;
	}
	mutex_init(&fram->mutex_lock);//初始化互斥锁

	

	sg_fram = fram;						//记录全局设备数据,设备硬件初始化完成了
	fram->fram_init_finished = 1;			//初始化完成

	dev_info(&client->dev, "fram probe succeeded\n");

	return 0;
}

//移除iic驱动
static int device_remove(struct i2c_client* client)
{
	struct fram_type* fram = i2c_get_clientdata(client);	//获取私有数据

	dev_info(&client->dev, "fram_remove\n");
	//释放互斥锁信号
	if (fram != NULL && fram->fram_init_finished)
	{
		mutex_destroy(&fram->mutex_lock);
	}

	return 0;
}

//===========================================================================================
//iic接口相关
static const struct of_device_id fram_match_table[] = {
	{
		.compatible = DTS_COMPATIBLE,
	},
	{},
};
MODULE_DEVICE_TABLE(of, fram_match_table);

static const struct i2c_device_id fram_id[] = {
	{ DTS_COMPATIBLE, 0 },
	{},
};
MODULE_DEVICE_TABLE(i2c, fram_id);

static struct i2c_driver fram_driver = {
	.driver = {
		.name		= DEVICE_NAME,
		.owner		= THIS_MODULE,
		.of_match_table = fram_match_table,
	},
	.probe    = device_probe,	//注册IIC
	.remove = device_remove,
	.id_table	= fram_id,

};

//module_i2c_driver(fram_driver);	//需要使用 i2c_add_driver 在 init中进行注册iic适配器,不能直接使用宏






//设备驱动操作相关接口结构体
static struct file_operations sg_device_opera_fops = {
	.owner = THIS_MODULE,
	.open = device_open,		//打开驱动文件接口
	.read = device_read,		//读取接口
	.write = device_write,		//写文件接口
	.release = device_release,	//释放文件接口
	.llseek = device_llseek,	//设置文件偏移
	//.ioctl = device_ioctl,		//参数设置接口
};






//驱动接口-驱动入口函数-初始化与申请资源
static int __init device_init(void)
{
	int retvalue;

	//printk("device_module_init\n");

	retvalue = i2c_add_driver(&fram_driver);	//添加iic驱动
	if (retvalue) {
		printk("%s i2c_add_driver failed! %d\n", __func__, retvalue);
		return -ENODEV;
	}

	if (sg_fram == NULL) //设备硬件初始化失败
	{
		printk("initialization failed!\r\n");
		return -EIO;
	}

	//注册字符设备驱动
	retvalue = alloc_chrdev_region(&sg_fram->devno, 0, 1, DEVICE_NAME);	//自动申请设备号,从0开始,申请1个
	if (retvalue < 0) {
		pr_err("alloc_chrdev_region failed!(%d)", retvalue);

		i2c_del_driver(&fram_driver);				//移除iic设备

		return retvalue;
	}
	printk("MAJOR is %d\n", MAJOR(sg_fram->devno));
	printk("MINOR is %d\n", MINOR(sg_fram->devno));

	cdev_init(&sg_fram->cd, &sg_device_opera_fops);			//字符驱动结构体初始化
	retvalue = cdev_add(&sg_fram->cd, sg_fram->devno, 1);	//注册字符设备驱动,数量1
	if (retvalue < 0) {
		pr_err("cdev_add failed!(%d)", retvalue);

		i2c_del_driver(&fram_driver);				//移除iic设备
		unregister_chrdev_region(sg_fram->devno, 1);	//注销一个范围的设备号

		return retvalue;
	}


	//自动在/dev目录下创建设备节点
	sg_fram->device_class = class_create(THIS_MODULE, DEVICE_NAME);	创建类
	if (NULL == sg_fram->device_class)
	{
		printk(KERN_INFO "create calss failed\n");

		cdev_del(&sg_fram->cd);
		unregister_chrdev_region(sg_fram->devno, 1);	//注销一个范围的设备号

		i2c_del_driver(&fram_driver);				//移除iic设备

		return -1;

	}
	else
	{
		sg_fram->device = device_create(sg_fram->device_class, NULL, sg_fram->devno, NULL, DEVICE_NAME);	//创建设备
		if (NULL == sg_fram->device)
		{
			printk(KERN_INFO "create device failed\n");
			
			cdev_del(&sg_fram->cd);
			unregister_chrdev_region(sg_fram->devno, 1);
			class_destroy(sg_fram->device_class);

			i2c_del_driver(&fram_driver);				//移除iic设备

			return -1;
		}
	}
	printk("%s succeeded\n", DEVICE_NAME);

	return 0;
}




//驱动接口-驱动出口函数-注销资源
static void __exit device_exit(void)
{
	printk("device_module_exit\n");
	i2c_del_driver(&fram_driver);				//移除iic设备

	cdev_del(&sg_fram->cd);						//注销字符设备驱动
	unregister_chrdev_region(sg_fram->devno, 1);	//注销设备号

	device_del(sg_fram->device);
	printk(KERN_INFO "delete device /dev/my_char_dev \n");
	class_destroy(sg_fram->device_class);
	printk(KERN_INFO "delete device /sys/class/my_char_dev \n");


}

module_init(device_init);
module_exit(device_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cp1300@139.com");

测试代码


void fram_test(void)
{
	int fd;
	u8 buff[512];
	int len;
	int i;

	fd = open("/dev/iic_fram", O_RDWR);
	if(fd < 0)
	{
		printf("open iic_frame error:%d\r\n", fd);
		return ;
	}

	lseek(fd, 0, SEEK_SET);
	len = read(fd, buff, 32);
	if(len <= 0)
	{
		printf("read iic_frame error:%d\r\n", fd);
		return ;
	}
	for(i = 0;i < len;i ++)
	{
		printf("0x%02X \t", buff[i]);
	}
	printf("\r\n");

	//lseek(fd, 0, SEEK_SET);
	/*for(i = 0;i < len;i ++)
	{
		buff[i] = i+0xF0;
	}
	len = write(fd, buff, len);
	if(len <= 0)
	{
		printf("write iic_frame error:%d\r\n", fd);
		return ;
	}*/
	close(fd);

}

读取32字节全部是0

试试33字节就正常了

 

仔细看底层驱动的打印信息

 sunxi-i2c sunxi-i2c2: drv-mode: dma read data end

读取32字节的时候,这个打印都结束了才提示DMA读取完成,很有可能就是因为数据都没读取完成,但是底层已经返回了,原因未知,珍爱生命,远离linux驱动,凑合着用吧(⊙o⊙)

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

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

相关文章

Golang Copy()方法学习

前言 主要是涉及到深浅拷贝相关的&#xff0c;但是在看的一个资料过程中发现他有错…并且一系列&#xff0c;复制粘贴他的&#xff0c;也都错了。 错误文章指路 很显然&#xff0c;Copy是深拷贝啊&#xff01;&#xff01;&#xff01; Copy功能 copy的代码很少&#xff0c…

如何证明线性规划系统最优解存在性

先给定simplex所对应的算法的流程图&#xff1a; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 上图是线性规划算法的基本流程描述&#xff0c;但是给定的基本流程描述中的一些步骤还需要进一步的进行分解&#xff0c;第一步是如何将线性规划系统依靠算…

【Java】Base理论的核心思想和理论三要素

目录 简介 BASE 理论的核心思想 BASE 理论三要素 1. 基本可用 2. 软状态 3. 最终一致性 总结 简介 BASE 是 Basically Available&#xff08;基本可用&#xff09; 、Soft-state&#xff08;软状态&#xff09; 和 Eventually Consistent&#xff08;最终一致性&#xf…

第 387 场 LeetCode 周赛题解

A 3069. 将元素分配到两个数组中 I 模拟 class Solution { public:vector<int> resultArray(vector<int> &nums) {vector<int> r1{nums[0]}, r2{nums[1]};for (int i 2; i < nums.size(); i) {if (r1.back() > r2.back())r1.push_back(nums[i]);e…

04. Nginx入门-Nginx WEB模块

测试环境 此处使用的yum安装的Nginx路径。 此处域名均在本地配置hosts。 主配置文件 路径&#xff1a;/etc/nginx/nginx.conf user nginx; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;events {worker_connection…

使用html网页播放多个视频的几种方法

前言 因为项目测试需要&#xff0c;我需要可以快速知道自己推流的多路视频流质量&#xff0c;于是我想到可以使用html网页来播放视频&#xff0c;实现效果极其简单&#xff0c;方法有好几种&#xff0c;以下是几种记录&#xff1a; 注意&#xff1a;测试过&#xff0c;VLC需要使…

97 spring 中的泛型类型注入

前言 呵呵 同样是 最近同事碰到的一个问题 他不太懂 英语, 看到的说明是 缺少一个 RedisTemplate 的实例, 但是找到了一个 RedisTemplate 的实例 呵呵 和我这里 spring 版本似乎是不太一样, 错误信息 有一些差异 以下环境基于 jdk8 spring-5.0.4-RELEASE 测试用例 BeanCon…

23种设计模式——工厂方法模式

定义&#xff1a; 一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其他子类。 工厂方法通用类图&#xff1a; 这个图更好理解 在工厂方法模式中&#xff0c;抽象产品类Product负责定义产品的共性&#xff0c;实现对事物最抽象的…

人大金仓KingbaseES:银河麒麟V10安装

人大金仓KingbaseES&#xff1a;银河麒麟V10安装 众所周知的原因&#xff0c;为了响应国家信创支持国产操作系统和数据库&#xff0c;现在我们公司的项目要从开源数据库PostgreSQL切换到人大金仓KingbaseES数据库&#xff08;本质是早期版本的PostgreSQL&#xff09;。使用的是…

自适应控制算法讲解-案例(附C代码)

目录 一、自适应控制算法的基本原理 二、自适应控制算法分类 三、案例 3.1自适应PID控制 1&#xff09; 模型识别 2&#xff09;动态调整PID参数逻辑 3&#xff09;PID控制器 自适应控制算法是一种高级控制算法&#xff0c;用于处理那些参数不确定或者动态变化的系统。这类…

Elasticsearch:向量相似度计算 - 可笑的速度

作者&#xff1a;Chris Hegarty 任何向量数据库的核心都是距离函数&#xff0c;它确定两个向量的接近程度。 这些距离函数在索引和搜索期间执行多次。 当合并段或在图表中导航最近邻居时&#xff0c;大部分执行时间都花在比较向量的相似性上。 对这些距离函数进行微观优化是值…

AI-数学-高中-34概率-古典概率模型

原作者视频&#xff1a;【概率】【一数辞典】3古典概型_哔哩哔哩_bilibili 等可能性&#xff1a;每个样本点出现的可能性是相同的。 随机事件A的发生事件A的样本点数k / 样板空间总样本点数n。 示例1&#xff1a; 示例2&#xff1a;

[DevOps云实践] IaaC:通过CloudWatch Agent和自定义Metric监视服务器

[DevOps云实践] IaaC:通过CloudWatch Agent和自定义Metric监视服务器 现在有许多第三方工具可以用于监控EC2实例。尽管事实如此,我想为您提供使用AWS原生资源和工具(如AWS CloudWatch、CloudWatch Agent和CloudFormation)快速设置EC2监控所需的所有信息。 首先,您应该知…

2024.3.4

思维导图 作业1&#xff1a;广播 发送端&#xff1a; #include<myhead.h> int main(int argc, const char *argv[]) {//创建套接字int sfd socket(AF_INET,SOCK_DGRAM,0);if(sfd -1){perror("sockeet error");return -1;}//设置当前套接字允许广播属性int …

idea中引入新JDK环境

在不同的项目中往往会需要不同的运行环境&#xff0c;那么如何下载一个新的环境并运用到idea中呢&#xff1f; 下面给出的就是oracle官网&#xff0c;以JDK17为例教大家如何下载 Java Archive Downloads - GraalVM for JDK 17https://www.oracle.com/java/technologies/javase…

End-to-End Weakly-Supervised SemanticSegmentation with Transformers

摘要 弱监督语义分割&#xff08;WSSS&#xff09;使用图像级标签是一项重要且具有挑战性的任务。由于高训练效率&#xff0c;端到端的WSSS解决方案受到社区越来越多的关注。然而&#xff0c;当前的方法主要基于卷积神经网络&#xff0c;并未正确地探索全局信息&#xff0c;因…

在Visual Studio配置C++的netCDF库的方法

本文介绍在Windows电脑的Visual Studio软件中&#xff0c;配置C 语言最新版netCDF库的方法。 netCDF&#xff08;Network Common Data Form&#xff09;是一种用于存储、访问和共享科学数据的文件格式和库&#xff0c;其提供了一种灵活的方式来组织、描述和存储多维数据&#x…

【系统架构设计师考试大纲】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 简述概要知识图谱考试目标考试要求考试题目题型分析计算机基础知识&#xff08;20%&#xff09;信息化战略与规划&#xff08;9%&#xff09;软件工程&#xff08;25%&#xff09;系统架构设计&#xff08;35%&#xff09;信…

适用于 Windows 的7大数据恢复软件解决方案

数据丢失是数字世界中令人不快的一部分&#xff0c;它会在某一时刻影响许多计算机用户。很容易意外删除一些重要文件&#xff0c;这可能会在您努力恢复它们时带来不必要的压力。幸运的是&#xff0c;数据恢复软件可以帮助恢复已删除的文件&#xff0c;即使您没有备份它们。以下…

AI大模型与小模型之间的“脱胎”与“反哺”(第三篇)

51. **异构图神经网络集成&#xff08;Heterogeneous Graph Neural Network Integration&#xff09;**&#xff1a; 构建异构图神经网络来捕获和整合各行业间复杂的实体关系及属性信息。每个行业的小模型可视为一个子图&#xff0c;将它们整合进一个统一的大图模型中&#xff…