STM32 软件I2C读写
单片机学习!
目录
前言
一、软件I2C读写代码框架
二、I2C初始化
三、六个时序基本单元
3.1 引脚操作的封装和改名
3.2 起始条件执行逻辑
3.3 终止条件执行逻辑
3.4 发送一个字节
3.5 接收一个字节
3.5 发送应答&接收应答
3.5.1 发送应答
3.5.2 接收应答
总结
前言
本文介绍了软件I2C读写代码,I2C协议层重点关注的是使用的两个引脚、I2C配置、时序的高低电平等协议相关的内容。
一、软件I2C读写代码框架
代码整体框架:首先建立I2C通信层的.c和.h模块,在通信层里写好I2C底层的GPIO初始化和6个时序基本单元,也就是起始、终止、发送一个字节、接收一个字节、发送应答和接收应答。
由于本代码使用软件I2C,所以I2C的库函数暂时不用看,软件I2C只需要用GPIO的读写函数就行了。
软件I2C初始化要做两个任务:
- 第一个任务,把SCL和SDA都初始化为开漏输出模式;
- 第二个任务,把SCL和SDA置高电平。
二、I2C初始化
当前接线SCL是PB10,SDA是PB11。所以要开启GPIOB,PB10和PB11都要配置成开漏输出的模式。虽然开漏输出名字上带了个输出,但并不代表它只能输出,开漏输出模式仍然可以输入。输入时先输出1再直接读取输入数据寄存器就行了。这个过程在之前博文讲I2C硬件规定时介绍过。
代码示例:
void MyI2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode= GPIO_Mode_Out_OD;GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);//把GPIOB的PB10和PB11都置高电平}
调用MyI2C_Init函数,PB10和PB11两个端口就被初始化为开漏输出模式,然后释放总线。SCL和SDA处于高电平,此时I2C总线处于空闲状态。
三、六个时序基本单元
3.1 引脚操作的封装和改名
第一个基本单元,起始条件。根据波形图,首先把SCL和SDA都确保释放。然后先拉低SDA,再拉低SCL,这样就能产生起始条件了。
在这里可以不断地调用SetBits和ResetBit函数,来手动翻转高低电平。但是这样做会在后面的程序中出现非常多的地方来指定这个GPIO端口号。一方面,这样做语义不是很明显;另一方面,如果之后需要换一个端口那就需要改动非常多的地方。所以这时就需要在上面做个定义,把端口号统一替换一个名字,这样无论是语义还是端口的修改都会非常方便。
给端口号换一个名字有很多方法都能实现功能,一种简单的替换方法就是宏定义,
#define SCL_PORT GPIOB
#define SCL_PIN GPIO_Pin_10
之后如果想释放SCL,就调用SetBits函数将GPIOB替换为SCL_PORT;将GPIO_Pin_10替换为SCL_PIN。
GPIO_SetBits(GPIOB,GPIO_Pin_10);
GPIO_SetBits(SCL_PORT,SCL_PIN);
这样语义比较明确,而且修改引脚的时候直接在上面修改一下宏定义,下面所有引用宏定义的地方都会自动更改。但是这样宏定义的方法如果换到一个主频很高的单片机中,需要对软件时序进行延时操作的时候,也不太方便进一步修改。所以这里也可以直接一点,定义函数对操作端口的库函数进行封装,这样既容易理解,又方便加软件延时。
定义函数对操作端口的库函数进行封装代码示例:
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
}
MyI2C_W_SCL这个W代表写的意思,函数里调用GPIO_WriteBit函数,第三个参数给BitValue强转为BitAction类型。
这样套一个函数替换之后,后面再调用MyI2C_W_SCL函数,参数给1或0,就可以释放或拉低SCL了。
如果要把这个程序移植到别的单片机,就可以把这个函数里的操作替换为其他单片机对应的操作。
比如SCL是51单片机的P10口,就可以把
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
上面这句替换为下面这句
P10=BitValue;
另外如果单片机主频比较快,函数里也非常方便加一些延时,比如这里要求每次操作引脚之后都要延时10us,就可以在引脚操作之后调用延时函数进行引脚延时操作了。I2C可以慢一些,多慢都行,但是快的话还是要看一下手册里对时序时间的要求。
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);Delay_us(10);
}
同理封装一下SDA:
//SDA封装
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);Delay_us(10);
}
另外还要再来个读SDA的函数,因为STM32库函数中,读和写不是同一个寄存器
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);Delay_us(10);return BitValue;
}
函数MyI2C_R_SDA中R代表读的意思,读出SDA之后也延时10us,返回读到SDA线的电平。
有了以上三个函数的封装就实现了函数名称、端口号的替换。同时也可以很方便地修改时序的延时。当需要替换端口时,或者把这个程序移植到别的单片机中时就只需要对这前4个函数里的操作对应更改,后面的函数都调用这里封装的新名称进行操作,这样在移植的时候后面的部分就不需要再进行修改了。
以上关于引脚操作的封装和改名就完成了。
3.2 起始条件执行逻辑
在起始条件里需要先把SCL和SDA都释放,也就是都输出1.然后先拉低SDA,再拉低SCL。
代码示例:
void MyI2C_Start(void)
{MyI2C_W_SDA(1);MyI2C_W_SCL(1);MyI2C_W_SDA(0);MyI2C_W_SCL(0);
}
代码中需注意,最好将释放SDA的放在最前面,这样更符合起始条件的波形,这样保险一些。
如果起始条件之前的SCL和SDA已经是高电平了,那不管先释放哪一个都是一样的效果。但是看下图中Sr这里,Start还要兼容这里的重复起始条件Sr,Sr最开始SCL是低电平,SDA电平不敢确定。所以保险起见,趁着SCL是低电平,先释放SDA;再释放SCL。这时SDA和SCL都是高电平。
然后再拉低SDA,拉低SCL。这样Start就可以兼容起始条件和重复起始条件了。
3.3 终止条件执行逻辑
当Stop开始,如果SDA和SCL都已经是低电平了,那就先释放SCL,再释放SDA就行了。但是在时序单元开始时,SDA并不一定是低电平。所以为了确保之后释放SDA能产生上升沿,要在时序单元开始时,先拉低SDA,然后再释放SCL、释放SDA。
所以在程序里Stop的执行逻辑是:先拉低SDA,再释放SCL,再释放SDA。
void MyI2C_Stop(void)
{MyI2C_W_SDA(0);MyI2C_W_SCL(1);MyI2C_W_SDA(1);
}
终止条件后,SCL和SDA都回归到高电平。
3.4 发送一个字节
函数的参数是要发送的一个字节。发送一个字节时序开始时,SCL是低电平。实际上除了终止条件,SCL以高电平结束,所有的单元都会保证SCL以低电平结束,这样方便各个单元的拼接。
上图所示,SCL低电平,变换数据;SCL高电平,保持数据稳定。由于是高位先行,所以变换数据的时候按照先放最高位,再放次高位,最后最低位,这样的顺序依次把一个字节的每一位放在SDA线上。每放完一位后,执行释放SCL,拉低SCL的操作,驱动时钟运转。
在程序中的操作就是,首先趁SCL低电平,先把Byte的最高位放在SDA线上,写SDA,写1还是写0取决于Byte的最高位。这里需要取出Byte的最高位,可以用 (Byte & 0x80) ,这是一个单片机中非常常见的操作。就是用按位与的方式取出数据的某一位或某几位。
按位与取出数据某一位的运行逻辑:
- Byte可以是任意的数据 xxxx xxxx
- 0x80就是 1000 0000
- Byte与0x80按位与结果 x000 0000
低7位因为和0相与,所以结果不受Byte数据的影响,始终是0;
最高位和1相与,所以结果取决于Byte的最高位。
- 如果Byte的最高位是1,结果就是1000 0000,也就是0x80;
- 如果Byte最高位是0,结果就是0000 0000,也就是0x00.
这就相当于把Byte的最高位取出来了。但是注意,Byte & 0x80 这个式子计算结果是0x80或0x00,而不是1或0.不过上文函数将参数BitValue强转为BitAction类型,就是非0即1,所以即使传入0x80也相当于传入了1,代码中可以直接写 (Byte & 0x80)。
上面方法步骤将最高位数据放好后,再释放SCL,再拉低SCL,驱动时钟走一个脉冲。
MyI2C_W_SDA(Byte & 0x80);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
当释放SCL之后,从机就会立刻把放好在SDA的数据读走,再拉低SCL,然后就可以放下一个数据了,下一位是次高位。
MyI2C_W_SDA(Byte & 0x40);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
写SDA,数据与0x40,取出次高位,再驱动SCL,来一个时钟。
之后继续,写SDA,数据与0x20,取出再下一位,再驱动SCL,来一个时钟。
MyI2C_W_SDA(Byte & 0x20);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
这样来8次这个操作,就可以写入一个字节。不过可以套个for循环,循环8次减少代码量。
void MyI2C_SendByte(uint8_t Byte);
{uint8_t i;for(i=0;i<8;i++){MyI2C_W_SDA(Byte & (0x80 >>i));MyI2C_W_SCL(1);MyI2C_W_SCL(0);}
}
定义一个迭代变量i,循环八次,然后把上述重复的操作单元放在里面。
第一次循环 i=0,需要 (Byte & 0x80)
第二次循环 i=1,需要 (Byte & 0x40)
第三次循环 i=2,需要 (Byte & 0x20)
...
所以这里的通式就是(Byte & (0x80 >>i)) 注意要加个括号,确保优先级。
3.5 接收一个字节
接收一个字节时序开始时,SCL低电平,此时从机需要把数据放到SDA上,为了防止主机干扰从机写入数据,主机需要先释放SDA,释放SDA也相当于切换为输入模式。那在SCL低电平时,从机会把数据放到SDA。
- 如果从机想发1,就释放SDA;
- 如果从机想发0,就拉低SDA;
然后主机释放SCL,在SCL高电平期间,读取SDA,再拉低SCL。SCL低电平期间从机就会把下一位数据放到SDA上。这样重复八次主机就能得到一个字节了。
在这里可以发现,SCL低电平变换数据,高电平读取数据。实际上就是一种读写分离的设计:
- 低电平时间定义为写的时间;
- 高电平时间定义为读的时间。
那在SCL高电平期间,如果非要动SDA来破坏读写规则的话,那这个信号就是起始条件和终止条件。SCL高电平时,SDA下降沿为起始条件,SDA上升沿为终止条件。这个设计也保证了起始条件和终止条件的特异性,能够在连续不断的波形中快速地定位起始和终止。因为起始终止与数据传输的波形有本质区别:
- 数据传输SCL高电平不许动SDA;
- 起始终止SCL高电平必须动SDA。
这就是这个设计的巧妙之处。
进接收一个字节的时序之后,SCL是低电平,主机释放SDA。从机把数据放到SDA时,主机释放SCL,SCL高电平时,主机就能读取数据了。
uint8_t MyI2C_ReceiveByte(void)
{uint8_t Byte = 0x00;MyI2C_W_SDA(1);MyI2C_W_SCL(1);if(MyI2C_R_SDA() == 1){Byte |= 0x80;}MyI2C_W_SCL(0);}
读取数据用MyI2C_R_SDA函数,套个if,如果读SDA为1,if成立,就知道接收这一位为1了。先定义一个数据Byte,给初始值0x00.
- 如果第一次读SDA为1,就Byte |= 0x80; 把Byte最高位置1;
- 如果第一次读SDA为0,if条件不成立,Byte默认为0x00,就相当于写如0了。
读取一位之后,再把SCL拉低。这时从机就会把下一位数据放到SDA上。再执行下方代码相同的流程8次就能接收一个字节了。
MyI2C_W_SCL(1);if(MyI2C_R_SDA() == 1){Byte |= 0x80;}MyI2C_W_SCL(0);
可以用个for循环,把上方代码放进去,循环8次,依次从高位到低位进行判断。所以在写个和发送一个字节一样的移位操作。就可以接收一个字节了。最后return Byte; 把接收的Byte返回去。
3.5 发送应答&接收应答
发送应答和接收应答其实就是发送一个字节和接收一个字节的简化版:
- 发送一个字节是发8位,发送应答是发1位;
- 接收一个字节是收8位,接收应答是收1位。
所以程序这里可以参照发送一个字节和接收一个字节来修改。
3.5.1 发送应答
将发送一个字节的代码中的for循环去掉,修改一下。
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
}
现在的逻辑是:函数进来时,SCL低电平。
- 主机把AckBit放到SDA上。
- SCL高电平,从机读取应答。
- SCL低电平,进入下一个时序单元。
3.5.2 接收应答
将接收一个字节的代码中的for循环去掉,修改一下。读SDA时,直接把读到的值,赋值给AckBit就行了。最后返回读AckBit。
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit;MyI2C_W_SDA(1);MyI2C_W_SCL(1);AckBit = MyI2C_R_SDA();MyI2C_W_SCL(0);return AckBit;
}
现在的逻辑是:函数进来时,SCL低电平。
- 主机释放SDA,防止干扰从机,同时从机把应答位放在SDA上。
- SCL高电平,主机读取应答位。
- SCL低电平,进入下一个时序单元。
代码疑问:
1.程序里主机先把SDA置1了,然后再读取SDA,应答位就肯定是1吗?
可以从两点分析:
- 第一,I2C的引脚都是开漏输出+弱上拉的配置,主机输出1并不是强置SDA为高电平,而是释放SDA。
- 第二,I2C是在进行通信,主机释放了SDA,那从机如果在的话,从机是有义务把SDA再拉低的。
所以即使主机在前面把SDA置1了,之后再读取SDA,读到的值也可能是0.
- 读到0代表从机给了应答。
- 读到1代表从机没给应答。
2.接收一个字节的代码里不断读取SDA,但是for循环中又没写过SDA,那SDA读出来应该始终是一个值吗?
I2C进行通信是有从机的,当主机不断驱动SCL时钟时,从机就有义务去改变SDA的电平。所以主机每次循环读取SDA的时候,这个读取到的数据是从机控制的,这个读取到的数据也正是从机想要给我们发送的数据。这也就是这个时序叫做接收一个字节。
通信是有时序的,有些引脚的电平之前读和之后读,读的值就是不一样的。
四、总代码示例
.c代码示例:
#include "stm32f10x.h" // Device header
#include "Delay.h" //CL封装
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);Delay_us(10);
}//SDA封装
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);Delay_us(10);
}//读SDA的函数
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);Delay_us(10);return BitValue;
}void MyI2C_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode= GPIO_Mode_Out_OD;GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStruct);GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);//把GPIOB的PB10和PB11都置高电平}//六个时序基本单元//起始条件
void MyI2C_Start(void)
{MyI2C_W_SDA(1);MyI2C_W_SCL(1);MyI2C_W_SDA(0);MyI2C_W_SCL(0);
}//终止条件
void MyI2C_Stop(void)
{MyI2C_W_SDA(0);MyI2C_W_SCL(1);MyI2C_W_SDA(1);
}//发送一个字节
void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for(i=0;i<8;i++){MyI2C_W_SDA(Byte & (0x80 >>i));MyI2C_W_SCL(1);MyI2C_W_SCL(0);}
}//接收一个字节
uint8_t MyI2C_ReceiveByte(void)
{uint8_t i, Byte = 0x00;MyI2C_W_SDA(1);for(i = 0 ; i < 8 ; i++){MyI2C_W_SCL(1);if(MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}MyI2C_W_SCL(0);}return Byte;
}//发送应答
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit);MyI2C_W_SCL(1);MyI2C_W_SCL(0);
}//接收应答
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit;MyI2C_W_SDA(1);MyI2C_W_SCL(1);AckBit = MyI2C_R_SDA();MyI2C_W_SCL(0);return AckBit;
}
.h代码示例:
#ifndef __MYI2C_H__
#define __MYI2C_H__void MyI2C_Init(void);//起始条件
void MyI2C_Start(void);//终止条件
void MyI2C_Stop(void);//发送一个字节
void MyI2C_SendByte(uint8_t Byte);//接收一个字节
uint8_t MyI2C_ReceiveByte(void);//发送应答
void MyI2C_SendAck(uint8_t AckBit);//接收应答
uint8_t MyI2C_ReceiveAck(void);#endif
总结
以上就是今天要讲的内容,本文仅仅简单介绍了软件I2C读写代码。其中有使用的两个引脚、I2C配置、时序的高低电平等协议相关内容的代码配置细节。
相关文章:

