0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FPGA基础学习IIC协议驱动设计详解

电子工程师 来源:CSDN技术社区 作者: FPGA技术江湖 2021-05-02 09:15 次阅读

本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子信息通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。

本篇实现基于叁芯智能科技的SANXIN -B01 FPGA开发板,以下为配套的教程,如有入手开发板,可以登录官方淘宝店购买,还有配套的学习视频。

I²C 即 Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,I²C是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。这种方式简化了信号传输总线接口。

I²C 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线 SDA,另一根是时钟线 SCL,两条线可以挂多个设备。 I²C 设备(绝大多数)里有个固化的地址,只有在两条线上传输的值等于 I²C 设备的固化地址时,其才会作出响应。通常我们为了方便把 I²C 设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。

64639d50-a08a-11eb-8b86-12bb97331649.png

A段为空闲段,在不进行数据传输时,SCL 和 SDA 保持为高电平。

B段为起始段(START CONDITION),在进行任何数据传输前,都应该先有一个起始段。当 SCL 为高电平期间,SDA有一个从高到低的变化视为起始段。

C段为结束段(STOP CONDITION),在数据传输结束后,都应该有一个结束段。当SCL为高电平期间,SDA有一个从低到高的变化视为结束段。

D段分为两段数据有效段(ADDRESS OR ACKNOWLEDGE)和数据修改段(DATA ALLOWED TO CHANGE)。在传输数据时,在 SCL 为高电平期间,SDA 不能够发生改变,视为数据有效段;在传输数据时,在 SCL 为低电平期间,SDA 可以发生改变(修改为下一个要发送的bit),视为数据修改段。

IIC的传输总结为:空闲时,SCL 和 SDA 都是高电平;发送信息前,首先发送起始段(SCL为高时,SDA有一个从高到低的变化);在发送信息时,每一个 SCL 脉冲,发送或者接收1bit,该bit在SCL为低电平时发生改变,在SCL为高电平时,保持稳定;发送信息完成后,发送结束段(SCL 为高时,SDA 有一个从低到高的变化)。

SDA的数据线是双向传输的。在读取时,SDA 从从设备到主设备;在写入时,SDA从主设备到从设备。

在 IIC 传输时,为了保证通信的成功。发送方每发送8 bit 数据后,要求接收方回复一个应答位。

648b26f4-a08a-11eb-8b86-12bb97331649.png

应答位为低电平有效。

EEPROM(24LC64)简介

24LC64是容量为8K*8bit(64Kbit)通信接口为IIC的EEPROM。EEPROM拥有掉电不丢失的功能,经常用来保存一些配置信息等等。这个器件可以一次进行最多32字节的页写入和一次进行整片读出的功能。

64cce12a-a08a-11eb-8b86-12bb97331649.png

64db8568-a08a-11eb-8b86-12bb97331649.png

A0、A1、A2为用户可配置的芯片选择端。该管脚一般都连接电源或者地,在通信时,24LC64作为从机,主机在发送的命令中存在地址号,当地址号与A0、A1、A2管脚的电平相同时,该芯片视为被选中。

SDA和SCL为IIC协议中的时钟线和数据线。

WP为写保护端,当连接高电平时,该芯片只能够被读,不能够写入;当连接低电平或者空置时,该芯片可以被读写。

24LC64的写入方式有两种:单字节写入和多字节写入。

单字节写入时序图:

64fc6486-a08a-11eb-8b86-12bb97331649.png

在控制字节中,1010为EEPROM的标号;A2、A1、A0为主机发送的地址号,接收设备接收到此信息后,会与本身的A2、A1、A0相对比,相同时,视为被选中;最后1bit,0表示写入、1表示读出。

在高字节地址中,高三位地址无效。24LC64的地址范围为8K,地址位13位即可。13位地址分为高五低八,在IIC中规定每次发送8bit,所以高五的前面必须发送任意3bit。

多字节写入时序图:

65328e30-a08a-11eb-8b86-12bb97331649.png

DATA BYTE 0被写入到指定的地址中,后续的数据写入到指定地址的后续地址中。EEPROM中每32个字节分为一块,写入时不能跨块写入。

24LC64的读出方式有三种:当前地址单字节读、任意地址单字节读和任意地址多字节读。

当前地址单字节读的时序图如下:

655281b8-a08a-11eb-8b86-12bb97331649.png

当主机接收24LC64的数据后,不发送ACK,而是发送高电平。

