I2C协议

news2024/11/24 13:35:33

i2c硬件电路

i2c总线连接图:
i2c总线连接图
注:

  1. i2c的SDA和SCL都需要上拉电阻,改变上拉电阻大小可调节I2C总线的上拉强度,上拉电阻用1k~100k不等,小了驱动能力就强,但电流就大了。
  2. 不同内核版本上的驱动不一样,I2C可以跑的最高频率也有区别。

i2c通信协议

i2c的时序分为四部分:起始信号,数据传输,应答信号,停止信号:
在这里插入图片描述

  1. 起始信号:当SCL高电平时,SDA有一个下降沿。
  2. 数据传输:bit7~bit0是数据位。
  3. 应答信号:在发送完8bit的数据后,发送方会释放SDA(这时候SDA被拉高,因为i2c有上拉电阻),这时候如果接收方正常接收到数据,会在第9个时钟拉低SDA,如果没有拉低,会被视为NACK,会出现i2c挂死。
  4. 停止信号:当SCL高电平时,SDA有一个上升沿。
    注:
    数据传输过程SDA变化必须在SCL低电平时变化,在SCL为高电平时保持,否则会被误认为是起始信号或者停止信号。

i2c数据格式

  1. i2c数据传输时,在起始信号之后会发送一个i2c的从机地址,这个地址会包含读写信息,即前面7bit是从机地址,第8bit代表数据的读写,0代表发送数据(写),1代表请求数据(读)。
  2. i2c数据传输时,MSB在前,LSB在后,也就是先传高位再传低位。

i2c常见问题

  1. 当 log: "timeout, ipd: 0x00, state: 1"出现时, 请检查硬件上拉是否给电或者 I2C pin脚的 iomux 值是否设置正确;
  2. 如果调用 i2c_transfer 返回值为-6 时候, 表示为 NACK 错误, 即对方设备无应答响应,这种情况一般为外设的问题, 常见的有以下几种情况:
    A. I2C 地址错误, 解决方法是测量 i2c 波形, 确认是否 i2c 设备地址错误;
    B. I2C slave 设备不处于正常工作状态, 比如未给电, 错误的上电时序等;
    C. 时序不符合 I2C slave 设备所要求也会产生 NACK 信号, 比如下面的第三点;
  3. 当外设对于读时序要求中间是 stop 信号,而不是 repeat start 信号的时候, 需要调用两次i2c_transfer, 分别将写寄存器地址的操作与读数据操作, 作两次 I2C 调用, 修改如下:
staticint i2c_read_bytes(struct i2c_client *client, u8 cmd, u8 *data, u8 data_len)
{
    struct i2c_msg msgs[2];
    int ret;
    u8 *buffer;
    buffer = kzalloc(data_len, GFP_KERNEL);
    if (!buffer)
        return-ENOMEM;;
    msgs[0].addr = client->addr;
    msgs[0].flags = client->flags;
    msgs[0].len = 1;
    msgs[0].buf = &cmd;
    ret = i2c_transfer(client->adapter, msgs, 1);
    if (ret < 0) {
        dev_err(&client->adapter->dev, "i2c read failed\n");
        kfree(buffer);
        return ret;
    }
    msgs[1].addr = client->addr;
    msgs[1].flags = client->flags | I2C_M_RD;
    msgs[1].len = data_len;
    msgs[1].buf = buffer;
    ret = i2c_transfer(client->adapter, &msgs[1], 1);
    if (ret < 0)
        dev_err(&client->adapter->dev, "i2c read failed\n");
    else
        memcpy(data, buffer, data_len);
    kfree(buffer);
    return ret;
}

实验

kernel读写i2c

注:板子上i2c总线4有个i2c设备,i2c地址为0x6a
以下demo是通过kernel驱动读写i2c设备。
dts修改:

&i2c4 {
         status = "okay";
         i2ctest: i2ctest@0 {
                compatible = "rockchip,i2c-test";
                reg = <0x6a>;
                status = "okay";
        };
};

kernel驱动代码:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <linux/syscalls.h>
#include <linux/reboot.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/of_device.h>

