IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,是一种同步、半双工的通信总线,用于连接微控制器及其外围设备。I2C总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。IIC数据传输速率有标准模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些变种实现了低速模式(10 kbps)和快速+模式(1 Mbps)。
IIC总线的特点:
简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控占用IIC总线。
下图是一个嵌入式系统中处理器仅通过2根线的IIC总线控制多个IIC外设的典型应用图:

下图是I2C多master多slave示意图:

若想实现多master多slave效果:
IIC总线接口是一个标准的双向传输接口,一次数据传输需要主机和从机按照IIC协议的标准进行。I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,并且在硬件上都需要接一个上拉电阻到VCC。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。
IIC的写过程(如图):

IIC的读过程(如图):

完整的读写过程如下图:

1) 总线空闲状态:
SDA和SCL同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2) 总线START:
SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
3) 总线STOP:
SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
4 )总线Restart:
SCL为高电平时,SDA由高电平向低电平跳变,本质上也是START信号,用在完整I2C读过程中的读阶段,在首次发送停止信号之前,master通过发送Restart信号,可以转换与当前slave的通信模式(从写模式到读模式),或是切换到与另一个slave通信。
5 )数据阶段:
在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定。只有在SCL为低电平期间,才允许SDA上的电平改变状态。简单的说就是,数据在SCL下降会被采样,所以SDA需要在SCL为高电平时保持稳定。
6) ACK与NACK信号:
IIC总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
这段话再说细一点:
写阶段,master写了一字节数据,在第9个时钟脉冲期间释放数据线,由slave反馈应答信号,ACK(低)表示数据成功接收,NACK(高)表示该字节没有接收成功;
读阶段,master向slave收数据,slave写了一字节数据,在第9个时钟脉冲期间释放数据线,由master反馈应答信号,ACK(低)表示数据成功接收,NACK(高)表示该字节没有接收成功。
还有一种特殊情况:当master决定不再接收数据时,应向slave发送NACK信号,高速slave不再发送。
以下情况会导致出现NACK位:

