Verilog 实现 i2c 协议

在时钟(SCL)为高电平的时候,数据总线(SDA)必须保持稳定,所以数据总线(SDA)在时钟(SCL)为低电平的时候才能改变。

在时钟(SCL)为高电平的时候,数据总线(SDA)由高到低的跳变为总线起始信号,在时钟(SCL)为高电平的时候,数据总线(SDA)由低到高的跳变为总线停止信号。

应答,当 IIC 主机(不一定是发送端还是接受端)将 8 位数据或命令传出后,会将数据总线(SDA)释放,即设置为输入,然后等待从机应答(低电平 0 表示应答,1 表示非应答),此时的时钟仍然是主机提供的。

数据帧格式,I2C 器件通讯的时候首先是要发送“起始信号”,紧跟着就是七位器件地址,第八位是数据传送方向位(0:代表写,1:代表读),再后面就是等待从机的应答。当然传送结束后,“终止信号”也是由主机来产生的。发送数据的时候是高位先发送。


module iic #(parameter DIV_CLK = 100,parameter WR_MAX  = 8'd1,parameter RD_MAX  = 8'd1
) (input clk,input rst_n,input enable,output reg busy,input [7:0] rdlen,input [7:0] wrlen,output reg [RD_MAX*8-1:0] rddata,input [WR_MAX*8-1:0] wrdata,output reg isack,output scl,inout  sda
);// -------------------------------------------------------------------------//                           信号分频// -------------------------------------------------------------------------reg [$clog2(DIV_CLK):0] clk_cnt;reg scl_clk;always @(posedge clk or negedge rst_n) beginif (~rst_n) beginclk_cnt <= 16'd0;scl_clk <= 1'd0;end else beginif (clk_cnt == (DIV_CLK >> 1) - 1) beginclk_cnt <= 16'd0;scl_clk = ~scl_clk;end else beginclk_cnt <= clk_cnt + 16'd1;endendendlocalparam  ST_IDLE = 0, ST_START = 1, ST_WR_DATA = 2, ST_WR_READ_ACK = 3, ST_WR_CHECK_ACK = 4, ST_RD_START = 5, ST_RD_DATA = 6, ST_RD_ACK_REPLY = 7, ST_RD_ACK_DONE = 8, ST_STOP = 9;reg [3:0] state = ST_IDLE;assign scl = (state == ST_IDLE || state == ST_START) ? 1 : scl_clk;// SDA 输入输出切换reg sda_r = 1;reg sda_oe = 1;assign sda = sda_oe ? sda_r : 1'bz;// 写入和读取数据时使用的中间变量reg [7:0] byte_no = 0;reg [3:0] bit_no = 0;always @(posedge clk or negedge rst_n) beginif (~rst_n) beginisack <= 0;busy <= 0;rddata <= 0;end else begincase (state)ST_IDLE: beginsda_r <= 1;if (enable) beginbusy <= 1;endif (busy && clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginstate <= ST_START;endend// 产生开始信号, 即在 scl_clk 为高电平时 sda 由高变低ST_START: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginsda_r <= 0;state <= ST_WR_DATA;byte_no <= wrlen;bit_no <= 4'd0;isack <= 0;endend// 写入数据, wrdata 的最高位字节是器件的 i2c 地址以及读写标志ST_WR_DATA: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginif (bit_no == 4'd8) beginstate   <= ST_WR_READ_ACK;byte_no <= byte_no - 1'b1;bit_no  <= 4'd0;// 这里切换 sda 为读模式以便在下一个 scl_clk 高电平时接收应答sda_oe  <= 0;end else beginbit_no <= bit_no + 1'b1;sda_r  <= wrdata[byte_no*8-1-bit_no-:1];sda_oe <= 1;endendend// scl_clk 为高时读取应答信号ST_WR_READ_ACK: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginif (sda) isack <= 1;state <= ST_WR_CHECK_ACK;endend// scl_clk 为低时检测应答信号并做出状态转移ST_WR_CHECK_ACK: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) begin// 数据还没写完则继续进入写数据状态if (isack && byte_no != 0) beginstate <= ST_WR_DATA;bit_no <= bit_no + 1'b1;sda_r <= wrdata[byte_no*8-1-bit_no-:1];sda_oe <= 1;isack <= 0;// 否则进入读状态end else if (isack && rdlen > 0) beginstate  <= ST_RD_START;sda_oe <= 1;sda_r  <= 1;// 没有应答或数据写完了且不需要读模式则停止传输end else beginstate  <= ST_STOP;sda_r  <= 0;sda_oe <= 0;endendend// 在 scl_clk 为高时将 sda 输出为低进入读状态ST_RD_START: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginsda_r <= 0;// 同时在 scl_clk 为低时将 sda 切到读模式, 以便下一个 scl_clk 为高时将 sda 读取end else if (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginsda_oe  <= 0;state   <= ST_RD_DATA;bit_no  <= 0;byte_no <= rdlen;endend// 在 scl_clk 为高时读取 sda 的数据ST_RD_DATA: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginbit_no <= bit_no + 1'b1;rddata[byte_no*8-1-bit_no-:1] <= sda;if (bit_no == 4'd7) beginstate   <= ST_RD_ACK_REPLY;byte_no <= byte_no - 1'b1;bit_no  <= 4'd0;endendend// 在 scl_clk 为低时读取产生应答信号, 以便在下一个高电平被对方采集到ST_RD_ACK_REPLY: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginsda_oe = 1;sda_r <= 0;state <= ST_RD_ACK_DONE;endend// 经过了一个高电平, 在该低电平处判断数据有没有传完以进行状态转移ST_RD_ACK_DONE: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 0) beginif (byte_no == 0) beginsda_oe = 1;state <= ST_STOP;end else beginsda_oe = 0;state <= ST_RD_DATA;endendend// 产生停止信号, 即在 scl_clk 为高电平时将 sda 由低变高ST_STOP: beginif (clk_cnt == DIV_CLK >> 2 && scl_clk == 1) beginsda_oe <= 1;sda_r  <= 1;state  <= ST_IDLE;busy   <= 0;endendendcaseendendendmodule