任意地址单字节读取时序图,如下:

65701232-a08a-11eb-8b86-12bb97331649.png

首先写入指定地址,然后按照当前地址读即可。

任意地址多字节读,时序如下:

658d7d5e-a08a-11eb-8b86-12bb97331649.png

任意地址多字节读就是在任意地址单字节读的时序中,接收到从机发送的数据后,不要发送NO ACK而是发送ACK,此时从机就会继续发送后续地址的数据,直到读取的长度符合设计要求,然后发送NO ACK以及STOP结束传输。

下图为24LC64的时序参数图。

6595793c-a08a-11eb-8b86-12bb97331649.png

65b110e8-a08a-11eb-8b86-12bb97331649.png

65d84dde-a08a-11eb-8b86-12bb97331649.png

根据时序参数,决定将IIC的速率定为50KHz。发送时,数据改变在SCL的低电平的正中间;读取时,在SCL高电平的正中间进行读取。

IIC一般用在配置或者读取少量的信息,对于速率来说要求相对较低。

硬件介绍

在开发板上,搭载了一个24LC64。

电路原理图如下:

65effcf4-a08a-11eb-8b86-12bb97331649.png

A0、A1、A2都被接地,主机再发送控制字节时,需要将此三位发送低电平。

WP管脚被接地,24LC64的整个片子都可以被读写。

双向IO介绍

在IIC协议中,SDA作为数据线使用,每一个SCL的脉冲,传递一bit的数据。在读取时,SDA由24LC64发送,FPGA接收;在写入时,SDA由FPGA发送,24LC64接收。所以IIC协议只能实现半双工通信。

在硬件电路中,一般是通过三态门来进行控制双向IO。

661e8ef2-a08a-11eb-8b86-12bb97331649.png

此电路在FPGA的IO中也是存在的。下面来分析具体的工作原理

三态门有三个端子,分别是输入端(obuf),输出端(SDA)和控制端(out_en)。工作方式为:当out_en有效时,obuf的值就可以输出到SDA;当out_en无效时,obuf的值不能输出到SDA,那么三态门就会输出高阻态。

高阻态,字节理解为阻值特别大,也可以理解为断开。高阻态与任何电平值相连接,所连接的线上的电平值都是和对方保持一致。在此可以延伸一下:所有的端口都是输出,包括输入端口在内,也会对外呈现出一种状态,所以输入端口对外呈现的状态就是高阻态。对于双向IO来说,输出时,正常输出即可;输入时,就要呈现为高阻态。

设计要求

设计任意地址的单字节读写控制器。大多数是配置或者读取少量的信息,对于多字节的读写,可以采用多次单字节的读写完成。

设计分析

24LC64的寻址范围为8K,地址bit共计13bit,所以需要高5bit和低8bit。在有些兼容IIC协议接口的器件中,寻址范围较少,8bit足够表示,所以在设计时,设计出适合两种寻址方式的驱动。

SCL的实现采用计数器控制,根据规定好的频率,在合适的地方进行拉高或者拉低。由于SDA的变化都发生在SCL为高或者为低的中间,所以还需要产生对应的脉冲。

SDA根据协议和对应的脉冲进行发送对应的协议段。

架构设计和信号说明

本模块命名为iic_drive。

662c78dc-a08a-11eb-8b86-12bb97331649.png

66427646-a08a-11eb-8b86-12bb97331649.png

暂不连接的端口,在后续测试时,会有其他模块来控制或者读取。

iic_drive设计实现

iic_scl采用计数器的方式实现。外部的时钟为50MHz,iic_scl为100KHz,所以计数器需要记录500个时钟周期。

计数器由cnt_en控制。cnt_en有效时,cnt从0到500不断循环;当cnt_en无效时,cnt保持等于0。

iic_scl在cnt等于0至249时,保持高电平;在250至499时,保持低电平。cnt等于125时,正好是iic_scl高电平的中间,用脉冲flag_high表示;cnt等于375时,正好是iic_scl低电平的中间,用脉冲flag_low表示。

其他信号采用状态机作为输出。out_en为三态门控制信号,sda_obuf为三态门输出端,drive_cnt为发送或者接收的计数器,temp为发送或者接收的中间寄存器

状态转移图如下:

666996e0-a08a-11eb-8b86-12bb97331649.png

设计代码为:

