该文章结合PCF8591 8-bit AD/DA 模数/数模转换器来详细介绍IIC通信协议,尽量做到条理清晰,通俗易懂。该文图片均从PCF8591手册中截取,一定程度上引导读者学习阅读data sheet。

1. PCF8591引脚

2. 功能介绍

2.1 地址位

在I2C总线系统中,每个PCF8591设备都通过发送一个有效地址来激活。地址由固定部分可编程部分组成。可编程部分必须根据地址引脚A0、A1和A2进行设置。在I2C总线协议中,地址必须始终作为起始条件后的第一个字节发送。地址字节的最后一位是读/写位,它设置了后续数据传输的方向



在市面上我们所购买到的PCF8591 模数/数模转换器已经被集成到了PCB板上,根据博主的调研,其PCB的原理图绘制如上图所示。

其中A0,A1,A2均接GND,所以在进行DA转换时,发送的写地址位应为8‘h90;在进行AD转换时,先发送的写地址位应为8’h90,再发送读地址位8‘h91

2.2 控制位

发送到PCF8591设备的第二个字节将被存储在其控制寄存器中,并用于控制设备功能。

控制寄存器的高四位用于启用模拟输出,并将模拟输入编程为单端或差分输入低四位选择由高四位定义的一个模拟输入通道

如果设置了自动递增标志,每次A/D转换后通道号将自动递增。

  • 如下图所示,假设我们要进行D/A转换,即将数字信号输入转换为模拟信号输出

    1. 我们要允许模拟输出,即把control byte第6位(从0开始,从右往左数)设为1
    2. 此时不需要模拟信号输入,所以将第5位和第4位均设为0
    3. 第1位和第0位为选择的模拟输出通道,在这里我们选择channel 0通道输出,则第1位和第0位为00
    4. 不需要自动递增,则将第2位设为0

    综上所述,我们需要发送的control byte为8‘b0100_0000,即8’h40

  • 假设我们要进行A/D转换,即将模拟信号输入转换为数字信号输出

    1. 我们要关闭模拟输出,即把control byte第6位(从0开始,从右往左数)设为0
    2. 此时需要模拟信号输入选择每个信号均为单通道输入,所以将第5位和第4位均设为0
    3. 第1位和第0位为选择的模拟输入通道,在这里我们选择channel 0通道输出,则第1位和第0位为00
    4. 不需要自动递增,则将第2位设为0

    综上所述,我们需要发送的control byte为8‘b0000_0000,即8’h00

2.3 D/A 转换

发送到PCF8591设备的第三个字节存储在DAC数据寄存器中,并使用芯片内的D/A转换器将其转换为相应的模拟电压

D/A转换序列的波形如下图所示。在PCF8591的D/A转换中,我们需要先发送写地址位8’h90,再发送控制位8‘h40,最后再发送想要转换的数字信号数据

2.4 A/D 转换

A/D转换器采用逐次逼近转换技术。在发送有效的读取模式地址到PCF8591设备后,始终会启动A/D转换周期。 A/D转换周期在应答时钟脉冲的下降沿触发,并在传输上一次转换的结果时执行详情参见下图所示

一旦触发了转换周期,所选通道的输入电压样本将存储在芯片上,并转换为相应的8位二进制代码。

转换结果存储在ADC数据寄存器中,并等待传输。如果设置了自动递增标志,则选择下一个通道。

A/D转换序列的波形如下图所示。

3. D/A转换及IIC通信协议波形详解

在有了上面的基本认识后,我们开始详细介绍IIC通信协议中SDA和SCL的波形。

开始D/A转换时,即IIC通信中主设备向从设备发送信号,我们需要模拟主设备的SDA和SCL信号

在该例子中主设备可以为包括FPGA在内的任意设备,而从设备为PCF8591,其他从设备也可以类比

3.1 空闲状态

如下图所示,在总线空闲时,数据线(SDA)时钟线(SCL)都保持高电平

3.2 开始状态(START condition)

如下图所示,当D/A转换开始时,时钟线(SCL)仍为高电平数据线(SDA)从高电平跳变到低电平,这被定义为起始条件

从设备在检测到起始条件后,会等待主设备发送地址和读/写位,以确定是否需要参与到通信过程中。