STM32 软件I2C读写
单片机学习! 目录 前言 一、软件I2C读写代码框架 二、I2C初始化 三、六个时序基本单元 3.1 引脚操作的封装和改名 3.2 起始条件执行逻辑 3.3 终止条件执行逻辑 3.4 发送一个字节 3.5 接收一个字节 3.5 发送应答&接收应答 3.5.1 发送应答 3.5.2 接…...
neo4j学习笔记
图数据库 图数据库是基于图论实现的一种NoSQL数据库,其数据存储结构和数据查询方式都是图论为基础的,图数据库主要用于存储更多的连接数据。 图论(GraphTheory)是数学的一个分支。图论以图为研究对象,图论的图是由若干…...

【动手学电机驱动】STM32-MBD(2)将 Simulink 模型部署到 STM32G431 开发板
STM32-MBD(1)安装 STM32 硬件支持包 STM32-MBD(2)Simulink 模型部署 【动手学电机驱动】STM32-MBD(2)Simulink 模型部署 1. 软硬件条件和环境测试1.1 软硬件条件1.2 开发环境测试 2. 创建基于 STM32 处理器…...

Nginx代理本地exe服务http为https
Nginx代理本地exe服务http为https 下载NginxNginx命令exe服务http代理为https 下载Nginx 点击下载Nginx 下载好之后是一个压缩包,解压放到没有中文的路径下就可以了 Nginx命令 调出cmd窗口cd到安装路径 输入:nginx -v 查看版本 nginx -hÿ…...
C++: glibc: pthread: pthread_cond_destroy,程序hang一例
今天碰到一个程序hang的情况。程序在退出的时候,调用到了pthread_cond_destroy,但是另一个线程还在pthread_cond_timedwait。应该是死锁的一个例子。应该查看libpthread.so的二进制文件,查看具体是在等什么。 Thread 1 (Thread 0x7f7028037580 (LWP 38)): #0 0x00007f7022e…...