#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/completion.h>
#include <asm/uaccess.h>
/*
static int i2c_read_bytes(struct i2c_client *client, u8 cmd, u8 *data, u8 data_len)
{
	struct i2c_msg msgs[2];
	int ret;
	u8 *buffer;
	buffer = kzalloc(data_len, GFP_KERNEL);
	if (!buffer)
		return-ENOMEM;;
	msgs[0].addr = client->addr;
	msgs[0].flags = client->flags;
	msgs[0].len = 1;
	msgs[0].buf = &cmd;
	msgs[1].addr = client->addr;
	msgs[1].flags = client->flags | I2C_M_RD;
	msgs[1].len = data_len;
	msgs[1].buf = buffer;
	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
	if (ret < 0)
		dev_err(&client->adapter->dev, "i2c read failed\n");
	else
		memcpy(data, buffer, data_len);
	kfree(buffer);
	return ret;
}	
*/
static int i2c_write_bytes(struct i2c_client *client, u8 *data, u8 data_len)
{
	struct i2c_msg msgs[1];
	u8 *buffer;
	int ret = 0;

	printk("<debug>---->:%s\n",__func__);
	buffer = kzalloc(data_len + 1, GFP_KERNEL);
	if (!buffer)
		return-ENOMEM;
	memcpy(buffer, data, data_len);
	msgs[0].addr = client->addr;
	msgs[0].flags = client->flags;
	msgs[0].len = data_len;
	msgs[0].buf = buffer;
	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
	if (ret < 0)
		dev_err(&client->adapter->dev, "i2c write failed\n");
		kfree(buffer);
	return ret;
}

static struct of_device_id i2c_test_of_match[] = {
	{ .compatible = "rockchip,i2c-test"},
	{ },
};
MODULE_DEVICE_TABLE(of, i2c_test_of_match);

static int  i2c_test_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
{
	
	const struct of_device_id *match;
	uint8_t pre_cmd_data[2]={0};	
	int i;
	printk("<debug>---->:%s\n",__func__);

	pre_cmd_data[0]=0x0f;
	pre_cmd_data[1]=0xbb;

	if (i2c->dev.of_node) {
		match = of_match_device(i2c_test_of_match, &i2c->dev);
		if (!match) {
			dev_err(&i2c->dev,"Failed to find matching dt id\n");
			return -EINVAL;
		}
	}
	for(i=20; i>=0; i--){
		i2c_write_bytes(i2c,pre_cmd_data,2);
		mdelay(100);
	}
	return 0;

}
static const struct i2c_device_id i2c_ids[] = {
	{ "i2c-test" },
	{ },
};
MODULE_DEVICE_TABLE(i2c, i2c_ids);

static struct i2c_driver i2c_test_driver = {
	.driver = {
		.name = "i2c-test",
		.owner = THIS_MODULE,
		.of_match_table =of_match_ptr(i2c_test_of_match),
	},
	.probe = i2c_test_probe,
	.id_table = i2c_ids,
};