代码:
`timescale 1ns / 1ps
//
// Module Name: IIC_control
// Tool Versions: Vivado 2018.3
// Description:
//
//
module IIC_control #(
parameter DIV_CLK = 'd500, //系统时钟分频系数
parameter WMEN_LEN = 8'd0, //写数据帧数
parameter RMEN_LEN = 8'd0 //读数据帧数
)
(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位
input iic_en, //iic使能信号
input [WMEN_LEN*8-1'b1:0] w_data, //iic设备的写入数据,其中w_data[7:0]应该是地址帧
input [7:0] wd_cnt, //iic写入数据的帧数
output reg [RMEN_LEN*8-1'b1:0] r_data, //iic读取数据存储
input [7:0] rd_cnt, //iic读取数据的帧数
output reg iic_busy, //iic工作标识,1为忙,2为空闲
//标准的iic设备总线
inout iic_sda, //iic总线的双向数据线
output reg iic_scl //iic总线的时钟线
);
parameter IDLE = 4'd0, //空闲状态
START = 4'd1, //开始
W_WAIT = 4'd2, //写状态
W_ACK = 4'd3, //写响应
R_WAIT = 4'd4, //读状态
R_ACK = 4'd5, //读响应
STOP1 = 4'd6, //结束1
STOP2 = 4'd7; //结束2
reg [3:0] iic_s; // 状态机状态
reg scl_clk; //分频时钟,通过对其移位获得iic串行时钟信号
reg iic_mode; // 设置iic数据线状态,1为输出(写),0为输入(读)
reg [2:0] bcnt; //比特计数
reg [7:0] wcnt; //写字节计数
reg [7:0] rcnt; //读字节计数
reg scl_r; //iic时钟信号寄存,时钟信号的来源包括两部分,一是iic空闲时主动拉高,二是每比特数据传输时拉高
reg sda_r; //iic数据线寄存,W_ACK时进行响应判断
reg sda_o = 0; //iic数据线,写数据
reg [7:0] sda_o_r; //iic数据线,写数据寄存器
reg [7:0] sda_i_r; //iic数据线,读数据寄存
reg [$clog2(DIV_CLK):0] clk_cnt; //分频时钟计数
// reg iic_busy;
reg rd_en; //读数据使能信号
//分频器对系统时钟进行分频处理,产生串行时钟信号
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n) begin
clk_cnt <= 0 ;
scl_clk <= 0;
end
else if(clk_cnt == (DIV_CLK>>1) -1'b1)begin//保持50%占空比
clk_cnt <= 0;
scl_clk <= ~scl_clk;
end
else clk_cnt <= clk_cnt +'b1;
end
//
always @(*)
begin
if(iic_s == IDLE || iic_s == STOP1 || iic_s == STOP2) //主动拉升iic时钟线
scl_r <= 1'b1;
else scl_r <= ~scl_clk;
end
//使iic时钟线高电平保持在数据线传输每比特数据的中心
wire scl_offset = (clk_cnt == DIV_CLK>>2);
always @(posedge sys_clk) iic_scl <= scl_offset ? scl_r : iic_scl; //产生iic串行时钟
//iic状态机,首先发送地址帧
always @(negedge scl_clk or negedge sys_rst_n) //在scl_clk时钟下降沿改变状态,为了保证scl与sda的时序
begin
if(!sys_rst_n) begin
iic_s <= IDLE ; //状态机复位
iic_mode <= 1'b1; // 设置iic为输出
bcnt <= 3'd7; //iic每次先发送高位,因此初始化赋值为7,发送最高比特,然后递减
wcnt <= 0; //将已写入数据帧数标识置零
rcnt <= 0; //将已读出数据帧数标识置零
iic_busy <= 0; //iic空闲
rd_en <= 0; //
end
else
case(iic_s)
IDLE : begin // 空闲状态设置scl与sda均为1
if(iic_en || rd_en)begin
iic_s <= START; //接收到使能信号iic开始工作
iic_busy <= 1'b1; //iic忙
iic_mode <= 1'b1; // 设置iic为写入
end
else begin
iic_mode <= 1'b1; // 设置iic为输出
wcnt <= 0; //将已写入数据帧数标识置零
rcnt <= 0; //将已读出数据帧数标识置零
rd_en <= 1'b0;
iic_busy <= 1'b0;
end
end
START: begin
bcnt <= 3'd7; //数据从最高位开始传输
iic_s <= W_WAIT;
end
W_WAIT:begin
if(bcnt >0)
bcnt <= bcnt -1'b1; //计数减一,写入下一位数据
else begin
iic_s <= W_ACK;
wcnt <= wcnt +1'b1; //每帧数据写入完成后,帧计数器加一
iic_mode <= 1'b0 ; // 设置iic为读取,即从机向主机输入
end
end
W_ACK:begin
if(wcnt < wd_cnt) begin
iic_s <= W_WAIT;
// wcnt <= wcnt +1'b1;
bcnt <= 3'd7;
iic_mode <= 1'b1 ; // 下一个状态是W_WAIT,因此设置iic为写入,即主机向从机输入
end
else if(rd_cnt>0) begin //如果rd_cnt大于0表示iic是读模式
if(rd_en == 1'b0) begin //rd_en==0表明还未写入读控制字,跳转到IDLE状态进行读控制字写入
rd_en <= 1'b1;
iic_s <= IDLE;
iic_mode <= 1'b1 ;
end
else
iic_s <= R_WAIT ; //已写入读控制字,开始进行数据读取
bcnt <= 3'd7;
end
else
iic_s <= STOP1; //写入完成跳转到结束状态
if(sda_r !== 1'b0) //如果未接收到从机应答信号,停止发送
iic_s <= STOP1;
end
R_WAIT:begin //读取数据
rd_en <= 1'b0; //置零,及时释放
if(bcnt > 0) begin
bcnt <= bcnt -1'b1;
end
else begin
rcnt <= rcnt +1'b1;
iic_s <= R_ACK;
iic_mode <= 1'b1 ; // 设置iic为写入,输出应答信号
end
end
R_ACK:begin
if(rcnt < rd_cnt) begin
bcnt <= 3'd7;
iic_s <= R_WAIT;
iic_mode <= 1'b0 ; // 设置iic为读取
end
else
begin
iic_s <= STOP1;
end
end
STOP1:begin//sda = 0 scl = 1
iic_s <= STOP2;
end
STOP2:begin//sda = 1 scl =1
iic_s <= IDLE;
end
default:
iic_s <= IDLE;
endcase
end
//IIC总线的SDA数据线是一个双向IO口,使用三态门
assign iic_sda = iic_mode ? sda_o: 1'bz;
//sda输出
always @(*)
begin
if(!sys_rst_n || iic_s == STOP2)
begin //sda = 1
sda_o <= 1'b1;
end
else if(iic_s == START || iic_s == STOP1 || (iic_s == R_ACK && rcnt != rd_cnt))
begin //sda = 0
sda_o <= 0;
end
else if(iic_s == W_WAIT)
begin //输出最高位
sda_o <= sda_o_r[7];
end //其他状态数据线拉高
else sda_o <= 1'b1;
end
always @(negedge scl_clk)
begin
if(iic_s == W_ACK || iic_s == START)begin
sda_o_r <= rd_en?({w_data[7:1],1'b1}):(w_data[(wcnt*8)+:8]);
// sda_o_r <= w_data[(wcnt*8)+:8];
/*写、读模式下均存在wcnt=0的时候,此时w_data应该是地址帧,
即w_data[7:0]为地址帧,其中w_data[0]为模式,0代表写,1代表读*/
// if(rd_en) sda_o_r <= {w_data[7:1],1'b1};
end
else if(iic_s == W_WAIT) //移位寄存器
sda_o_r <= {sda_o_r[6:0],1'b1};
else
sda_o_r <= sda_o_r;
end
//暂存iic数据线的数据,用于检测从机应答
always @(posedge scl_clk) sda_r <= iic_sda;
//读模式,
always @(posedge scl_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
r_data <= 'b0;
else if(iic_s == R_ACK)
r_data[((rcnt-1)*8)+:8] <= sda_i_r;
else if(iic_s == R_WAIT || iic_s == W_ACK)
sda_i_r <= {sda_i_r[6:0],iic_sda};
else
sda_i_r <= 0;
end
endmodule
参考:
IIC总线解析(包含时钟拉伸,地址扩展,死锁,总线冲突总线仲裁的问题描述)
I2C详解
【接口时序】6、IIC总线的原理与Verilog实现
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定
我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项
我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)
虽然1.8.7的构建我似乎有一个向后移植的Shellwords::shellescape版本,但我知道该方法是1.9的一个特性,在1.8的早期版本中绝对不支持.有谁知道我在哪里可以找到(以Gem形式或仅作为片段)针对Ruby转义的Bourne-shell命令的强大独立实现? 最佳答案 您也可以从shellwords.rb中复制您想要的内容。在Ruby的颠覆存储库的主干中(即GPLv2'd):defshellescape(str)#Anemptyargumentwillbeskipped,soreturnemptyquotes.ret