【中间件】docker+kafka单节点部署---zookeeper模式
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言消息中间件介绍1. KRaft模式2. zookeeper模式2.1. 单节点部署安装验证 前言 最近生产环境上准备部署ELFK日志监控,先在测试环境部署单节点kafka验证…...

深入Android架构(从线程到AIDL)_08 认识Android的主线程
目录 3、 认识Android的主线程(又称UI线程) 复习: 各进程(Process)里的主线程编辑 UI线程的责任: 迅速处理UI事件 举例 3、 认识Android的主线程(又称UI线程) 复习: 各进程(Process)里的主线程 UI线程的责任: 迅速处理UI事…...

集线器,交换机,路由器,mac地址和ip地址知识记录总结
一篇很不错的视频简介 基本功能 从使用方面来说,都是为了网络传输的标识,和机器确定访问对象 集线器、交换机和路由器 常听到路由器和集线器,下面是区别: 集线器 集线器:一个简单的物理扩展接口数量的物理硬件。…...

【VUE】使用create-vue快速创建一个vue + vite +vue-route 等其他查看的工程
create-vue 简介 GitHub:https://github.com/vuejs/create-vue 创建的选项有多个,具体的可以看下方截图,当创建完成的时候可以发现工程中是自带vite的。 下面对其中的各种内容进行简单的说明 JSX (可以选择,但是我感觉没什么必要) 全称:JavaScript XML 允许你在 Java…...