3.3 写状态

2.1部分及2.2部分所说,在PCF8591的D/A转换中,我们需要先发送写地址位8’h90,再发送控制位8‘h40,最后再发送想要转换的数字信号数据

归根结底,我们向从机发送地址位、控制位和数字信号数据其实都是在向从机写入数据,并且每个数据都是8-bit,所以在这三个阶段时钟线(SCL)数据线(SDA)所遵循的规则是一样的





现在详细解释一下,在一个发送8-bit数据时,SDA和SCL的信号变化(数据由高位到低位传输)。

在IIC通信协议中,发送数据过程中,SCL信号为低电平时SDA信号可以发生变化,而在SCL为高电平时,SDA信号有效,应保持不变,所以SDA信号1bit、1bit地传输数据时,SCL信号也对应地由低变高再由低边高。

!!!!!!!!!!!!!!!!!!!以下开始为重点!!!!!!!!!!!!!!!!!!!!

如上图所示,承接开始状态时SDA从高电平跳转为低电平,向从机发送数据,写状态开始。

  1. 在开始状态SDA变为低电平后,SCL需要至少经过\(t_{HD;\ STA}\) \(\mu s\),即4.7\(\mu s\)后才能下拉为低电平
  2. 并且SCL低电平的时间至少要持续\(t_{LOW}\) \(\mu s\),即4.7\(\mu s\)才能再度变为高电平,在这期间,SDA开始传输数据,即这段时间内SDA信号可以发生变化。
  3. 在\(t_{Low}\) \(\mu s\)过去后,SCL变为高电平,此时SDA信号有效(即SDA所表示的0、1数据被写入从机对应的寄存器中),SCL高电平的时间至少维持\(t_{HIGH}\) \(\mu s\),即4.0\(\mu s\)。在此期间SDA信号不能发生改变,否则会导致信号传输错误
  4. 在\(t_{HIGH}\) \(\mu s\)过去后,SCL又变为低电平,此时进入下 1 bit的传输,重复1~4步骤的内容,循环8次,直到传输完8-bit的数据为止(每循环一次代表传输1bit)
  5. 传输完8-bit的数据后从机会返回一个Acknowledge信号(ACK信号,即应答信号),此时主机应该释放SDA信号线特别注意,SDA信号线是inout类型,可以由外部传输数据进来,也可以由内部传输信号出去),以便从机控制SDA信号线传输ACK信号(应答信号),即该阶段的SDA信号线表示的是ACK信号。如果ACK信号为低电平时(因为SDA信号线在默认状态下会被拉为高电平,所以将下拉为低电平作为有效信号),说明传输成功,可以继续进行下一个8-bit数据的传输或是结束传输,转为终止状态(STOP condition);如果ACK信号为高电平时,说明传输失败,转为空闲状态。
  6. 接收ACK信号时,SCL信号和在发送1 bit的数据时一样,先经过\(t_{LOW}\) \(\mu s\)变为高电平,再经过\(t_{HIGH}\) \(\mu s\)后变为低电平,此时ACK信号接收完成。

3.4 终止状态(STOP)

在终止状态时SCL信号先变成高电平,在至少经过\(t_{SU;\ STO}\) \(\mu s\),即4.0\(\mu s\)后SDA才能变为高电平,至此D/A转换结束,IIC通信协议结束。

3.5 D/A转换代码(Verilog实现)

