文章目录
1、I2C为双线总线接口,仅有SCL(时钟线)、SDA(数据线)两根线。
2、其中两根线均为开漏输出, 均无输出高电平的能力,需要外界上拉电阻来输出高电平,SCL、SDA在空闲状态为高阻态。
3、在一个I2C通讯总线中,可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
4、传输速率标准模式下可以达到100kb/s,快速模式下可以达到400kb/s,高速模式下可达 3.4Mbit/s。

【图为I2C硬件原理图】
1、在时钟(SCL)为高电平的时候,数据总线(SDA)必须保持稳定,所以数据总线(SDA),在时钟(SCL)为低电平的时候才能改变。
2、当SCL为高电平时,SDA产生下降沿为起始状态。
3、当SCL为高电平时,SDA产生上升沿为停止状态,同时转为空闲状态。

4、当一个完整字节的指令或数据传输完成,从机设备正确接收到指令或数据后,会通过拉低SDA为低电平,向主机设备发送单比特的应答信号,表示数据或指令写入成功。若从机正确应答,可以结束或开始下一字节数据或指令的传输,否则表明数据或指令写入失败,主机就可以决定是否放弃写入或者重新发起写入。
5、每个I2C 器件都有一个器件地址,有的器件地址在出厂时地址就设置好了,用户不可以更改(例如 OV7670 器件地址为固定的 0x42),有的确定了几位,剩下几位由硬件确定(比如常见的I2C 接口的 EEPROM 存储器,留有 3 个控地址的引脚,由用户自己在硬件设计时确定)。(本帖以器件地址为A0H为例。)
6、IIC读操作

单字节写操作时序图 (单字节存储地址 )

单字节写操作时序图 (双字节存储地址 )
7、IIC写操作

关于时序的详细解释资料很多,这里就不过多解释
本程序使用EEPROM单字节写和读实现连续写入六个数据并读出。
按下按键1进行写,按下按键2进行读。
RTL视图如下:

有点丑

