当前位置: 首页 > news >正文

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)

  • 单字节写时序
  • 单字节读时序
  • I2C 控制器设计
    • 模块框图
    • scl_high 和 scl_low 产生的时序图
    • 状态转移图
  • Verilog代码

FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(1)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(2)
FPGA_IIC代码-正点原子 野火 小梅哥 特权同学对比写法(3)

单字节写时序

EEPROM器件或其他不同器件,I2C 器件地址字节不同,这样对于 I2C 单字节写时序就会有所差别,如下图分别为 1 字节地址段器件和 2 字节地址段器件单字节写时序图。
在这里插入图片描述
根据时序图,从主机角度来描述一次写入单字节数据过程如下:
主机设置 SDA 为输出;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 为输出,传输 1 字节地址数据;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,对于两字节地址段器件,传输地址数据低字节,对于 1字节地址段器件,主机设置 SDA 为输出,传输待写入的数据;
设置 SDA 为三态门输入,读取从机应答信号,对于两字节地址段器件,接着步骤 i;对于 1 字节地址段器件,直接跳转到步骤 k;
读取应答信号成功,主机设置 SDA 为输出,传输待写入的数据(对于两字节地址段器件);
设置 SDA 为三态门输入,读取从机应答信号(两字节地址段器件);
读取应答信号成功,主机产生 STOP 位,终止传输。

单字节读时序

在这里插入图片描述
根据时序图,从主机角度描述一次读数据过程,如下:
主机设置 SDA 为输出;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 0,表明为写操作;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 输出,传输 1 字节地址数据;
主机设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 输出,对于两字节地址段器件,传输低字节地址数据;对于 1 字节地址段器件,无此步骤,直接跳转到步骤 h;
主机发起起始信号;
主机传输器件地址字节,其中最低位为 1,表明为读操作;
设置 SDA 为三态门输入,读取从机应答信号;
读取应答信号成功,主机设置 SDA 为三态门输入,读取 SDA 总线上的一个字节的数据;
产生无应答信号(高电平)(无需设置为输出高电平,因为总线会被自动拉高);
主机产生 STOP 位,终止传输。

I2C 控制器设计

模块框图

在这里插入图片描述
状态机里面线性序列机思路

在这里插入图片描述

scl_high 和 scl_low 产生的时序图

通过上述的讲述,对 I2C 读写器件数据时序有了一定的了解,下面将开始进行控制程序的设计。根据上面 I2C 的基本概念中有关读写时SDA 与 SCL 时序,不管对于从机还是主机,SDA 上的每一位数据在 SCL 的高电平期间保持不变,而数据的改变总是在 SCL 的低电平期间发生。因此,我们可以选用 2 个标志位对时钟 SCL 的高电平和低电平进行标记,如下图所示:scl_high 对 SCL 高电平期间进行标志,scl_low 对 SCL 低电平期间进行标志。这样就可以在 scl_high 有效时读 SDA 数据,在 scl_low 有效时改变数据

如下图所示
在这里插入图片描述

在状态机中,从主机角度来看,SDA 数据线上在写控制、写数据、读控制状态过程是需要串行输出数据,而在读数据状态过程是需要串行输入数据。根据数据在时钟高电平期间保持不变,改变数据在低电平时期的规则,本设计对时钟信号的高低电平进行计数,从而在指定的计数值进行输出或读取数据实现数据的串行输出和串行输入。串行输出和串行输入数据采用任务的形式进行表示,便于在主状态机中多次的调用。图下为计数的过程以及特定状态变化的时序图,这里的特定状态主要是指读/写控制、读/写地址和读/写数据状态。
在这里插入图片描述

状态转移图

定义
在这里插入图片描述

在这里插入图片描述

独热码
在这里插入图片描述

为了节省资源,可以通过上一个状态一定会执行下一个状态进而连贯在一起弄一个组合拳法

创建任务的思想
对于计数器 halfbit_cnt 只在写控制、写数据、读控制、读数据状态下才进行计数,其他状态为零。代码中 FF 是进行串行输出或输入任务的标志位,当 FF 为1 时表示退出任务,FF 为 0 时表示进入任务。这样便于在状态机中对任务的调用,以及在指定的时间退出任务。

状态机里面线性序列机思路

Verilog代码