module_i2c_driver(i2c_test_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jstl <jstl@xx.xxx>");
MODULE_DESCRIPTION("i2c test driver");

测得波形
i2c波形图
从波形读得数据:0xd4 0x0f 0xbb
其中:0xd4为:i2c地址+读写标志,即:0xd4(8b’11010100)=0x6a(7b’1101010)+写标志(1b’0)
0x0f 0xbb为数据

应用层读写i2c

注:板子上i2c总线4有个i2c设备,i2c地址为0x6a
以下demo是直接在应用层读写i2c设备,因此,kernel中只要把i2c接口打开即可。
dts把i2c4开启:

&i2c4 {
    status = "okay";
};

应用层代码:

#include "encryptic.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h> 
#include <sys/ioctl.h>
#include <stdbool.h>

#define LOG_TAG 	"encryptchip"
#define DEBUG

#define I2C_M_WR        0x0000
#define I2C_M_RD		0x0001	/* read data, from slave to master */

#define IIC_MSGBUF_LENGTH		256
#define IIC_READ_COUNT_MAX		50

#define IC_CMD_SN_LENGTH		19
#define IC_SN_OFFSET			1
#define IC_SN_LENGTH			16
#define IC_CMD_VER_LENGTH		5
#define IC_VER_OFFSET			1
#define IC_VER_LENGTH			2
#define IC_CERT_RANDOM_OFFSET	1
#define IC_CERT_RANDOM_LENGTH	48
#define IC_CERT_DATA_OFFSET		49
#define IC_CERT_DATA_LENGTH		131
#define IC_CERT_LENGTH			182
#define IC_SIGN_LENGTH			51
#define IC_CMD_KEY_LENGTH		51
#define IC_KEY_OFFSET			1

int I2C_WriteNbyte(unsigned char i2c_no, unsigned char dev_addr, unsigned char reg_addr, unsigned char send_len, unsigned char send_buf[])
{
    int tmp_ret = 0;
    struct i2c_rdwr_ioctl_data ctl_data;
    int device_fd = -1;
 
    if (send_buf == NULL) {
		printf("\nwrite data buf is NULL\n");
        return -1;
    }
 		if (send_len < 1)
 			{
 				printf("write data length error \n");
 				return -1;
 			}
    if (i2c_no == 7) {
        device_fd = open("/dev/i2c-7", O_RDWR); 
     } else if (i2c_no == 4) {
        device_fd = open("/dev/i2c-4", O_RDWR);        
    } else if (i2c_no == 2) {
        device_fd = open("/dev/i2c-2", O_RDWR);
    } else if (i2c_no == 1) {
        device_fd = open("/dev/i2c-1", O_RDWR);
    } else {
        device_fd = open("/dev/i2c-0", O_RDWR);
    }
    if (device_fd < 0) {
				printf("\nwrite open error!\n");
        return -1;
    }
 		
 	ctl_data.nmsgs = 1;
    ctl_data.msgs = (struct i2c_msg *)malloc(ctl_data.nmsgs * sizeof(struct i2c_msg));  
    if (!ctl_data.msgs){  
        printf("Memory alloc error\n");   
        return 0;  
    }  
 		
    ctl_data.msgs[0].len = send_len+1;
    ctl_data.msgs[0].addr = dev_addr;  
    ctl_data.msgs[0].flags = I2C_M_WR;
    ctl_data.msgs[0].buf	= (unsigned char*)malloc(send_len+1);
    ctl_data.msgs[0].buf[0]	= reg_addr;
    
    memcpy(&ctl_data.msgs[0].buf[1], send_buf, send_len);

		#ifdef DEBUG
		for (int i=0; i<send_len+1; i++)
		{
			printf("\n %02x",ctl_data.msgs[0].buf[i]);
		}
		printf("\n");
		#endif
		
    tmp_ret = ioctl(device_fd, I2C_RDWR, &ctl_data);
    printf("<debug>:send_len=%d\n",ctl_data.msgs[0].len);
    if (tmp_ret < 0)
    {
			printf("\nwrite error!\n");
			close(device_fd);
			
       return -1;
    }
 
    usleep(10 * 1000);
    if (device_fd >= 0) {
        close(device_fd);
        device_fd = -1;
    }
    return tmp_ret;
}
 
int I2C_ReadNbyte(unsigned char i2c_no, unsigned char dev_addr, unsigned char reg_addr, unsigned char rev_len, unsigned char rev_buf[])
{
    int tmp_ret = -1;
    struct i2c_rdwr_ioctl_data ctl_data;
    int device_fd = -1;

    if (rev_buf == NULL) {
		printf("\nread data buf is NULL\n");
        return -1;
    }

    if (i2c_no == 7) {
        device_fd = open("/dev/i2c-7", O_RDWR); 
    } else if (i2c_no == 4) {
        device_fd = open("/dev/i2c-4", O_RDWR);        
    } else if (i2c_no == 2) {
        device_fd = open("/dev/i2c-2", O_RDWR);
    } else if (i2c_no == 1) {
        device_fd = open("/dev/i2c-1", O_RDWR);
    } else {
        device_fd = open("/dev/i2c-0", O_RDWR);
    }
    if (device_fd < 0) {
		printf("\nread open error!\n");
        return -1;
    }
 
 		ctl_data.nmsgs = 2;
    ctl_data.msgs = (struct i2c_msg *)malloc(ctl_data.nmsgs * sizeof(struct i2c_msg));  
    if (!ctl_data.msgs){  
        printf("Memory alloc error\n");   
        return 0;  
    }  
 	
    ctl_data.msgs[0].len = 1;
    ctl_data.msgs[0].addr = dev_addr;  
    ctl_data.msgs[0].flags = I2C_M_WR;
    ctl_data.msgs[0].buf	= (unsigned char*)malloc(1); 
    ctl_data.msgs[0].buf[0] = reg_addr;
        
    ctl_data.msgs[1].len = rev_len;  
    ctl_data.msgs[1].addr = dev_addr;  
    ctl_data.msgs[1].flags = I2C_M_RD;
    ctl_data.msgs[1].buf = (unsigned char*)malloc(rev_len);
 
    memset(ctl_data.msgs[1].buf, 0, rev_len);
 
    tmp_ret = ioctl(device_fd, I2C_RDWR, &ctl_data);
    if (tmp_ret < 0)
    {
			printf("\nread error!\n");
			close(device_fd);
			
       return -1;
    }
 		memcpy(rev_buf, ctl_data.msgs[1].buf, rev_len);

    usleep(10 * 1000);
 		
    if (device_fd >= 0) {
        close(device_fd);
        device_fd = -1;
    }
    return tmp_ret;
}

static bool cmdToBuffer(unsigned char *cmd, int cmdlen, unsigned char databuf[])
{
	int i;
	unsigned char Xor;
	
	Xor = cmdlen;
	databuf[0] = cmdlen;
	
	for(i=0;i<cmdlen;i++)
	{
		databuf[i+1] = cmd[i];
		Xor ^= cmd[i];
	}
	databuf[i+1] = Xor;
	return true;
}

static int checkResult(unsigned char Result[], int ResultLen)
{
	int i;
	unsigned char Xor;

	if(Result[ResultLen-2] != 0x90)
		return 1;
	
	Xor = 0;
	for(i=0;i<(ResultLen-1);i++)
	{
		Xor ^= Result[i];
	}

	if(Xor != Result[i])
		return 2;
	return 0;
}

void EncryptIC_Init(void)
{
	printf("\nEncryptIC_Init\n");
}

int iic_send_recv(char* send, int send_len,char* recv,int recv_len)
{
	int i = 0;
	unsigned char buf[128];
	int nret;
	unsigned char send_data[10];
	
	memset(send_data, 0, sizeof(send_data));
	cmdToBuffer(send, send_len, send_data);

	while(1)
	{ 
			sleep(2);
			
			#ifdef DEBUG
			//send_data[0]= 0x0f;			
			printf("write data:\n");
			for(i=0;i<3;i++)
				printf("%02x",send_data[i]);
			printf("\n");
			#endif
			//I2C_WriteNbyte(4, 0x2a, 0x54, 3, send_data);
			I2C_WriteNbyte(4, 0x6a, 0x06, 1, send_data);
			usleep(10*1000);
			sleep(1);
			
			memset(buf, 0, sizeof(buf));
			
			//I2C_ReadNbyte(4, 0x2a, 0x55, recv_len+2, buf);
			//I2C_ReadNbyte(4, 0x6a, 0x06, 1, buf);
			
			//for(i = 0; i < IIC_READ_COUNT_MAX; i++) 
			{
				//if(I2C_ReadNbyte(7, 0x2a, 0, recv_len+2, buf) >= 0)
					//break;
			}
			
			//if(i >= IIC_READ_COUNT_MAX)
				//return -1;
			
			#ifdef DEBUG
			printf("read data:\n");
			for(i=0;i<0x12;i++)
				printf("%02x",buf[i]);
			printf("\n");
			#endif	
}
	nret = checkResult(buf, recv_len+2);
	if(nret != 0)
		return nret;
	
	for(i=0;i<recv_len;i++)
		recv[i] = buf[i+1];
		
	return 0;
}
int main()
{
	int ret = 0;
	unsigned char read_cert[1] = {0x01};
	unsigned char cert_buf[128] = {0};
	
	ret = iic_send_recv(read_cert,1,cert_buf,0x12);
		
	printf("\nret:%d\n",ret);
	
	#ifdef DEBUG
	printf("cert_buf:\n");
	for(int i=0;i<113;i++)
		printf("%02x",cert_buf[i]);
	#endif
	return 0;
}

执行应用:

root@linaro-alip:~# encryptic
write data:
010100

06
01
<debug>:send_len=2
read data:
000000000000000000000000000000000000

测得波形
i2c波形图
从波形读得数据:0xd4 0x06 0x01
其中:0xd4为:i2c地址+写标志,即:0xd4(8b’11010100)=0x6a(7b’1101010)+写标志(1b’0)
0x06 0x01为数据。

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

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

相关文章

C语言之指针详解(3)

目录 本章重点 1. 字符指针 2. 数组指针 3. 指针数组 4. 数组传参和指针传参 5. 函数指针 6. 函数指针数组 7. 指向函数指针数组的指针 8. 回调函数 9. 指针和数组面试题的解析、 4. 数组参数、指针参数 我们来看一维数组传参 #include<stdio.h> void test(in…

Rust语言从入门到入坑——(2)Rust在windows上搭建开发环境

文章目录 0 引入1、搭建 Visual Studio Code 开发环境1.1、安装 Rust 编译工具1.2 、VS Code安装 2、官网在线3、总结4、引用 0 引入 开始搭建一个适合在windows上运行的Rust环境。 Rust支持的程序语言很多&#xff1a;可详见官网介绍 1、搭建 Visual Studio Code 开发环境 …

Systrace分析知识点

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、抓取Systrace二、CPU模块知识点三、input 点击事件处理流程四、Vsync 事件处理五、Android 绘制一帧流程分析六、Camx Trace TAG开启方法七、参考文…

管理类联考——英语——趣味篇——刷题需要实现什么目的?

刷题需要实现什么目的&#xff1f;  第一&#xff1a;在真题中巩固考研英语基础 在掌握一定的词汇量和句子分析能力后&#xff0c;开始一字不漏、逐字逐句的完成历年真题的超精读。抄写真题中生词&#xff0c;继续夯实词汇、分析长难句翻译&#xff0c;弄清楚题目中正确选项为…

Science: 功能饮料里面的这种添加剂会使动物寿命更长——这对人类意味着什么尚不清楚...

一项研究表明&#xff0c;当喂食大量牛磺酸&#xff08;一种常见的健康补充剂和能量饮料成分&#xff0c;最早从牛黄中分离出来&#xff09;时&#xff0c;老年小鼠、线虫和猴子可以活得更长或更健康。研究人员证明&#xff0c;这种天然氨基酸的水平与动物的衰老有关&#xff0…

STM32之外设DMA

DMA(Direct Memory Access)—直接存储器存取&#xff0c;是单片机的一个外设&#xff0c;它的主要功能是用来搬数据&#xff0c;但是不需要占用 CPU&#xff0c;即在传输数据的时候&#xff0c;CPU 可以干其他的事情&#xff0c;好像是多线程一样。数据传输支持从外设到存储器或…

Triton教程 -- 模型仓库

Triton教程 – 模型仓库 文章目录 Triton教程 -- 模型仓库存储库布局模型存储库位置本地文件系统 具有环境变量的云存储谷歌云存储S3Azure 存储带凭证文件的云存储&#xff08;测试版&#xff09; 模型版本模型文件TensorRT 模型ONNX 模型TorchScript 模型TensorFlow 模型OpenV…

滴滴实时数据链路建设组件选型实践篇

写在前面 随着滴滴内部技术栈的不断统一&#xff0c;实时相关技术组件资源的不断整合&#xff0c;各业务线实时数据相关开发经验的不断沉淀&#xff0c;基本形成了一套面向公司不同业务场景需求的最佳技术选型和具体落地方案。但同时我们也发现&#xff0c;大部分实时开发同学在…

React基础教程(一):React简介

React基础教程(一)&#xff1a;React简介 1、React是什么&#xff1f; 发生请求获取数据处理数据&#xff08;过滤&#xff0c;整理格式等&#xff09;操作DOM呈现页面 2、谁开发的 由Facebook的软件工程师 Jordan Walke创建于2011年部署于Facebook的newsfeed随后在2012年部…

一本书让你彻底搞懂安卓系统性能优化

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Transformer详解,中文版架构图

2.2.1 “编码器-解码器”架构 Seq2Seq 思想就是将网络的输入输出分别看作不同的序列&#xff0c;然后实现序列到序列 的映射&#xff0c;其经典实现结构就是“编码器-解码器”框架。编码器-解码器框架如图 2.7 所 示。 图2.7 编码器-解码器的基本框架 在 Seq2Seq 思想应用于自然…

机器鸟实现摆动尾巴功能

1. 功能说明 本文示例将实现R329样机机器鸟摆动尾巴的功能。 2. 电子硬件 在这个示例中&#xff0c;我们采用了以下硬件&#xff0c;请大家参考&#xff1a; 主控板 Basra主控板&#xff08;兼容Arduino Uno&#xff09;‍ 扩展板 Bigfish2.1扩展板‍ 电池7.4V锂电池 电路连接…

新的挑战:WebGL

这段时间一直在死磕 Chromium 的 8K 高清视频播放&#xff0c;虽然之前写过一些关键技术的实现&#xff0c;主要难点差不多攻破&#xff0c;但投入到产品中&#xff0c;依然还要解决很多实际中的问题&#xff0c;比如卡顿、格式支持、音视频不同步等等。前期的相关文章&#xf…

RocketMQ基础API使用以及基本原理探究

文章目录 同步发送异步发送单向发送拉模式随机获取一个queue的消息指定一个queue的消息 顺序消息广播消息延迟消息批量消息过滤消息Tag过滤sql过滤 事务消息RocketMQ常见问题RocketMQ如何保证消息不丢失&#xff1f;RocketMQ的消息持久化机制RocketMQ如何保证消息顺序RocketMQ事…

Day07 Python函数详解

文章目录 第四章 Python函数使用4.1. 函数介绍4.2. 函数的定义与使用4.2.1. 函数的定义4.2.2. 调用 4.3. 函数的参数4.4. 函数的返回值4.4.1. 返回值介绍4.4.2. None类型 4.5. 函数说明4.5.1. 函数注释4.5.2. 函数的4中定义方式4.5.3. 函数的调用 4.6. 函数的嵌套调用4.7. 函数…

哪款 IMG BXS GPU 适合您的汽车?

Imagination 是汽车行业领先的图形处理器供应商。Imagination 的 GPU IP 经过了九代更新迭代&#xff0c;为车辆提供了舒适性和安全性&#xff0c;在汽车行业的总出货量接近 5 亿。通过将响应迅速的 HMI&#xff08;人机界面&#xff09;与功能日益强大的高级驾驶员辅助系统相结…

Linux:主机状态监控

查看系统的资源占用 可以通过top命令&#xff0c;查看系统CPU、内存使用情况 top命令内容详解&#xff1a; 第一行&#xff1a;top&#xff1a;命令名称&#xff0c;10.49.16&#xff1a;当前系统时间&#xff0c;up 4:40&#xff1a;启动了4小时40分&#xff0c;4 users&#…

Aspose.Pdf使用教程:为PDF文件添加swf注释

Aspose.PDF 是一款高级PDF处理API&#xff0c;可以在跨平台应用程序中轻松生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现&#xff0c;保护和打印文档。无需使用Adobe Acrobat。此外&#xff0c;API提供压缩选项&#xff0c;表创建和处理&#xff0c;图形和图像功能&am…

STL之priority_queue与仿函数

目录 一.仿函数1.介绍2.示例 二.priority_queue1.介绍2.成员函数3.模拟实现4.使用 三.其他1.typename Container::value_type 一.仿函数 1.介绍 函数对象&#xff0c;又称仿函数&#xff0c;是可以像函数一样使用的对象&#xff0c;其原理就是重载了函数调用符&#xff1a;()…

浅谈数据中台之标签管理平台

在现如今的大数据时代&#xff0c;相信大家一定了解或者听说过下列几个场景&#xff1a; 购物APP&#xff1a;千人千面&#xff0c;意思不同用户使用相关的产品感觉是不一样的&#xff0c;不同用户看到的购物APP首页推荐内容和其他相关推荐流信息可能是完全不同的。 社交APP&…