module iic_drive (

input wire clk, input wire rst_n, input wire start_flag, input wire [6:0] slave_addr, input wire wren, input wire addr_sel, input wire [15:0] addr, input wire [7:0] wdata, output reg iic_scl, inout wire iic_sda, output reg iic_done, output reg [7:0] rdata); parameter FREQ_clk = 50_000_000; parameter FREQ_iic = 100_000; localparam T_SCL = FREQ_clk/FREQ_iic; localparam IDLE = 16‘b0000_0000_0000_0001; localparam START = 16’b0000_0000_0000_0010; localparam CTRL = 16‘b0000_0000_0000_0100; localparam CTRL_ACK = 16’b0000_0000_0000_1000; localparam HADDR = 16‘b0000_0000_0001_0000; localparam HADDR_ACK = 16’b0000_0000_0010_0000; localparam LADDR = 16‘b0000_0000_0100_0000; localparam LADDR_ACK = 16’b0000_0000_1000_0000; localparam WR = 16‘b0000_0001_0000_0000; localparam WR_ACK = 16’b0000_0010_0000_0000; localparam RD_START = 16‘b0000_0100_0000_0000; localparam RD_CTRL = 16’b0000_1000_0000_0000; localparam RD_CTRL_ACK = 16‘b0001_0000_0000_0000; localparam RD = 16’b0010_0000_0000_0000; localparam NO_ACK = 16‘b0100_0000_0000_0000; localparam STOP = 16’b1000_0000_0000_0000; reg [15:0] c_state; reg [15:0] n_state; reg [31:0] cnt; reg cnt_en; reg flag_high; reg flag_low; reg out_en; reg sda_obuf; reg [3:0] drive_cnt; reg [7:0] temp; assign iic_sda = (out_en == 1‘b1) ? sda_obuf : 1’bz; always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) cnt 《= 32’d0; else if (cnt 《 T_SCL - 1‘b1 && cnt_en == 1’b1) cnt 《= cnt + 1‘b1; else cnt 《= 32’d0; end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) iic_scl 《= 1’b1; else if (cnt 《 T_SCL/2) iic_scl 《= 1‘b1; else iic_scl 《= 1’b0; end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) flag_high 《= 1’b0; else if (cnt == T_SCL/4 - 1‘b1) flag_high 《= 1’b1; else flag_high 《= 1‘b0; end

always @ (posedge clk, negedge rst_n) begin if (rst_n == 1’b0) flag_low 《= 1‘b0; else if (cnt == (T_SCL * 3)/4 - 1’b1) flag_low 《= 1‘b1; else flag_low 《= 1’b0; end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) c_state 《= IDLE; else c_state 《= n_state; end always @ * begin case (c_state) IDLE : begin if (start_flag == 1’b1) n_state = START; else n_state = IDLE; end START : begin if (flag_high == 1‘b1) n_state = CTRL; else n_state = START; end CTRL : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) n_state = CTRL_ACK; else n_state = CTRL; end CTRL_ACK : begin if (flag_high == 1’b1) if (iic_sda == 1‘b0) if (addr_sel == 1’b1) n_state = HADDR; else n_state = LADDR; else n_state = START; else n_state = CTRL_ACK; end HADDR : begin if (flag_low == 1‘b1 && drive_cnt == 4’d8) n_state = HADDR_ACK; else n_state = HADDR; end HADDR_ACK : begin if (flag_high == 1‘b1) if (iic_sda == 1’b0) n_state = LADDR; else n_state = START; else n_state = HADDR_ACK; end LADDR : begin if (flag_low == 1‘b1 && drive_cnt == 4’d8) n_state = LADDR_ACK; else n_state = LADDR; end LADDR_ACK : begin if (flag_high == 1‘b1) if (iic_sda == 1’b0) if (wren == 1‘b1) n_state = WR; else n_state = RD_START; else n_state = START; else n_state = LADDR_ACK; end WR : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) n_state = WR_ACK; else n_state = WR; end WR_ACK : begin if (flag_high == 1’b1) if (iic_sda == 1‘b0) n_state = STOP; else n_state = START; else n_state = WR_ACK; end STOP : begin if (flag_high == 1’b1) n_state = IDLE; else n_state = STOP; end RD_START : begin if (flag_high == 1‘b1) n_state = RD_CTRL; else n_state = RD_START; end RD_CTRL : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) n_state = RD_CTRL_ACK; else n_state = RD_CTRL; end RD_CTRL_ACK : begin if (flag_high == 1’b1) if (iic_sda == 1‘b0) n_state = RD; else n_state = START; else n_state = RD_CTRL_ACK; end RD : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) n_state = NO_ACK; else n_state = RD; end NO_ACK : begin if (flag_high == 1’b1) n_state = STOP; else n_state = NO_ACK; end default : n_state = IDLE; endcase end