module I2C(Clk,Rst_n,Rddata_num,Wrdata_num,Wdaddr_num,Device_addr,Word_addr,Wr,Wr_data,Wr_data_vaild,Rd,Rd_data,Rd_data_vaild,Scl,Sda,Done
);//系统时钟采用50MHz
parameter SYS_CLOCK = 50_000_000;
//SCL总线时钟采用400kHz
parameter SCL_CLOCK = 400_000;
//产生时钟SCL计数器最大值
localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK;input             Clk;          //系统时钟input             Rst_n;        //系统复位信号input     [5:0]   Rddata_num;   //I2C总线连续读取数据字节数input     [5:0]   Wrdata_num;   //I2C总线连续读取数据字节数input     [1:0]   Wdaddr_num;   //I2C器件数据地址字节数input     [2:0]   Device_addr;  //I2C器件地址input     [15:0]  Word_addr;    //I2C寄存器地址input             Wr;           //I2C器件写使能input     [7:0]   Wr_data;      //I2C器件写数据output            Wr_data_vaild;//I2C器件写数据有效标志位input             Rd;           //I2C器件读使能output reg[7:0]   Rd_data;      //I2C器件读数据output reg        Rd_data_vaild;//I2C器件读数据有效标志位output  reg       Scl;          //I2C时钟线inout             Sda;          //I2C数据线output  reg       Done;         //对I2C器件读写完成标识位//主状态机状态localparam  IDLE     = 9'b0_0000_0001,//空闲状态WR_START = 9'b0_0000_0010,//写开始状态WR_CTRL  = 9'b0_0000_0100,//写控制状态WR_WADDR = 9'b0_0000_1000,//写地址状态WR_DATA  = 9'b0_0001_0000,//写数据状态RD_START = 9'b0_0010_0000,//读开始状态RD_CTRL  = 9'b0_0100_0000,//读控制状态RD_DATA  = 9'b0_1000_0000,//读数据状态STOP     = 9'b1_0000_0000;//停止状态reg [8:0] main_state;	   //主状态机状态寄存器reg       sda_en;          //sda数据总线控制位reg       sda_reg;         //sda数据输出寄存器reg       W_flag;          //IIC写操作标志位reg       R_flag;          //IIC读操作标志位reg       FF;              //串行输出输入任务执行标志位wire[7:0] wr_ctrl_word;    //写控制数据寄存器wire[7:0] rd_ctrl_word;    //读控制数据寄存器reg [15:0]scl_cnt;         //SCL时钟计数器reg       scl_vaild;       //IIC非空闲时期reg       scl_high;        //SCL时钟高电平中部标志位reg       scl_low;         //SCL时钟低电平中部标志位	reg [7:0] halfbit_cnt;     //串行数据传输计数器reg       ack;             //串行输出输入高低电平计数完成标志位reg [1:0] waddr_cnt;       //地址字节数计数器reg [7:0] wdata_cnt;       //写数据字节数计数器reg [7:0] rdata_cnt;       //读数据字节数计数器reg [7:0] sda_data_out;    //待输出SDA串行数据reg [7:0] sda_data_in;     //SDA串行输入后数据wire      rdata_vaild_r; //读写控制字assign wr_ctrl_word = {4'b1010, Device_addr,1'b0};assign rd_ctrl_word = {4'b1010, Device_addr,1'b1};//I2C数据线采用三态门传输assign Sda = sda_en ? sda_reg : 1'bz;//I2C非空闲时期scl_vaild的产生always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_vaild <= 1'b0;else if(Wr | Rd)scl_vaild <= 1'b1;else if(Done)scl_vaild <= 1'b0;elsescl_vaild <= scl_vaild;end//scl时钟计数器always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_cnt <= 16'd0;else if(scl_vaild)beginif(scl_cnt == SCL_CNT_M - 1)scl_cnt <= 16'd0;elsescl_cnt <= scl_cnt + 16'd1;endelsescl_cnt <= 16'd0;end//scl时钟,在计数器值到达最大值一半和0时翻转always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Scl <= 1'b1;else if(scl_cnt == SCL_CNT_M >>1)Scl <= 1'b0;else if(scl_cnt == 16'd0)Scl <= 1'b1;elseScl <= Scl;end	//scl时钟高低电平中部标志位always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_high <= 1'b0;else if(scl_cnt == (SCL_CNT_M>>2))scl_high <= 1'b1;elsescl_high <= 1'b0;end//scl时钟低电平中部标志位//(SCL_CNT_M>>2)和(SCL_CNT_M>>1)+(SCL_CNT_M>>2)分别为 1/4 的 SCL_CNT_M 和 3/4 的SCL_CNT_M 的计数值。always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)scl_low <= 1'b0;else if(scl_cnt == (SCL_CNT_M>>1)+(SCL_CNT_M>>2))scl_low <= 1'b1;elsescl_low <= 1'b0;end	//主状态机always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)beginmain_state <= IDLE;sda_reg    <= 1'b1;//三态控制信号W_flag     <= 1'b0;//写标注      R_flag     <= 1'b0;//读标注 			Done       <= 1'b0;//完成waddr_cnt  <= 2'd1;//地址 wdata_cnt  <= 8'd1;//写数据rdata_cnt  <= 8'd1;//读数据endelse begincase(main_state)IDLE:begin//空闲状态sda_reg   <= 1'b1;W_flag    <= 1'b0;R_flag    <= 1'b0;Done      <= 1'b0;waddr_cnt <= 2'd1;wdata_cnt <= 8'd1;rdata_cnt <= 8'd1;if(Wr)beginmain_state <= WR_START;W_flag     <= 1'b1;endelse if(Rd)beginmain_state <= WR_START;R_flag     <= 1'b1;endelsemain_state <= IDLE;endWR_START:begin//写开始状态if(scl_low)begin//在scl_low寄存器下个周期发送起始信号main_state   <= WR_CTRL;sda_data_out <= wr_ctrl_word;FF           <= 1'b0;endelse if(scl_high)beginsda_reg    <= 1'b0;main_state <= WR_START;endelsemain_state <= WR_START;endWR_CTRL:begin//写控制状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(scl_low)beginmain_state   <= WR_WADDR;FF           <= 1'b0;if(Wdaddr_num == 2'b1)sda_data_out <= Word_addr[7:0];elsesda_data_out <= Word_addr[15:8];endelsemain_state   <= WR_CTRL;endelse//未收到响应main_state      <= IDLE;endendWR_WADDR:begin//写地址状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(waddr_cnt == Wdaddr_num)beginif(W_flag && scl_low)beginmain_state   <= WR_DATA;sda_data_out <= Wr_data;waddr_cnt    <= 2'd1;FF           <= 1'b0;endelse if(R_flag && scl_low)beginmain_state <= RD_START;sda_reg    <= 1'b1;endelsemain_state <= WR_WADDR;endelse beginif(scl_low)beginwaddr_cnt    <= waddr_cnt + 2'd1;main_state   <= WR_WADDR;sda_data_out <= Word_addr[7:0];FF           <= 1'b0;endelsemain_state   <= WR_WADDR;endendelse//未收到响应main_state <= IDLE;endendWR_DATA:begin//写数据状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(wdata_cnt == Wrdata_num)beginif(scl_low)beginmain_state <= STOP;sda_reg    <= 1'b0;wdata_cnt  <= 8'd1;endelsemain_state <= WR_DATA;endelse beginif(scl_low)beginwdata_cnt    <= wdata_cnt + 8'd1;main_state   <= WR_DATA;sda_data_out <= Wr_data;FF           <= 1'b0;endelsemain_state   <= WR_DATA;endendelse//未收到响应main_state <= IDLE;endendRD_START:begin//读开始状态if(scl_low)beginmain_state   <= RD_CTRL;sda_data_out <= rd_ctrl_word;FF           <= 1'b0;endelse if(scl_high)beginmain_state <= RD_START;sda_reg    <= 1'b0;endelsemain_state <= RD_START;endRD_CTRL:begin//读控制状态if(FF == 1'b0)send_8bit_data;else beginif(ack == 1'b1) begin//收到响应if(scl_low)beginmain_state <= RD_DATA;FF         <= 1'b0;endelsemain_state <= RD_CTRL;endelse//未收到响应main_state    <= IDLE;endendRD_DATA:begin//读数据状态if(FF == 1'b0)receive_8bit_data;else beginif(rdata_cnt == Rddata_num)beginsda_reg <= 1'b1;if(scl_low)beginmain_state <= STOP;sda_reg    <= 1'b0;endelsemain_state <= RD_DATA;endelse beginsda_reg <= 1'b0;if(scl_low)beginrdata_cnt  <= rdata_cnt + 8'd1;main_state <= RD_DATA;FF         <= 1'b0;endelsemain_state <= RD_DATA;endendendSTOP:begin//结束操作if(scl_high)beginsda_reg    <= 1'b1;main_state <= IDLE;Done       <= 1'b1;endelsemain_state <= STOP;enddefault:main_state <= IDLE;endcaseendend//sda串行接收与发送时scl高低电平计数器根据中计数器 halfbit_cnt 和数据接收方对发送的响应检测标志位 ack 以及串行输出、输入数据任务always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)halfbit_cnt <= 8'd0;else if((main_state == WR_CTRL)||(main_state == WR_WADDR)||(main_state == WR_DATA)||(main_state == RD_CTRL)||(main_state == RD_DATA))beginif(scl_low | scl_high)beginif(halfbit_cnt == 8'd17)halfbit_cnt <= 8'd0;elsehalfbit_cnt <= halfbit_cnt + 8'd1;endelsehalfbit_cnt <= halfbit_cnt;endelsehalfbit_cnt <= 8'd0;end//数据接收方对发送的响应检测标志位always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)ack <= 1'b0;else if((halfbit_cnt == 8'd16)&&scl_high&&(Sda==1'b0))ack <= 1'b1;else if((halfbit_cnt == 8'd17)&&scl_low)ack <= 1'b0;elseack <= ack;end//输出串行数据任务task send_8bit_data;if(scl_high && (halfbit_cnt == 8'd16))FF <= 1;else if(halfbit_cnt < 8'd17)beginsda_reg <= sda_data_out[7];if(scl_low)sda_data_out <= {sda_data_out[6:0],1'b0};elsesda_data_out <= sda_data_out;endelse;endtask//串行数据输入任务task receive_8bit_data;if(scl_low && (halfbit_cnt == 8'd15))FF <= 1;else if((halfbit_cnt < 8'd15))beginif(scl_high)sda_data_in <= {sda_data_in[6:0],Sda};else beginsda_data_in <= sda_data_in;endendelse;endtask//sda三态使能信号sda_enalways@(*)begincase(main_state)IDLE:sda_en = 1'b0;WR_START,RD_START,STOP:sda_en = 1'b1;WR_CTRL,WR_WADDR,WR_DATA,RD_CTRL:if(halfbit_cnt < 16)sda_en = 1'b1;elsesda_en = 1'b0;RD_DATA:if(halfbit_cnt < 16)sda_en = 1'b0;elsesda_en = 1'b1;		default:sda_en = 1'b0;		endcaseend//写数据有效标志位assign Wr_data_vaild = ((main_state==WR_WADDR)&&(waddr_cnt==Wdaddr_num)&&(W_flag && scl_low)&&(ack == 1'b1))||((main_state == WR_DATA)&&(ack == 1'b1)&&(scl_low)&&(wdata_cnt != Wrdata_num));//读数据有效标志位前寄存器assign rdata_vaild_r = (main_state == RD_DATA)&&(halfbit_cnt == 8'd15)&&scl_low;//读出数据有效标志位always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Rd_data_vaild <= 1'b0;else if(rdata_vaild_r)Rd_data_vaild <= 1'b1;elseRd_data_vaild <= 1'b0;end//读出的有效数据always@(posedge Clk or negedge Rst_n)beginif(!Rst_n)Rd_data <= 8'd0;else if(rdata_vaild_r)Rd_data <= sda_data_in;elseRd_data <= Rd_data;endendmodule 
http://www.lryc.cn/news/237087.html