module DAC_I2C
(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效 output reg dac_done, //DAC采样完成标志
input [7:0] dac_data, //DAC采样数据 output scl_out, //I2C总线SCL
inout sda_out //I2C总线SDA
); parameter CNT_NUM = 15; localparam IDLE = 3'd0;
localparam MAIN = 3'd1;
localparam START = 3'd2;
localparam WRITE = 3'd3;
localparam STOP = 3'd4; //根据PCF8591的datasheet,I2C的频率最高为100KHz,
//我们准备使用4个节拍完成1bit数据的传输,所以需要400KHz的时钟触发完成该设计
//使用计数器分频产生400KHz时钟信号clk_400khz
//其中CNT_NUM控制分配器的分频,例如如果FPGA的时钟为50MHz,则CNT_NUM = 125,因为400K*125 = 50MHz
reg clk_400khz;
reg [9:0] cnt_400khz;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
cnt_400khz <= 10'd0;
clk_400khz <= 1'b0;
end else if(cnt_400khz >= CNT_NUM-1) begin
cnt_400khz <= 10'd0;
clk_400khz <= ~clk_400khz;
end else begin
cnt_400khz <= cnt_400khz + 1'b1;
end
end reg [7:0] adc_data_r;
reg scl_out_r;
reg sda_out_r;
reg [2:0] cnt;
reg [2:0] cnt_main;
reg [7:0] data_wr;
reg [2:0] cnt_start;
reg [2:0] cnt_write;
reg [2:0] cnt_stop;
reg [2:0] state; always@(posedge clk_400khz or negedge rst_n_in) begin
if(!rst_n_in) begin //如果按键复位,将相关数据初始化
scl_out_r <= 1'd1;
sda_out_r <= 1'd1;
cnt <= 1'b0;
cnt_main <= 1'b0;
cnt_start <= 1'b0;
cnt_write <= 3'd0;
cnt_stop <= 1'd0;
dac_done <= 1'b1;
state <= IDLE;
end else begin
case(state)
IDLE:begin //软件自复位,主要用于程序跑飞后的处理
scl_out_r <= 1'd1;
sda_out_r <= 1'd1;
cnt <= 1'b0;
cnt_main <= 1'b0;
cnt_start <= 1'b0;
cnt_write <= 3'd0;
cnt_stop <= 1'd0;
dac_done <= 1'b1;
state <= MAIN;
end
MAIN:begin
if(cnt_main >= 3'd3) cnt_main <= 3'd3; //对MAIN中的子状态执行控制cnt_main
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
3'd0: begin state <= START; end //I2C通信时序中的START
3'd1: begin data_wr <= 8'h90; state <= WRITE; end //A0,A1,A2都接了GND,写地址为8'h90
3'd2: begin data_wr <= 8'h40; state <= WRITE; end //control byte为8'h40,打开DAC功能
3'd3: begin data_wr <= dac_data; state <= WRITE; dac_done <= 1'b0; end //需要进行DAC转换的数据
3'd4: begin state <= STOP; end //I2C通信时序中的结束STOP
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
START:begin //I2C通信时序中的起始START
if(cnt_start >= 3'd5) cnt_start <= 1'b0; //对START中的子状态执行控制cnt_start
else cnt_start <= cnt_start + 1'b1;
case(cnt_start)
3'd0: begin sda_out_r <= 1'b1; scl_out_r <= 1'b1; end //将SCL和SDA拉高,保持4.7us以上
3'd1: begin sda_out_r <= 1'b1; scl_out_r <= 1'b1; end //clk_400khz每个周期2.5us,需要两个周期
3'd2: begin sda_out_r <= 1'b0; end //SDA拉低到SCL拉低,保持4.0us以上
3'd3: begin sda_out_r <= 1'b0; end //clk_400khz每个周期2.5us,需要两个周期
3'd4: begin scl_out_r <= 1'b0; end //SCL拉低,保持4.7us以上
3'd5: begin scl_out_r <= 1'b0; state <= MAIN; end //clk_400khz每个周期2.5us,需要两个周期,返回MAIN
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
WRITE:begin //I2C通信时序中的写操作WRITE和相应判断操作ACK
if(cnt <= 3'd6) begin //共需要发送8bit的数据,这里控制循环的次数
if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end
else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
end else begin
if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值
else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
end
case(cnt_write)
//按照I2C的时序传输数据
3'd0: begin scl_out_r <= 1'b0; sda_out_r <= data_wr[7-cnt]; end //SCL拉低,并控制SDA输出对应的位
3'd1: begin scl_out_r <= 1'b1; end //SCL拉高,保持4.0us以上
3'd2: begin scl_out_r <= 1'b1; end //clk_400khz每个周期2.5us,需要两个周期
3'd3: begin scl_out_r <= 1'b0; end //SCL拉低,准备发送下1bit的数据
//获取从设备的响应信号并判断
3'd4: begin sda_out_r <= 1'bz; dac_done <= 1'b1; end //释放SDA线,准备接收从设备的响应信号
3'd5: begin scl_out_r <= 1'b1; end //SCL拉高,保持4.0us以上
3'd6: begin if(sda_out) state <= IDLE; else state <= state; end //获取从设备的响应信号并判断
3'd7: begin scl_out_r <= 1'b0; state <= MAIN; end //SCL拉低,返回MAIN状态
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
STOP:begin //I2C通信时序中的结束STOP
if(cnt_stop >= 3'd5) cnt_stop <= 1'b0; //对STOP中的子状态执行控制cnt_stop
else cnt_stop <= cnt_stop + 1'b1;
case(cnt_stop)
3'd0: begin sda_out_r <= 1'b0; end //SDA拉低,准备STOP
3'd1: begin sda_out_r <= 1'b0; end //SDA拉低,准备STOP
3'd2: begin scl_out_r <= 1'b1; end //SCL提前SDA拉高4.0us
3'd3: begin scl_out_r <= 1'b1; end //SCL提前SDA拉高4.0us
3'd4: begin sda_out_r <= 1'b1; end //SDA拉高
3'd5: begin sda_out_r <= 1'b1; state <= MAIN; end //完成STOP操作,返回MAIN状态
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
default:;
endcase
end
end assign scl_out = scl_out_r; //对SCL端口赋值
assign sda_out = sda_out_r; //对SDA端口赋值 endmodule

4. A/D转换及IIC通信协议波形详解

4.1 A/D转换过程

A/D转换过程与D/A转换类似,只不过A/D转换比D/A多了一个读状态,用来读取由模拟信号转化为数字信号的数据

  1. 设置从机工作状态:A/D转换先从空闲状态开始,再进入开始状态,再进入写状态,如2.1部分及2.2部分所说,我们先要发送写地址位8’h90,再发送控制位8‘h00,此时从机(PCF8591)被设置成A/D转换模式,此时模拟输入从channel 0通道进入,再进入终止状态结束此次通信。
  2. 读取从机返回数据:IIC通信再进入开始状态,再进入写状态,在写状态中如2.1部分所说,将读地址位8’h91写入从机对应的寄存器中,此时SDA信号线的数据即从机信号返回的数字信号的数据,然后IIC通信再进入读状态

如果读者看到这里觉得很绕,没关系,文章最后的代码将状态转换写得十分清楚,下面我们先来讲解读状态。

4.2 读状态

读状态和写状态类似,一次也是读8-bit的数据,其中SCL信号的变化和写状态一致SDA信号则要在读状态开始时设为1’bz,即主机释放SDA信号线,使得从机能够控制SDA信号线返回数字信号的数据(返回数据也由高到低返回),并且在每读完一个8-bit的数据后,主机应向从机发送ACK信号,即把SDA信号线拉低,表示传输成功,在这期间需要把SCL信号拉高并且维持\(4.7 \mu s\)。

4.3 A/D转换代码(Verilog实现)

module ADC_I2C
(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
output scl_out, //I2C总线SCL
inout sda_out, //I2C总线SDA
output reg adc_done, //ADC采样完成标志
output reg [7:0] adc_data //ADC采样数据
); parameter CNT_NUM = 15; localparam IDLE = 3'd0;
localparam MAIN = 3'd1;
localparam START = 3'd2;
localparam WRITE = 3'd3;
localparam READ = 3'd4;
localparam STOP = 3'd5; //根据PCF8591的datasheet,I2C的频率最高为100KHz,
//我们准备使用4个节拍完成1bit数据的传输,所以需要400KHz的时钟触发完成该设计
//使用计数器分频产生400KHz时钟信号clk_400khz
//其中CNT_NUM控制分配器的分频,例如如果FPGA的时钟为50MHz,则CNT_NUM = 125,因为400K*125 = 50MHz
reg clk_400khz;
reg [9:0] cnt_400khz;
always@(posedge clk_in or negedge rst_n_in) begin
if(!rst_n_in) begin
cnt_400khz <= 10'd0;
clk_400khz <= 1'b0;
end else if(cnt_400khz >= CNT_NUM-1) begin
cnt_400khz <= 10'd0;
clk_400khz <= ~clk_400khz;
end else begin
cnt_400khz <= cnt_400khz + 1'b1;
end
end reg [7:0] adc_data_r;
reg scl_out_r;
reg sda_out_r;
reg [2:0] cnt;
reg [3:0] cnt_main;
reg [7:0] data_wr;
reg [2:0] cnt_start;
reg [2:0] cnt_write;
reg [4:0] cnt_read;
reg [2:0] cnt_stop;
reg [2:0] state; always@(posedge clk_400khz or negedge rst_n_in) begin
if(!rst_n_in) begin //如果按键复位,将相关数据初始化
scl_out_r <= 1'd1;
sda_out_r <= 1'd1;
cnt <= 1'b0;
cnt_main <= 4'd0;
cnt_start <= 3'd0;
cnt_write <= 3'd0;
cnt_read <= 5'd0;
cnt_stop <= 1'd0;
adc_done <= 1'b0;
adc_data <= 1'b0;
state <= IDLE;
end else begin
case(state)
IDLE:begin //软件自复位,主要用于程序跑飞后的处理
scl_out_r <= 1'd1;
sda_out_r <= 1'd1;
cnt <= 1'b0;
cnt_main <= 4'd0;
cnt_start <= 3'd0;
cnt_write <= 3'd0;
cnt_read <= 5'd0;
cnt_stop <= 1'd0;
adc_done <= 1'b0;
state <= MAIN;
end
MAIN:begin
if(cnt_main >= 4'd6) cnt_main <= 4'd6; //对MAIN中的子状态执行控制cnt_main
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
4'd0: begin state <= START; end //I2C通信时序中的START
4'd1: begin data_wr <= 8'h90; state <= WRITE; end //A0,A1,A2都接了GND,写地址为8'h90
4'd2: begin data_wr <= 8'h00; state <= WRITE; end //control byte为8'h00,采用4通道ADC中的通道0
4'd3: begin state <= STOP; end //I2C通信时序中的START
4'd4: begin state <= START; end //I2C通信时序中的STOP
4'd5: begin data_wr <= 8'h91; state <= WRITE; end //A0 A1 A2都接了GND,读地址为8'h91
4'd6: begin state <= READ; adc_done <= 1'b0; end //读取ADC的采样数据
4'd7: begin state <= STOP; adc_done <= 1'b1; end //I2C通信时序中的STOP,读取完成标志
4'd8: begin state <= MAIN; end //预留状态,不执行
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
START:begin //I2C通信时序中的起始START
if(cnt_start >= 3'd5) cnt_start <= 1'b0; //对START中的子状态执行控制cnt_start
else cnt_start <= cnt_start + 1'b1;
case(cnt_start)
3'd0: begin sda_out_r <= 1'b1; scl_out_r <= 1'b1; end //将SCL和SDA拉高,保持4.7us以上
3'd1: begin sda_out_r <= 1'b1; scl_out_r <= 1'b1; end //clk_400khz每个周期2.5us,需要两个周期
3'd2: begin sda_out_r <= 1'b0; end //SDA拉低到SCL拉低,保持4.0us以上
3'd3: begin sda_out_r <= 1'b0; end //clk_400khz每个周期2.5us,需要两个周期
3'd4: begin scl_out_r <= 1'b0; end //SCL拉低,保持4.7us以上
3'd5: begin scl_out_r <= 1'b0; state <= MAIN; end //clk_400khz每个周期2.5us,需要两个周期,返回MAIN
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
WRITE:begin //I2C通信时序中的写操作WRITE和相应判断操作ACK
if(cnt <= 3'd6) begin //共需要发送8bit的数据,这里控制循环的次数
if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end
else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
end else begin
if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值
else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
end
case(cnt_write)
//按照I2C的时序传输数据
3'd0: begin scl_out_r <= 1'b0; sda_out_r <= data_wr[7-cnt]; end //SCL拉低,并控制SDA输出对应的位
3'd1: begin scl_out_r <= 1'b1; end //SCL拉高,保持4.0us以上
3'd2: begin scl_out_r <= 1'b1; end //clk_400khz每个周期2.5us,需要两个周期
3'd3: begin scl_out_r <= 1'b0; end //SCL拉低,准备发送下1bit的数据
//获取从设备的响应信号并判断
3'd4: begin sda_out_r <= 1'bz; end //释放SDA线,准备接收从设备的响应信号
3'd5: begin scl_out_r <= 1'b1; end //SCL拉高,保持4.0us以上
3'd6: begin if(sda_out) state <= IDLE; else state <= state; end //获取从设备的响应信号并判断
3'd7: begin scl_out_r <= 1'b0; state <= MAIN; end //SCL拉低,返回MAIN状态
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
READ:begin //I2C通信时序中的读操作READ和返回ACK的操作
if(cnt <= 3'd6) begin //共需要接收8bit的数据,这里控制循环的次数
if(cnt_read >= 3'd3) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end
else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
end else begin
if(cnt_read >= 3'd7) begin cnt_read <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值
else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
end
case(cnt_read)
//按照I2C的时序接收数据
3'd0: begin scl_out_r <= 1'b0; sda_out_r <= 1'bz; end //SCL拉低,释放SDA线,准备接收从设备数据
3'd1: begin scl_out_r <= 1'b1; end //SCL拉高,保持4.0us以上
3'd2: begin adc_data_r[7-cnt] <= sda_out; end //读取从设备返回的数据
3'd3: begin scl_out_r <= 1'b0; end //SCL拉低,准备接收下1bit的数据
//向从设备发送响应信号
3'd4: begin sda_out_r <= 1'b0; adc_done <= 1'b1; adc_data <= adc_data_r; end //发送响应信号,将前面接收的数据锁存
3'd5: begin scl_out_r <= 1'b1; end //SCL拉高,保持4.0us以上
3'd6: begin scl_out_r <= 1'b1; adc_done <= 1'b0; end //SCL拉高,保持4.0us以上
3'd7: begin scl_out_r <= 1'b0; state <= MAIN; end //SCL拉低,返回MAIN状态
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
STOP:begin //I2C通信时序中的结束STOP
if(cnt_stop >= 3'd5) cnt_stop <= 1'b0; //对STOP中的子状态执行控制cnt_stop
else cnt_stop <= cnt_stop + 1'b1;
case(cnt_stop)
3'd0: begin sda_out_r <= 1'b0; end //SDA拉低,准备STOP
3'd1: begin sda_out_r <= 1'b0; end //SDA拉低,准备STOP
3'd2: begin scl_out_r <= 1'b1; end //SCL提前SDA拉高4.0us
3'd3: begin scl_out_r <= 1'b1; end //SCL提前SDA拉高4.0us
3'd4: begin sda_out_r <= 1'b1; end //SDA拉高
3'd5: begin sda_out_r <= 1'b1; state <= MAIN; end //完成STOP操作,返回MAIN状态
default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
endcase
end
default:;
endcase
end
end assign scl_out = scl_out_r; //对SCL端口赋值
assign sda_out = sda_out_r; //对SDA端口赋值 endmodule

5.总结

如果读者有何疑问欢迎在评论区下面评论,或者博主有哪些写错的地方也欢迎指正。

IIC通信协议详解 & PCF8591应用(Verilog实现)的更多相关文章

  1. IIC通信协议详解

    IIC通信详解 IIC概述 IIC:两线式串行总线,它是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据. 在CPU与被控IC之间.IC与IC之间进行双向传送,高速IIC总线一般可达400 ...

  2. STM32学习笔记:IIC通信协议详解(附带软件模拟源码)

    什么是IIC(I2C)? IIC 即Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司设计出来的一种简单.双向.二线制.同步串行总线.它是一种多向控制总 ...

  3. 第十六章 IIC协议详解+UART串口读写EEPROM

    十六.IIC协议详解+Uart串口读写EEPROM 本文由杭电网友曾凯峰根据小梅哥FPGA IIC协议基本概念公开课内容整理并最终编写Verilog代码实现使用串口读写EEPROM的功能. 以下为原文 ...

  4. IIC时序详解

    Verilog IIC通信实验笔记 Write by Gianttank 我实验的是 AT24C08的单字节读,单字节写,页读和页写,在高于3.3V系统中他的通信速率最高400KHZ的,我实验里用的是 ...

  5. iOS中 HTTP/Socket/TCP/IP通信协议详解

    // OSI(开放式系统互联), 由ISO(国际化标准组织)制定 // 1. 应用层 // 2. 表示层 // 3. 会话层 // 4. 传输层 // 5. 网络层 // 6. 数据链接层 // 7. ...

  6. http通信协议详解

    转载自:http://blog.csdn.net/gueter/article/details/1524447 引言 HTTP是一个属于应用层的面向对象的协议,由于其简捷.快速的方式,适用于分布式超媒 ...

  7. iOS中 HTTP/Socket/TCP/IP通信协议详解 韩俊强的博客

    每日更新关注:http://weibo.com/hanjunqiang  新浪微博 简单介绍: // OSI(开放式系统互联), 由ISO(国际化标准组织)制定 // 1. 应用层 // 2. 表示层 ...

  8. UART, SPI, IIC的详解及三者的区别和联系

    UART.SPI.IIC是经常用到的几个数据传输标准,下面分别总结一下: UART(Universal Asynchronous Receive Transmitter):也就是我们经常所说的串口,基 ...

  9. Modbus RTU通信协议详解以及与Modbus TCP通信协议之间的区别和联系

    Modbus通信协议由Modicon公司(现已经为施耐德公司并购,成为其旗下的子品牌)于1979年发明的,是全球最早用于工业现场的总线规约.由于其免费公开发行,使用该协议的厂家无需缴纳任何费用,Mod ...

  10. Modbus通信协议详解

    附:http://www.360doc.com/content/14/0214/13/15800361_352436989.shtml 一.Modbus 协议简介 Modbus 协议是应用于电子控制器 ...

随机推荐

  1. 关于Mongodb索引创建的一些体会

    mongodb索引分类以及创建我就不多说了,如果想了解可以直接在百度上搜索,这里我说一下关于索引创建的个人想法. 1.优先给一些Id类字段添加索引,查询时可以缩小扫描范围. 2.创建联合索引时,索引字 ...

  2. Win32 SDK(四)Edit控件用法

    Win32 SDK(四)Edit控件用法 1获得控件句柄 HWND hEdit2 = ::GetDlgItem(hWnd, IDC_EDIT2); WINUSERAPI HWND WINAPI Get ...

  3. 再探se

    对象 没有分配内存空间的对象是一个特殊的对象 null null是引用类型的,但是没有指向任何位置,所以是不能被访问的,强制访问会空指针异常 针对具体对象的属性称之为对象属性,成员属性,实例属性 针对 ...

  4. java_html笔记

    颜色 color 字体大小 1. 数值+单位 2. 关键字 - px - em 字体(可以写多个,但 不是全都生效 只生效存在的 如果全都不存在 则使用默认字体) font-family:" ...

  5. Oracle导出数据库与还原

    导出部分 1.获取到Oracle directory目录与实际电脑目录的映射 2.CMD导出Oracle数据库 DMP文件 //expdp 用户/密码@数据库监听地址 schemas=表空间名称 du ...

  6. Python爬虫常用库介绍(requests、BeautifulSoup、lxml、json)

    1.requests库 http协议中,最常用的就是GET方法: import requests response = requests.get('http://www.baidu.com') pri ...

  7. vmware虚拟机历史版本下载

    如果你要15.0的版本,点选Open Source吧,里面有多个版本,需要登陆账号,至于账号注册用临时邮箱 https://my.vmware.com/en/web/vmware/info/slug/ ...

  8. 外挂级OCR神器:免费文档解析、表格识别、手写识别、古籍识别、PDF转Word

    TextIn Tools是一款免费的在线OCR工具,支持快速准确的文字和表格识别,手写.古籍识别,提供PDF转Markdown大模型辅助工具,同时支持PDF.WORD.EXCEL.JPG.PPT等各类 ...

  9. IOI2000 邮局 加强版 题解

    [IOI2000] 邮局 加强版 题解 考虑动态规划,设 \(f_{i,j}\) 为经过了 \(i\) 个村庄,正在建第 \(j\)​ 个邮局的最优距离. 以及 \(w_{i,j}\) 表示区间 \( ...

  10. QT框架中的缓存:为什么有QHash和QMap,还设计了QCache和QContiguousCache?

    简介 本文介绍了QT框架中可用于缓存的几个数据类型各自的特点:通过本文读者可以了解到为什么有QHash和QMap,还设计了QCache和QContiguousCache? 目录 QHash和QMap有 ...