仿真文件 tb.v

`timescale 1ns / 1nsmodule tb;// -------------------------------------------------------------------------//                    clk and reset signal // -------------------------------------------------------------------------reg clk;reg rst_n;initial clk = 0;always #10 clk = ~clk;initial beginrst_n = 0;#100;rst_n = 1;endinitial begin$dumpvars;#5000000;$finish;endreg enable;wire busy;initial beginenable = 0;@(posedge rst_n);enable = 1;@(negedge busy);enable = 0;#10000;enable = 1;@(negedge busy);enable = 0;#10000;endwire [15:0] rddata;wire isack;iic #(.DIV_CLK(100),.WR_MAX (8'd2),.RD_MAX (8'd2)) u_iic (.clk   (clk),.rst_n (rst_n),.enable(enable),.busy  (busy),.rdlen (8'd2),.wrlen (8'd2),.rddata(rddata),.wrdata(16'hd552),.isack (isack),.scl   (scl),.sda   (sda));endmodule

通常直接使用上面的 testbench 由于没有接实际的设备,没有收到应答后 iic 模块会自动停止传输,并拉低 busy 标志, 同时 isack 也为 0

为了方便查看波形,以下是将 iic.v 的 if (sda) isack <= 1; 改为 if (1) isack <= 1; 即在 读取 ack 信号时, 总是假设能读到

并且 将读信号 rddata[byte_no*8-1-bit_no-:1] <= sda; 总是写读到 1,rddata[byte_no*8-1-bit_no-:1] <= 1; 于是有如下波形

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/707262.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【c语言】TIMI哥听课笔记

计算机的组成 主储存器&#xff1a;内存条&#xff0c;硬盘 CPU内部&#xff1a;运算器&#xff0c;控制器&#xff0c;寄存器 进制转化&#xff1a;二转八拆三&#xff0c;二转十六拆四 基本数据类型 常量&#xff1a;整型常量&#xff08;十进制&#xff0c;0x十六&#x…

【Altium】AD-检查原理图中元器件未连接的Passive Pin

1、 文档目标 如何让原理图编译时找出元器件上未连接的Passive Pin 2、 问题场景 当引脚属性&#xff08;Pin type&#xff09;为passive时&#xff0c;原理图编译的默认规则是不会去检查它们是否有连接的。在实际设计过程中&#xff0c;经常会有导线虚连&#xff0c;漏连的事…

24长三角B题1-5问完整代码+15页保姆级思路已更新

比赛题目的完整版思路可执行代码数据参考论文都会在第一时间更新上传的&#xff0c;大家可以参考我往期的资料&#xff0c;所有的资料数据以及到最后更新的参考论文都是一次付费后续免费的。注意&#xff1a;&#xff08;建议先下单占坑&#xff0c;因为随着后续我们更新资料数…

添加屏幕照片太大了怎么缩小?改变图片大小这几个方法够了

现在我们经常使用手机、平板电脑和相机拍摄照片&#xff0c;然而&#xff0c;有时候我们可能会遇到一个常见的问题就是照片的尺寸太大&#xff0c;难以在特定场合或平台上使用&#xff0c;其实不用担心&#xff0c;本教程将向大家介绍几个如何简单地调整图片大小的方法&#xf…

06_机器学习算法_朴素贝叶斯

1. 朴素贝叶斯的介绍与应用 1.1 朴素贝叶斯的介绍 朴素贝叶斯算法(Naive Bayes, NB)是应用最为广泛的分类算法之一。它是基于贝叶斯定义和特征条件独立假设的分类方法。由于朴素贝叶斯法基于贝叶斯公式计算得到,有着坚实的数学基础,以及稳定的分类效率。NB模型所需估计的…

【Linux】线程周边001之多线程

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.线程的理解 2.地址…

狙击策略专用术语以及含义,WeTrade3秒讲解

想必各位交易高手对狙击策略不会陌生吧!但你想必不知道狙击策略的开发者为了推广狙击策略&#xff0c;在狙击策略基础的经典技术分析理论引入了自己的术语。今天WeTrade众汇和各位投资者继续了解狙击策略专用术语以及含义。 一.BL 银行级别(BL)是前一日线收盘的级别。时间是格…

外贸业务中的12个“坑”,你踩到了吗?

在竞争激烈的外贸领域&#xff0c;企业在拓展市场的同时&#xff0c;也面临着各种潜在的陷阱和风险。对于外贸公司而言&#xff0c;如何在复杂的交易过程中识破陷阱&#xff0c;防范潜在风险&#xff0c;成为确保企业长远发展的关键一环。 以下是一些外贸企业可能遇到的陷阱&a…

栈和队列的相互实现(C)

目录 1.[用栈实现队列]<https://leetcode.cn/problems/implement-queue-using-stacks/description/>2.全套代码3.[用队列实现栈]<https://leetcode.cn/problems/implement-stack-using-queues/description/>4.全套代码 1.[用栈实现队列]https://leetcode.cn/proble…

【Linux系统编程】第十九弹---进程状态(下)

​​​​​​​ ✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、僵尸进程 2、孤儿进程 3、运行状态 4、阻塞状态 5、挂起状态 6、进程切换 总结 1、僵尸进程 上一弹…

AIGC文生视频:Sora模型报告总结

作为世界模拟器的视频生成模型 我们探索视频数据生成模型的大规模训练。具体来说&#xff0c;我们在可变持续时间、分辨率和宽高比的视频和图像上联合训练文本条件扩散模型。我们利用对视频和图像潜在代码的时空补丁进行操作的变压器架构。我们最大的模型 Sora 能够生成一分钟…

利用宝塔面板搭建nodejs网站(不使用pm2)

利用宝塔面板搭建nodejs网站&#xff08;不使用pm2&#xff09; 1. 准备代码文件2. 将代码上传至云主机3. 云主机配置3.1 绑定域名3.2 利用面板配置node环境3.3 利用面板增加node项目 4. 打开端口 暂时只演示http的。https类似&#xff0c;需要添加证书。 1. 准备代码文件 清单…