相关文章:

  • LabVIEW编程开发NI-USRP
  • ES6中实现继承
  • 车载通信架构 —— 新车载总线类型下(以太网)的通信架构
  • ArkTS - HarmonyOS服务卡片(创建)
  • Zotero在word中插入带超链接的参考文献/交叉引用/跳转参考文献
  • 持续集成部署-k8s-配置与存储-配置管理:ConfigMap 的热更新
  • Python文本段落翻译
  • Flink(七)【输出算子(Sink)】
  • 【自我管理】To-do list已过时?学写Done List培养事业成功感
  • 优思学院|什么是精益生产管理?从一个生活上的故事出发来说明。
  • Swagger-----knife4j框架
  • 企业数字化过程中数据仓库与商业智能的目标
  • 从零开始写一个APM监控程序(一)协议
  • UDP网络套接字编程
  • 【苏州元德维康生物医药-注册】
  • 从零带你底层实现unordered_map (1)
  • 第六届浙江省大学生网络与信息安全竞赛 2023年 初赛/决赛 WEB方向 Writeup
  • 设计模式篇---装饰模式
  • JAXB:根据Java文件生成XML schema文件
  • opencv(5): 滤波器
  • 《微信小程序开发从入门到实战》学习二十二
  • LLM模型-讯飞星火与百度文心api调用
  • AIGC ChatGPT 4 将数据接口文件使用Python进行入库Mysql
  • Loguru:一个超酷的Python库
  • cloud的概念
  • 物联网赋能:WIFI HaLow在无线连接中的优势
  • 淘宝商品详情数据接口(Taobao.item_get)
  • 视频剪辑方法:一键批量调整色调的高效技巧
  • NAS层协议栈学习笔记
  • 前端食堂技术周刊第 105 期:TS 5.3 RC、Vite 5.0、W3C 新任 CEO、有害的 Pinia 模式、2024 更快的 Web