Jetpack Compose 学习笔记(一)—— 快速上手
本篇主要是对 Jetpack Compose 有一个宏观上的了解。 1、Jetpack Compose 是什么与优势 Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。 Compose 的优势&am…...
Kafka3.x KRaft 模式 (没有zookeeper) 常用命令
版本号:kafka_2.12-3.7.0 说明:如有多个地址,用逗号分隔 创建主题 bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic demo --partitions 1 --replication-factor 1删除主题 bin/kafka-topics.sh --delete --boots…...

Leetcode 最大正方形
java 实现 class Solution {public int maximalSquare(char[][] matrix) {//处理特殊情况if(matrix null || matrix.length 0 || matrix[0].length 0) return 0;int rows matrix.length;int cols matrix[0].length;int[][] dp new int[rows][cols]; //dp[i][j]的含义是以…...
ubuntu22.04录屏黑屏,飞书共享屏幕黑屏问题
参考https://cloud.tencent.com/developer/ask/sof/116470494 电脑是联想x1笔记本,显卡是intel的,nvidia显卡好像没看见这种问题。 sudo apt update sudo apt install xserver-xorg打开custom.conf, sudo gedit /etc/gdm3/custom.conf 解…...

沙箱模拟支付宝支付3--支付的实现
1 支付流程实现 演示案例 主要参考程序员青戈的视频【支付宝沙箱支付快速集成版】支付宝沙箱支付快速集成版_哔哩哔哩_bilibili 对应的源码在 alipay-demo: 使用支付宝沙箱实现支付功能 - Gitee.com 以下是完整的实现步骤 1.首先导入相关的依赖 <?xml version"1…...

Golang的代码质量分析工具
Golang的代码质量分析工具 一、介绍 作为一种高效、简洁、可靠的编程语言,被越来越多的开发者所喜爱和采用。而随着项目规模的增长和团队人员的扩大,代码质量的管理变得尤为重要。为了保障代码的可维护性、健壮性和可扩展性,我们需要借助代码…...

【Linux】:多线程(读写锁 自旋锁)
✨ 倘若南方知我意,莫将晚霞落黄昏 🌏 📃个人主页:island1314 🔥个人专栏:Linux—登神长阶 ⛺️ 欢迎关注:👍点赞 &#…...

Java开发 PDF文件生成方案
业务需求背景 业务端需要能够将考试答卷内容按指定格式呈现并导出为pdf格式进行存档,作为紧急需求插入。导出内容存在样式复杂性,包括特定的字体(中文)、字号、颜色,页面得有页眉、页码,数据需要进行表格聚…...
数学期望和方差
数学期望(Mathematical Expectation)和方差(Variance)是概率论和统计学中两个非常重要的概念。下面将分别对这两个概念进行解释。 数学期望 数学期望是随机变量的平均值,它描述了随机变量的中心位置。对于离散随机变…...
【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据...本篇介绍Tensor RT 的优化流程。
【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍Tensor RT 的优化流程。 【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍Tensor RT 的优化流程。 文章目录 【面试AI算法题中的知识点】方向涉及:ML/D…...

BLDC无感控制的驱动逻辑
如何知道转子已经到达预定位置,因为我们只有知道了转子到达了预定位置之后才能进行换相,这样电机才能顺滑的运转。转子位置检测常用的有三种方式。 方式一:通过过零检测,三相相电压与电机中性点电压进行比较。过零检测的优点在于…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...