always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) cnt_en 《= 1’b0; else case (c_state) IDLE : cnt_en 《= 1‘b0; CTRL_ACK, HADDR_ACK, LADDR_ACK, WR_ACK, RD_CTRL_ACK : begin if (flag_high == 1’b1) if (iic_sda == 1‘b0) cnt_en 《= 1’b1; else cnt_en 《= 1‘b0; else cnt_en 《= cnt_en; end default : cnt_en 《= 1’b1; endcase end

always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) out_en 《= 1’b0; else case (c_state) IDLE : out_en 《= 1‘b0; START : out_en 《= 1’b1; CTRL : begin if (flag_low == 1‘b1 && drive_cnt == 4’d8) out_en 《= 1‘b0; else out_en 《= 1’b1; end CTRL_ACK : out_en 《= 1‘b0; HADDR : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) out_en 《= 1’b0; else if (flag_low == 1‘b1) out_en 《= 1’b1; else out_en 《= out_en; end HADDR_ACK : out_en 《= 1‘b0; LADDR : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) out_en 《= 1’b0; else if (flag_low == 1‘b1) out_en 《= 1’b1; else out_en 《= out_en; end LADDR_ACK : out_en 《= 1‘b0; WR : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) out_en 《= 1’b0; else if (flag_low == 1‘b1) out_en 《= 1’b1; else out_en 《= out_en; end WR_ACK : out_en 《= 1‘b0; STOP : begin if (flag_low == 1’b1) out_en 《= 1‘b1; else out_en 《= out_en; end RD_START : begin if (flag_low == 1’b1) out_en 《= 1‘b1; else out_en 《= out_en; end RD_CTRL : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) out_en 《= 1’b0; else out_en 《= 1‘b1; end RD_CTRL_ACK : out_en 《= 1’b0; RD : begin if (flag_low == 1‘b1 && drive_cnt == 4’d8) out_en 《= 1‘b1; else out_en 《= 1’b0; end NO_ACK : out_en 《= 1‘b1; default : out_en 《= 1’b0; endcase end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) sda_obuf 《= 1’b1; else case (c_state) IDLE : sda_obuf 《= 1‘b1; START : begin if (flag_high == 1’b1) sda_obuf 《= 1‘b0; else sda_obuf 《= 1’b1; end CTRL : begin if (flag_low == 1‘b1 && drive_cnt 《 4’d8) sda_obuf 《= temp[7]; else sda_obuf 《= sda_obuf; end CTRL_ACK : sda_obuf 《= 1‘b0; HADDR : begin if (flag_low == 1’b1 && drive_cnt 《 4‘d8) sda_obuf 《= temp[7]; else sda_obuf 《= sda_obuf; end HADDR_ACK : sda_obuf 《= 1’b0; LADDR : begin if (flag_low == 1‘b1 && drive_cnt 《 4’d8) sda_obuf 《= temp[7]; else sda_obuf 《= sda_obuf; end LADDR_ACK : sda_obuf 《= 1‘b0; WR : begin if (flag_low == 1’b1 && drive_cnt 《 4‘d8) sda_obuf 《= temp[7]; else sda_obuf 《= sda_obuf; end WR_ACK : sda_obuf 《= 1’b0; STOP : begin if (flag_low == 1‘b1) sda_obuf 《= 1’b0; else if (flag_high == 1‘b1) sda_obuf 《= 1’b1; else sda_obuf 《= sda_obuf; end RD_START : begin if (flag_low == 1‘b1) sda_obuf 《= 1’b1; else if (flag_high == 1‘b1) sda_obuf 《= 1’b0; else sda_obuf 《= sda_obuf; end RD_CTRL : begin if (flag_low == 1‘b1 && drive_cnt 《 4’d8) sda_obuf 《= temp[7]; else sda_obuf 《= sda_obuf; end RD_CTRL_ACK : sda_obuf 《= 1‘b0; RD : begin if (flag_low == 1’b1 && drive_cnt == 4‘d8) sda_obuf 《= 1’b1; else sda_obuf 《= sda_obuf; end NO_ACK : sda_obuf 《= sda_obuf; default : sda_obuf 《= 1‘b1; endcase end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1’b0) drive_cnt 《= 4‘d0; else case (c_state) IDLE : drive_cnt 《= 4’d0; START : drive_cnt 《= 4‘d0; CTRL : begin if (flag_low == 1’b1) if (drive_cnt 《 4‘d8) drive_cnt 《= drive_cnt + 1’b1; else drive_cnt 《= 4‘d0; else drive_cnt 《= drive_cnt; end CTRL_ACK : drive_cnt 《= 4’d0; HADDR : begin if (flag_low == 1‘b1) if (drive_cnt 《 4’d8) drive_cnt 《= drive_cnt + 1‘b1; else drive_cnt 《= 4’d0; else drive_cnt 《= drive_cnt; end HADDR_ACK : drive_cnt 《= 4‘d0; LADDR : begin if (flag_low == 1’b1) if (drive_cnt 《 4‘d8) drive_cnt 《= drive_cnt + 1’b1; else drive_cnt 《= 4‘d0; else drive_cnt 《= drive_cnt; end LADDR_ACK : drive_cnt 《= 4’d0; WR : begin if (flag_low == 1‘b1) if (drive_cnt 《 4’d8) drive_cnt 《= drive_cnt + 1‘b1; else drive_cnt 《= 4’d0; else drive_cnt 《= drive_cnt; end WR_ACK : drive_cnt 《= 4‘d0; STOP : drive_cnt 《= 4’d0; RD_START : drive_cnt 《= 4‘d0; RD_CTRL : begin if (flag_low == 1’b1) if (drive_cnt 《 4‘d8) drive_cnt 《= drive_cnt + 1’b1; else drive_cnt 《= 4‘d0; else drive_cnt 《= drive_cnt; end RD_CTRL_ACK : drive_cnt 《= 4’d0; RD : begin if (flag_high == 1‘b1 && drive_cnt 《 4’d8) drive_cnt 《= drive_cnt + 1‘b1; else if (flag_low == 1’b1 && drive_cnt == 4‘d8) drive_cnt 《= 4’d0; else drive_cnt 《= drive_cnt; end NO_ACK : drive_cnt 《= 4‘d0; default : drive_cnt 《= 4’d0; endcase end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) temp 《= 8’d0; else case (c_state) IDLE : temp 《= 8‘d0; START : begin if (flag_high == 1’b1) temp 《= {slave_addr, 1‘b0}; else temp 《= temp; end CTRL : begin if (flag_low == 1’b1 && drive_cnt 《 4‘d8) temp 《= temp 《《 1’b1; else temp 《= temp; end CTRL_ACK : begin if (flag_high == 1‘b1 && iic_sda == 1’b0) if (addr_sel == 1‘b1) temp 《= addr[15:8]; else temp 《= addr[7:0]; else temp 《= temp; end HADDR : begin if (flag_low == 1’b1 && drive_cnt 《 4‘d8) temp 《= temp 《《 1’b1; else temp 《= temp; end HADDR_ACK : begin if (flag_high == 1‘b1 && iic_sda == 1’b0) temp 《= addr[7:0]; else temp 《= temp; end LADDR : begin if (flag_low == 1‘b1 && drive_cnt 《 4’d8) temp 《= temp 《《 1‘b1; else temp 《= temp; end LADDR_ACK : begin if (flag_high == 1’b1 && iic_sda == 1‘b0) if (wren == 1’b1) temp 《= wdata; else temp 《= {slave_addr, 1‘b1}; else temp 《= temp; end WR : begin if (flag_low == 1’b1 && drive_cnt 《 4‘d8) temp 《= temp 《《 1’b1; else temp 《= temp; end WR_ACK : temp 《= 8‘d0; STOP : temp 《= 8’d0; RD_START : temp 《= temp; RD_CTRL : begin if (flag_low == 1‘b1 && drive_cnt 《 4’d8) temp 《= temp 《《 1‘b1; else temp 《= temp; end RD_CTRL_ACK : temp 《= 8’d0; RD : begin if (flag_high == 1‘b1 && drive_cnt 《 4’d8) temp 《= {temp[6:0], iic_sda}; else temp 《= temp; end NO_ACK : temp 《= 8‘d0; default : temp 《= 8’d0; endcase end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) iic_done 《= 1’b0; else if (c_state == STOP && flag_high == 1‘b1) iic_done 《= 1’b1; else iic_done 《= 1‘b0; end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1’b0) rdata 《= 8‘d0; else if (c_state == RD && flag_low == 1’b1 && drive_cnt == 4‘d8) rdata 《= temp; else rdata 《= rdata; end endmodule