为了提高可移植性,将I2C控制协议单独做了一个模块。
开发平台:Vivado 2018.3
仿真平台:Modusim SE-64 10.5
开发平台:ALINX AX7Z020
模块列表如下
module i2c_ctrl
#(
parameter SLAVE_ADDR = 7'b1010000 , //EEPROM从机地址
parameter CLK_FREQ = 26'd50_000_000, //模块输入的时钟频率
parameter I2C_FREQ = 18'd250_000 //IIC_SCL的时钟频率ss
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire i2c_wr_en ,//写操作使能端口
input wire i2c_rd_en ,//读操作使能端口
input wire i2c_start , //产生一个高脉冲进行I2C开始
input wire [1:0] addr_num , //存储地址字节选择,1为单字节,2为双个字节
input wire [15:0] byte_addr ,//读或者写字节地址
input wire [7:0] i2c_data_w ,//预写入的数据
output reg [7:0] i2c_data_r ,//读出的数据
output reg i2c_done ,//读写操作完成信号
output reg i2c_clk ,//由于IIC写于运行速率过慢,故先分频为1MHz进行处理
output reg i2c_ack ,//若读写失败产生的非应答信号,将其拉高
output reg i2c_scl ,//I2C_SCL信号
inout wire i2c_sda //I2C_SDA信号
);
注:
由于SDA需要进行输入输出,且要求连接到总线上的输出端必须是开漏输出结构,给不了高电平,所以总线上所有的高电平应该是由上拉电阻上拉达到效果的,而不是由主机直接给总线赋值 1就能实现,所以我们在写这个逻辑的时候也应该遵循这个标准,当总线上要输出低电平的时候,我们就直接给总线赋值 0,要输出高电平的时候,只能将总线设置成高阻态,这样再由外部上拉电阻来上拉成高电平。
用三态门很好实现。
assign i2c_sda = (sda_en)?(sda_out?1'bZ:1'b0):1'bZ;
assign sda_in = i2c_sda;
//sda_en为1时为输入,0为输出状态
IIC控制代码较长只给出状态机第二段的写法(文末附全部代码链接)
其中用clk_bit进行时序计数器,建议结合仿真一起查看
always@(posedge i2c_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0) begin
i2c_scl <= 1'b1;
sda_out <= 1'b1;
sda_en <= 1'b1;
st_done <= 1'b0;
clk_bit <= 8'd0;
i2c_ack <= 1'd0;
i2c_done <= 1'b0;
addr_num_r <= 2'b0;
i2c_wr_en_r <= 1'd0;
i2c_rd_en_r <= 1'd0;
i2c_data_r <= 1'b0;
i2c_data_w_r <= 8'b0;
byte_addr_r <= 16'b0;
i2c_read_data_r <= 8'b0;
end
else begin
st_done <= 1'b0;
case(state)
IDLE: begin
sda_out <= 1'b1;
i2c_done <= 1'b0;
clk_bit <= 8'd0;
if(i2c_start == 1'b1) begin
addr_num_r <= addr_num;
i2c_wr_en_r <= i2c_wr_en;
i2c_rd_en_r <= i2c_rd_en;
i2c_data_w_r <= i2c_data_w;
byte_addr_r <= byte_addr;
end
end
START: begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :begin
sda_out <= 1'b0;
st_done <= 1'b1;
end
1 :clk_bit <= 1'b0;
endcase
end
SEND_SLADDR_W: begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0,4,8,12,16,20,24,28,32 :i2c_scl <= 1'b0;
2,6,10,14,18,22,26,30,34 :i2c_scl <= 1'b1;
1 :sda_out <= SLAVE_ADDR[6];
5 :sda_out <= SLAVE_ADDR[5];
9 :sda_out <= SLAVE_ADDR[4];
13 :sda_out <= SLAVE_ADDR[3];
17 :sda_out <= SLAVE_ADDR[2];
21 :sda_out <= SLAVE_ADDR[1];
25 :sda_out <= SLAVE_ADDR[0];
29 :sda_out <= 0;
33 :sda_en <=1'b0;
35 :st_done <= 1'b1;
36 :begin
i2c_scl <= 1'b0;
if(sda_in == 1'b1) begin
i2c_ack <= 1'b1;
end
clk_bit <= 1'b0;
end
default : ;
endcase
end
SEND_ADDR16: begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :begin
sda_en <=1'b1;
sda_out <= byte_addr_r[15];
end
3,7,11,15,19,23,27,31 : i2c_scl <= 1'b0;
1,5,9,13,17,21,25,29,33 : i2c_scl <= 1'b1;
4 :sda_out <= byte_addr_r[14];
8 :sda_out <= byte_addr_r[13];
12 :sda_out <= byte_addr_r[12];
16 :sda_out <= byte_addr_r[11];
20 :sda_out <= byte_addr_r[10];
24 :sda_out <= byte_addr_r[9];
28 :sda_out <= byte_addr_r[8];
32 :sda_en <=1'b0;
34 :st_done <= 1'b1;
35 :begin
i2c_scl <= 1'b0;
if(sda_in == 1'b1) begin
i2c_ack <= 1'b1;
end
clk_bit <= 1'b0;
end
default : ;
endcase
end
SEND_ADDR8:begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :begin
sda_en <=1'b1;
sda_out <= byte_addr_r[7];
end
3,7,11,15,19,23,27,31 : i2c_scl <= 1'b0;
1,5,9,13,17,21,25,29,33 : i2c_scl <= 1'b1;
4 :sda_out <= byte_addr_r[6];
8 :sda_out <= byte_addr_r[5];
12 :sda_out <= byte_addr_r[4];
16 :sda_out <= byte_addr_r[3];
20 :sda_out <= byte_addr_r[2];
24 :sda_out <= byte_addr_r[1];
28 :sda_out <= byte_addr_r[0];
32 :sda_en <=1'b0;
34 :st_done <= 1'b1;
35 :begin
i2c_scl <= 1'b0;
if(sda_in == 1'b1) begin
i2c_ack <= 1'b1;
end
clk_bit <= 1'b0;
end
default : ;
endcase
end
SEND_DATA:begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :begin
sda_en <=1'b1;
sda_out <= i2c_data_w_r[7];
end
3,7,11,15,19,23,27,31 : i2c_scl <= 1'b0;
1,5,9,13,17,21,25,29,33 : i2c_scl <= 1'b1;
4 :sda_out <= i2c_data_w_r[6];
8 :sda_out <= i2c_data_w_r[5];
12 :sda_out <= i2c_data_w_r[4];
16 :sda_out <= i2c_data_w_r[3];
20 :sda_out <= i2c_data_w_r[2];
24 :sda_out <= i2c_data_w_r[1];
28 :sda_out <= i2c_data_w_r[0];
32 :sda_en <=1'b0;
34 :st_done <= 1'b1;
35 :begin
i2c_scl <= 1'b0;
if(sda_in == 1'b1) begin
i2c_ack <= 1'b1;
end
clk_bit <= 1'b0;
end
default : ;
endcase
end
STOP:begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :begin
sda_en <=1'b1;
sda_out <= 1'b0;
end
1 :i2c_scl <= 1'b1;
2 :sda_out <= 1'b1;
8 :st_done <= 1'b1;
9 :begin
clk_bit <= 1'b0;
i2c_done <= 1'b1;
end
default : ;
endcase
end
START_2: begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :sda_en <=1'b1;
1 :begin
sda_out <= 1'b1;
i2c_scl <= 1'b1;
st_done <= 1'b1;
end
2 :begin
sda_out <= 1'b0;
clk_bit <= 1'b0;
end
endcase
end
SEND_SLADDR_R:begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :begin
i2c_scl <= 1'b0;
sda_en <=1'b1;
end
4,8,12,16,20,24,28,32 :i2c_scl <= 1'b0;
2,6,10,14,18,22,26,30,34 :i2c_scl <= 1'b1;
1 :sda_out <= SLAVE_ADDR[6];
5 :sda_out <= SLAVE_ADDR[5];
9 :sda_out <= SLAVE_ADDR[4];
13 :sda_out <= SLAVE_ADDR[3];
17 :sda_out <= SLAVE_ADDR[2];
21 :sda_out <= SLAVE_ADDR[1];
25 :sda_out <= SLAVE_ADDR[0];
29 :sda_out <= 1;
33 :sda_en <=1'b0;
35 :st_done <= 1'b1;
36 :begin
i2c_scl <= 1'b0;
if(sda_in == 1'b1) begin
i2c_ack <= 1'b1;
end
clk_bit <= 1'b0;
end
default : ;
endcase
end
READ_DATA:begin
clk_bit <= clk_bit + 1'b1;
case(clk_bit)
0 :sda_en <=1'b0;
3,7,11,15,19,23,27,31 :i2c_scl <= 1'b0;
1,5,9,13,17,21,25,29,33 :i2c_scl <= 1'b1;
2 :i2c_read_data_r[7]<= sda_in;
6 :i2c_read_data_r[6]<= sda_in;
10 :i2c_read_data_r[5]<= sda_in;
14 :i2c_read_data_r[4]<= sda_in;
18 :i2c_read_data_r[3]<= sda_in;
22 :i2c_read_data_r[2]<= sda_in;
26 :i2c_read_data_r[1]<= sda_in;
30 :i2c_read_data_r[0]<= sda_in;
32 :sda_en <= 1'b1;
33 :sda_out <= 1'b1;
34 :st_done <= 1'b1;
35 :begin
i2c_scl <= 1'b0;
clk_bit <= 1'b0;
i2c_data_r <= i2c_read_data_r;
end
default : ;
endcase
end
endcase
end
end
仿真如下
EEPROM_AT24C64 为原子哥提供的仿真模型
module tb_i2c_ctrl();
reg sys_clk;
reg sys_rst_n;
reg i2c_wr_en ;
reg i2c_rd_en ;
reg i2c_start ;
reg [1:0] addr_num ;
reg [15:0] byte_addr ;
reg [7:0] i2c_data_w;
wire [7:0] i2c_data_r ;
wire i2c_done ;
wire i2c_clk ;
wire i2c_ack ;
wire i2c_scl ;
wire i2c_sda ;
pullup(i2c_sda);
//给输入信号初始值
initial
begin
sys_clk = 1'b0;
sys_rst_n = 1'b0; //复位
addr_num <= 2'd2;
i2c_wr_en <= 1'b1;
i2c_rd_en <= 1'b0;
byte_addr <= 16'hA5A5;
i2c_data_w <= 8'hAA;
#21;
sys_rst_n = 1'b1; //在第21ns的时候复位信号信号拉高
i2c_start <= 1'b1;
#1000;
i2c_start <= 1'b0;
#500000;
i2c_wr_en <= 1'b0;
i2c_rd_en <= 1'b1;
byte_addr <= 16'hA5A5;
#1000;
i2c_start <= 1'b1;
#1000;
i2c_start <= 1'b0;
#500000;
i2c_wr_en <= 1'b1;
i2c_rd_en <= 1'b0;
byte_addr <= 16'h5AA5;
i2c_data_w <= 8'h55;
i2c_start <= 1'b1;
#1000;
i2c_start <= 1'b0;
#500000;
i2c_wr_en <= 1'b0;
i2c_rd_en <= 1'b1;
byte_addr <= 16'h5AA5;
i2c_start <= 1'b1;
#1000;
i2c_start <= 1'b0;
#500000;
i2c_wr_en <= 1'b0;
i2c_rd_en <= 1'b1;
byte_addr <= 16'hA5A5;
i2c_start <= 1'b1;
#1000;
i2c_start <= 1'b0;
end
//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #10 sys_clk = ~sys_clk;
i2c_ctrl
#(
.SLAVE_ADDR(7'b1010000 ) , //EEPROM从机地址
.CLK_FREQ (26'd50_000_000 ) , //模块输入的时钟频率
.I2C_FREQ (18'd250_000 ) //IIC_SCL的时钟频率ss
)
i2c_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.i2c_wr_en (i2c_wr_en ),
.i2c_rd_en (i2c_rd_en ),
.i2c_start (i2c_start ),
.addr_num (addr_num ),
.byte_addr (byte_addr ),
.i2c_data_w (i2c_data_w),
.i2c_data_r (i2c_data_r),
.i2c_done (i2c_done ),
.i2c_clk (i2c_clk ),
.i2c_ack (i2c_ack ),
.i2c_scl (i2c_scl ),
.i2c_sda (i2c_sda )
);
EEPROM_AT24C64 u_EEPROM_AT24C64(
.scl (i2c_scl ),
.sda (i2c_sda )
);
endmodule
顶层EEPROM的读写就很轻松了,这里仅给出仿真结果(文末附全部文件)


读写的数据一致,仿真通过。
黑金的EEPROM竟然接在了PS段,上不了板了,呜呜呜
文件链接在这里,嘿咻
提取码:4ik6
IIC协议在FPGA中算是比较难实现的协议了.
孩子写了好久,呜呜呜。
文中I2C控制的代码参考了原子哥的程序(还得是原子哥)。
协议方面个人认为小梅哥的线下班讲的很棒。
在写代码之前一定要理清协议的原理,最好是画一下波形图,写代码的时候如果觉得还是有点乱可以边仿真变写代码,个人认为这是比较快的方法。>o<
此工程可以应用于实际项目,如果认为文章写的不错请点赞支持。
FPGA初学者,欢迎交流鸭>o<
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源
我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n
尝试通过SSL连接到ImgurAPI时出现错误。这是代码和错误:API_URI=URI.parse('https://api.imgur.com')API_PUBLIC_KEY='Client-ID--'ENDPOINTS={:image=>'/3/image',:gallery=>'/3/gallery'}#Public:Uploadanimage##args-Theimagepathfortheimagetoupload#defupload(image_path)http=Net::HTTP.new(API_URI.host)http.use_ssl=truehttp.verify
我想要像“嘿那里”这样的东西变成,例如,#316583。我希望将任意长度的字符串“归结”为十六进制颜色。我不知道从哪里开始。我在想,每个字符串的MD5散列都是不同的-但如何将该散列转换为十六进制颜色数字? 最佳答案 你可以只取几位前几位:require'digest/md5'color=Digest::MD5.hexdigest('Mytext')[0..5] 关于ruby-如何使用Ruby基于字母数字字符串生成颜色?,我们在StackOverflow上找到一个类似的问题:
下载微PE工具箱进入官网下载微PE工具箱-下载 安装好后,打开微PE工具箱客户端,选择安装PE到U盘 PE壁纸可选择自己喜欢的壁纸,勾选上包含DOS工具箱,个性化盘符图标 下载原版系统进入网站下载镜像NEXT,ITELLYOU如果没有账号,注册一下就好进入选择开始使用选择win10 这里我们选择消费者版,用迅雷把BT种子下载下来 下面的两个盘符,是PE工具箱安装进U盘后,分成的盘符,注意EFI的盘符,这里面不能删东西,也不能添东西,另一个盘符可以当做正常的U盘空间使用,我们现在需要把下载下来的景象文件复制到正常的U盘空间中去 这个时候我们的系统U盘就只做好了 安装系统我们将U盘插入电脑,开机,
文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3