RTL仿真设计

在仿真时,需要外部提供ACK。如果直接仿真的话,由于外部没有提供ACK,导致一直都会重复发送一小段。在仿真中,我们自己给出ACK的难度也比较大。

24LC64的厂商帮我们解决了这个问题,他们在造出这个芯片的同时,还设计出了可以帮助我们仿真调试的verliog模型。此模型放在msim-》24LC64_sim_module中,文件名字为24LC64.v。此模型的sda也是双向IO,所以在硬件设计时,会在此IO上加上拉电阻,在仿真时,我们也要模拟上拉电阻。

仿真设计如下:

`timescale 1ns/1ps

module iic_drive_tb;

reg clk; reg rst_n; reg start_flag; reg [6:0] slave_addr; reg wren; reg addr_sel; reg [15:0] addr; reg [7:0] wdata; wire iic_scl; wire iic_sda; wire iic_done; wire [7:0] rdata; pullup(iic_sda); iic_drive iic_drive_inst(

.clk (clk), .rst_n (rst_n), .start_flag (start_flag), .slave_addr (slave_addr), .wren (wren), .addr_sel (addr_sel), .addr (addr), .wdata (wdata), .iic_scl (iic_scl), .iic_sda (iic_sda), .iic_done (iic_done), .rdata (rdata) ); M24LC64 M24LC64_inst( .A0 (1’b0), .A1 (1‘b0), .A2 (1’b0), .WP (1‘b0), .SDA (iic_sda), .SCL (iic_scl) );

initial clk = 1’b0; always # 10 clk = ~clk; initial begin rst_n = 1‘b0; start_flag = 1’b0; slave_addr = 7‘b1010_000; wren = 1’b0; addr_sel = 1‘b1; addr = 16’h5555; wdata = 8‘haa; # 201 rst_n = 1’b1; # 200; @ (posedge clk); # 2; start_flag = 1‘b1; wren = 1’b1; @ (posedge clk); # 2; start_flag = 1‘b0; @ (posedge iic_done); # 200; @ (posedge clk); # 2; start_flag = 1’b1; wren = 1‘b0; @ (posedge clk); # 2; start_flag = 1’b0; @ (posedge iic_done); # 200 $stop; end

endmodule

pullup(iic_sda):将iic_sda上加上拉电阻。

仿真时,在地址16’h5555(虽然高三位没有用),写入了8’ha;写入完成后,又将此数据读出。

当对24LC64进行写入之后,它会进入到一个内部写循环(和flash类似),在此期间,不接受任何指令。所以在仿真时,需要等待写循环完成,时间为5ms(此参数在仿真模型中,可以进行修改)。

写入的仿真图如下:

66a1d5c8-a08a-11eb-8b86-12bb97331649.png

读出的仿真图如下:

66aed57a-a08a-11eb-8b86-12bb97331649.png

板级测试

上述的设计在使用时会有上游控制器进行控制,所以板级测试还需要设计控制器。在控制器中只需要某一个地址写入数据,然后读出即可。测试时,可以多次更换地址和数据,保证设计的正确性。

在测试时,将slave_addr、addr_sel、addr、wdata设置为定值。slave_addr为7’b1010_000,addr_sel为1’b1,addr为16’h5555,wdata为8’haa。

将上有控制器命名为test_ctrl,顶层命名为test。

66d59c46-a08a-11eb-8b86-12bb97331649.png

test_ctrl模块采用状态机设计实现。

66f91612-a08a-11eb-8b86-12bb97331649.png

test_ctrl的设计代码如下:

module test_ctrl (

input wire clk, input wire rst_n, output reg start_flag, output reg wren, input wire iic_done, input wire [7:0] rdata);

localparam WR = 4‘b0001; localparam WR_WAIT = 4’b0010; localparam RD_WAIT = 4‘b0100; localparam TESTDONE = 4’b1000; reg [3:0] c_state; reg [3:0] n_state; always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) c_state 《= WR; else c_state 《= n_state; end always @ * begin case (c_state) WR : n_state = WR_WAIT; WR_WAIT : begin if (iic_done == 1’b1) n_state = RD_WAIT; else n_state = WR_WAIT; end RD_WAIT : begin if (iic_done == 1‘b1 && rdata == 8’haa) n_state = TESTDONE; else n_state = RD_WAIT; end TESTDONE : n_state = TESTDONE; default : n_state = WR; endcase end

always @ (posedge clk, negedge rst_n) begin if (rst_n == 1‘b0) start_flag 《= 1’b0; else if ((c_state == WR) || (c_state == WR_WAIT && iic_done == 1‘b1)) start_flag 《= 1’b1; else start_flag 《= 1‘b0; end always @ (posedge clk, negedge rst_n) begin if (rst_n == 1’b0) wren 《= 1‘b0; else if (c_state == WR || c_state == WR_WAIT) wren 《= 1’b1; else wren 《= 1‘b0; end endmodule

test模块负责将test_ctrl和iic_drive模块进行例化连接。将test模块设置为顶层。

test模块设计代码如下:

module test (

input wire clk, input wire rst_n, output wire iic_scl, inout wire iic_sda);

wire start_flag; wire wren; wire iic_done; wire [7:0] rdata; test_ctrl test_ctrl_inst(

.clk (clk), .rst_n (rst_n), .start_flag (start_flag), .wren (wren), .iic_done (iic_done), .rdata (rdata) ); iic_drive iic_drive_inst(

.clk (clk), .rst_n (rst_n), .start_flag (start_flag), .slave_addr (7’b1010_000), .wren (wren), .addr_sel (1‘b1), .addr (16’h5555), .wdata (8‘haa), .iic_scl (iic_scl), .iic_sda (iic_sda), .iic_done (iic_done), .rdata (rdata) ); endmodule

板级测试采用逻辑分析仪测试。

采样时钟选择外部的50MHz,采样深度选择32K。

670614de-a08a-11eb-8b86-12bb97331649.png

触发位置选择post。

672a69e2-a08a-11eb-8b86-12bb97331649.png

将iic_sda、iic_scl、test_ctrl模块中的RD_WAIT和TESTDONE两个状态,共计四个信号进行观测。

676d976c-a08a-11eb-8b86-12bb97331649.png

将RD_WAIT设置为上升沿触发。进入此状态时,刚刚写入完成。

679182f8-a08a-11eb-8b86-12bb97331649.png

点击触发后,按一下复位键。

67b19782-a08a-11eb-8b86-12bb97331649.png

从波形图中,可以分析出,在地址16’h5555中写入了8’haa。

将RD_WAIT设置为donot care,将TESTDONE设置为上升沿,此时读出刚刚完成。

67d30688-a08a-11eb-8b86-12bb97331649.png

触发后,按一下复位按键。

67f5efc2-a08a-11eb-8b86-12bb97331649.png

可以看出在地址16’h5555的位置,读出aa。

注意发送时,是在SCL的低电平的正中间;接收是在SCL的高电平的正中间;观看波形时,要和标准的时序图进行对比。

证明设计正确。
编辑:lyn

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FPGA
    +关注

    关注

    1625

    文章

    21665

    浏览量

    601775
  • EEPROM
    +关注

    关注

    9

    文章

    1010

    浏览量

    81400
  • IIC
    IIC
    +关注

    关注

    11

    文章

    300

    浏览量

    38273

原文标题:FPGA零基础学习精选 | IIC协议驱动设计

文章出处:【微信号:HXSLH1010101010,微信公众号:FPGA技术江湖】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    FPGA加速深度学习模型的案例

    FPGA(现场可编程门阵列)加速深度学习模型是当前硬件加速领域的一个热门研究方向。以下是一些FPGA加速深度学习模型的案例: 一、基于FPGA
    的头像 发表于 10-25 09:22 140次阅读

    IIC信号线需要增加上拉电阻,是因为IIC的IO是什么

    IIC通信中扮演着至关重要的角色,而上拉电阻的加入则是实现这种通信方式有效性和可靠性的关键。以下是对此现象的介绍: 一、IIC通信与开漏输出 IIC通信协议是一种广泛应用的串行通信
    的头像 发表于 10-06 15:50 456次阅读

    LwIP协议栈源码详解—TCP/IP协议的实现

    电子发烧友网站提供《LwIP协议栈源码详解—TCP/IP协议的实现.pdf》资料免费下载
    发表于 07-03 11:22 3次下载

    【GD32F303红枫派开发板使用手册】第二十二讲 IIC-IIC OLED显示实验

    通过本实验主要学习以下内容: •OLED驱动原理 •IIC驱动OLED显示操作 22.2 实验原理 OLED模块的驱动芯片为SSD1306,
    的头像 发表于 06-22 09:59 578次阅读
    【GD32F303红枫派开发板使用手册】第二十二讲  <b class='flag-5'>IIC-IIC</b> OLED显示实验

    FPGA学习笔记-关于FPGA资源

    FPGA学习。 在学习中才发现,FPGA远不是门电路那么简单。FPGA中有各种需要的资源,比如门电路、存储单元、片内RAM、嵌入式乘法器、
    发表于 05-22 18:27

    面试遇到再不怕了:IIC协议

    点击上方蓝字关注我们I2C(Inter-IntegratedCircuit),有时也称为IIC或TWI(Two-WireInterface),是一种用于低速度串行通信的总线协议,主要用于连接微控制器
    的头像 发表于 04-29 08:10 7757次阅读
    面试遇到再不怕了:<b class='flag-5'>IIC</b><b class='flag-5'>协议</b>

    FPGA学习笔记-入门

    大概10年前,大学同学建议我学习DSP。当因为工作忙,也只是简单学习了DSP的一些基础知识,没有进一步深入学习和时间。结果现在,好像DSP已经不再是主流了,现在有了FPGA。 现在想想
    发表于 04-09 10:55

    FPGA初步学习印象

    了。FPGA出现后,也想学习学习这方面的知识。抱着先简单学习下的态度,在网上找资料看看。感觉FPGA,就像是利用芯片内部资源,自由组合门电路
    发表于 04-09 09:46

    fpga学习需要具备哪些课程

    FPGA(Field Programmable Gate Array)学习需要具备一系列的课程知识和实践技能
    的头像 发表于 03-14 15:51 1118次阅读

    FPGA实现IIC协议的设计

    今天给大家带来的是IIC通信,IIC协议应用非常广泛,例如与MPU6050进行通信,配置OV5640摄像头、驱动OLED屏幕等等,都需要使用到IIC
    的头像 发表于 03-04 10:49 1179次阅读
    <b class='flag-5'>FPGA</b>实现<b class='flag-5'>IIC</b><b class='flag-5'>协议</b>的设计

    【有那么点详细的CW32学习笔记】IIC接口-主机发送

    OLED屏幕,如果你对如何编写各种模块的驱动代码束手无策,那本系列教程的IIC章节或许能让你受益匪浅。 Inter-Integrated Circuit Bus,集成电路总线,简称IIC总线。这是一种
    的头像 发表于 01-11 09:53 571次阅读
    【有那么点详细的CW32<b class='flag-5'>学习</b>笔记】<b class='flag-5'>IIC</b>接口-主机发送

    【有那么点详细的CW32学习笔记】IIC接口-OLED驱动

    上一章我们讲解了IIC的通信流程以及通信代码,这一章就以市面上常见的IIC接口模块——OLED屏为例教学一下IIC接口的驱动怎么写。 第一步当然是搞清楚自己使用的OLED屏幕用的是什么
    的头像 发表于 01-11 09:49 691次阅读
    【有那么点详细的CW32<b class='flag-5'>学习</b>笔记】<b class='flag-5'>IIC</b>接口-OLED<b class='flag-5'>驱动</b>

    【CW32学习笔记】IIC接口-主机发送

    接着就是比较重要的部分了,IIC接口的收发并不是全自动的,因为一个完整的通信不仅包括发数据(地址、数据什么的),还包含收数据(啥也不干也得接收ACK信号),所以IIC通信的每个部分基本上都是收发易位的过程,IIC外设并不会自动完
    的头像 发表于 01-10 15:59 605次阅读
    【CW32<b class='flag-5'>学习</b>笔记】<b class='flag-5'>IIC</b>接口-主机发送

    IIC总线的FPGA实现说明

    DE2_TV中,有关于寄存器的配置的部分,采用的方法是通过IIC的功能,这里对IIC总线的FPGA实现做个说明。
    的头像 发表于 01-05 10:16 1008次阅读
    <b class='flag-5'>IIC</b>总线的<b class='flag-5'>FPGA</b>实现说明

    学习FPGA必须知道的社区

    学习FPGA必须知道的社区
    的头像 发表于 01-03 17:51 353次阅读
    <b class='flag-5'>学习</b><b class='flag-5'>FPGA</b>必须知道的社区