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

FPGA开发——IIC实现简单的串口回环

一、概述

        在我们进行日常开发时,不管是进行MCU、单片机、还是FPGA,都会使用到IIC通信协议。采用串行总线可以简化系统硬件结构、减小系统体积、提高系统可靠性。常 用的串行总线有单总线(1-Wire Bus)、IICInter-Integrated Circuit)、SPISerial Peripheral Interface)等。 IIC 总线是 Phlips 公司推出的一种同步串行总线,是一种支持多主机多从机 系统、总线仲裁以及高低速器件同步功能的高性能串行总线技术。

        在前面学习串口通信的时候,我们实现了数据回环的设计,现在我们学到了IIC通信协议,我们也来进行一下简单的IIC数据回环的设计,所谓的IIC数据回环设计就是在使用我们设计出的串口的基础之上,编写IIC的主机和从机,按照IIC通信协议的要求,进行一个数据的交互,从而实现IIC的数据回环实现。今天这篇文章的目的就是为了让我们熟悉一下IIC的通信协议。

二、框架构造

1、总体系统框架

        在框图中可以看到,我们实现的思路就是通过串口接收和发送模块实现与PC机的交互,从而实现数据的回环,而IIC的作用就是通过对于接收模块接收到的数据进行处理,最后把数据传递给发送模块。        

2、IIC主机模块

图中我们把SDA分成了三根线,在代码端口定义中我们对于sda_oen,sda_out,sda_in进行了处理,最后只使用了一根线。

 

3、从机模块

因为我们在主机中对于SDA线进行了处理,所以对与从机来说,只需要一根线接收就行,只不过为了方便观察我们写出来了。

 

三、工程实现

1、IIC主机模块的代码编写

        新建一个iic_master.v文件,如下:这里的输入数据是来自串口接收模块接收到的数据

//---------<模块及端口声名>-------------------------------------------
module iic_master( input				clk		,input				rst_n	,//IICinput		[7:0]   din		,input               send_en ,//iic_slave从机数据output		reg    	iic_scl	,inout		    	iic_sda	
);								 
//---------<参数定义>------------------------------------------------
parameter  CLK   =50_000_000 ;
parameter  SPEED =100_000;//传输速率
//---------<内部信号定义>--------------------------------------------
parameter      IDLE     =5'b000_01 ,START    =5'b000_10 ,TRANS    =5'b001_00 ,ACK      =5'b010_00 ,STOP     =5'b100_00 ;parameter  CNT_MAX =CLK/SPEED;reg             iic_sda_out;
reg             iic_sda_en ;
wire            iic_sda_in ;reg     [9:0]  scl_cnt;//SCL时钟计数器
wire           add_scl_cnt;
wire           end_scl_cnt;reg		[3:0]	cnt_bit	   ;
wire			add_cnt_bit;
wire			end_cnt_bit;reg            iic_scl_r;
wire            nedge;   
reg     [4:0]   state_c ;//现态
reg     [4:0]   state_n ;//次态wire    idle2start  ;
wire    start2trans ;
wire    trans2ack   ;
wire    ack2stop    ;
wire    stop2idle   ;reg     [7:0] din_r;//输入数据缓存//IIC_SDA描述
assign iic_sda =iic_sda_en ? iic_sda_out : 1'bz;
assign iic_sda_in =iic_sda;
//第一段:同步时序描述状态转移
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginstate_c <=IDLE ;end else begin state_c <= state_n;end 
end//第二段:组合逻辑判断状态转移条件,描述状态转移规律
always @(*) begincase(state_c)IDLE  : beginif(idle2start)state_n = START;elsestate_n = state_c; endSTART : beginif(start2trans)state_n = TRANS;elsestate_n = state_c; endTRANS : beginif(trans2ack)state_n = ACK;elsestate_n = state_c; endACK   : beginif(ack2stop)state_n = STOP;elsestate_n = state_c; endSTOP  : beginif(stop2idle)state_n = IDLE;elsestate_n = state_c; enddefault : ;endcase
endassign idle2start  = state_c == IDLE  && send_en;
assign start2trans = state_c == START && end_scl_cnt;
assign trans2ack   = state_c == TRANS && end_cnt_bit;
assign ack2stop    = state_c == ACK   && end_scl_cnt;
assign stop2idle   = state_c == STOP  && end_scl_cnt;//第三段:描述状态输出(组合逻辑/时序逻辑)
//scl_cnt
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginscl_cnt <= 'd0;end else if(add_scl_cnt)begin if(end_scl_cnt)begin scl_cnt <= 'd0;endelse begin scl_cnt <= scl_cnt + 1'b1;end end
end assign add_scl_cnt = state_c!=IDLE ;
assign end_scl_cnt = add_scl_cnt && scl_cnt ==CNT_MAX-1 ;
//IIC_SCL时钟
always @(posedge clk or negedge rst_n)begin if(!rst_n)iic_scl  <=1'b1;else if(state_c !=IDLE && scl_cnt<=CNT_MAX/2-1)iic_scl <= 1'b0;elseiic_scl <=1'b1;
end    //bit计数器
always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_bit <= 'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 'd0;endelse begin cnt_bit <= cnt_bit + 1'b1;end end
end assign add_cnt_bit =(state_c ==TRANS) && end_scl_cnt ;
assign end_cnt_bit = add_cnt_bit && cnt_bit ==8-1 ;//din_r
always @(posedge clk or negedge rst_n)begin if(!rst_n)begindin_r <= 'd0;end else if(send_en)begin din_r <= din;end 
endalways @(posedge clk or negedge rst_n)begin if(!rst_n)beginiic_sda_en <= 'd0;iic_sda_out <= 'd1;end else begincase (state_c)IDLE  :begin//修改iic_sda_en <= 'd0;iic_sda_out <= 1'b1;endSTART :beginiic_sda_en <= 1'b1;if((iic_scl==1) && (scl_cnt>=(CNT_MAX/2+CNT_MAX/4)))iic_sda_out <= 1'b0;elseiic_sda_out <= 1'b1;endTRANS :beginiic_sda_en <= 1'b1;if((iic_scl==0) && (scl_cnt <=CNT_MAX>>2))iic_sda_out <= din_r[7-cnt_bit];               endACK   :beginiic_sda_en <= 1'b0;iic_sda_out <= 1'bz;endSTOP  :beginiic_sda_en <= 1'b1;if(scl_cnt<=CNT_MAX>>2)iic_sda_out <= 1'b0;else if((iic_scl==1) && (scl_cnt>=(CNT_MAX/2+CNT_MAX/4)))iic_sda_out <= 1'b1;enddefault:;endcaseendend   
endmodule 

2、从机模块代码的编写

这里和主机模块一样也是新建一个iic_slave.v文件,如下:

    
//---------<模块及端口声名>-------------------------------------------
module iic_slave( input				clk		,input				rst_n	,//IICinput				iic_scl	,inout		        iic_sda	,//uart_txoutput	reg	[7:0]	dout	,//串口发送模块输入数据output	reg     	dout_vld //串口发送模块输入数据标志位
);								 
//---------<参数定义>------------------------------------------------
parameter  CLK   =50_000_000 ;
parameter  SPEED =100_000;//传输速率
//---------<内部信号定义>--------------------------------------------
parameter      IDLE     =5'b000_01 ,START    =5'b000_10 ,TRANS    =5'b001_00 ,ACK      =5'b010_00 ,STOP     =5'b100_00 ;parameter  CNT_MAX =CLK/SPEED;reg iic_scl_r1;//同步打拍
reg iic_scl_r2;
reg iic_sda_r1;
reg iic_sda_r2;wire     sda_nedge;//sda信号检测
wire     sda_pedge;wire     scl_nedge;//scl信号检测
wire     scl_pedge;reg         iic_sda_en;//对于sda线进行一个描述(三台门)
reg         iic_sda_out;
wire        iic_sda_in;reg   [3:0]  cnt_bit;
wire         add_cnt_bit;
wire         end_cnt_bit;reg     [9:0]  scl_cnt;//SCL时钟计数器
wire           add_scl_cnt;
wire           end_scl_cnt;   reg     [4:0]   state_c ;//现态
reg     [4:0]   state_n ;//次态wire    idle2start  ;
wire    start2trans ;
wire    trans2ack   ;
wire    ack2stop    ;
wire    stop2idle   ;reg     [7:0] dout_r;//输出数据寄存器
//sda信号描述
assign  iic_sda = iic_sda_en ? iic_sda_out :1'bz;
assign  iic_sda_in = iic_sda;
//第一段:同步时序描述状态转移
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginstate_c <= IDLE;end else begin state_c <= state_n;end 
end//第二段:组合逻辑判断状态转移条件,描述状态转移规律
always @(*) begincase(state_c)IDLE  : beginif(idle2start)state_n<=START   ;elsestate_n<= state_c;     endSTART : beginif(start2trans)state_n<=TRANS   ;elsestate_n<= state_c;     endTRANS : beginif(trans2ack)state_n<=ACK   ;elsestate_n<= state_c;     endACK   : beginif(ack2stop)state_n<=STOP   ;elsestate_n<= state_c;     endSTOP  : beginif(stop2idle)state_n<=IDLE   ;elsestate_n<= state_c;     enddefault : state_n <=IDLE ;endcase
end
assign idle2start  = (state_c == IDLE    ) && (iic_scl_r2 && sda_nedge);
assign start2trans = (state_c == START   ) && (scl_nedge);
assign trans2ack   = (state_c == TRANS   ) && (end_cnt_bit);
assign ack2stop    = (state_c == ACK     ) && (scl_nedge);
assign stop2idle   = (state_c == STOP    ) && (iic_scl_r2 && sda_pedge);//第三段:描述状态输出(组合逻辑/时序逻辑) 
//对于scl输入信号进行打拍
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginiic_scl_r1 <= 1'b1;iic_scl_r2 <= 1'b1;end else begin iic_scl_r1 <= iic_scl;iic_scl_r2 <= iic_scl_r1;end 
end 
assign scl_nedge= ~iic_scl_r1 && iic_scl_r2 ;
assign scl_pedge= iic_scl_r1 && ~iic_scl_r2 ;
//对于sda输入信号进行打拍
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginiic_sda_r1 <= 1'b1;iic_sda_r2 <= 1'b1;end else begin iic_sda_r1 <= iic_sda;iic_sda_r2 <= iic_sda_r1;end 
end 
assign sda_nedge= ~iic_sda_r1 && iic_sda_r2 ;
assign sda_pedge= iic_sda_r1 && ~iic_sda_r2 ;//bit计数器
always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_bit <= 'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 'd0;endelse begin cnt_bit <= cnt_bit + 1'b1;end end
end assign add_cnt_bit =(state_c ==TRANS) && scl_nedge ;
assign end_cnt_bit = add_cnt_bit && cnt_bit ==8-1 ;//sda_en、sda_out
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginiic_sda_en <= 'd0;iic_sda_out <= 'd1;end else if(state_c==ACK)beginiic_sda_en <= 1'b1;iic_sda_out <= 1'b0;endelse beginiic_sda_en  <= 'd0;iic_sda_out <= 'd0;end
end   //数据类型转换,串转并
always @(posedge clk or negedge rst_n)begin if(!rst_n)begindout_r <= 'd0;end else if(state_c==TRANS  && iic_scl_r2)begin dout_r[7-cnt_bit] <=iic_sda_r2;end 
end 
always @(posedge clk or negedge rst_n)begin if(!rst_n)begindout <= 'd0;end  else if(state_c==STOP)begin dout <=dout_r; end 
endalways @(posedge clk or negedge rst_n)begin if(!rst_n)begindout_vld <= 'd0;end  else begin dout_vld <=stop2idle; end 
end
endmodule 

关于串口接收和发送模块的代码在前面的文章中已经写过了,如果不知道怎么写的小伙伴直接去前面文章里面找就行。

3、顶层文件 的编写

通过顶层文件将串口接收模块和IIC主从机模块连接起来,从而实现数据回环。

新建top.v文件,如下:

module top (input       clk     ,input       rst_n   ,input       rx      ,output      tx          
);
wire  [7:0] rx_dout;
wire        rx_dout_vld;
wire            busy;
wire           iic_scl;
wire           iic_sda;
wire    [7:0]  tx_din    ;
wire           tx_din_vld;
uart_rx uart_rx_inst(/*input            */ .clk	     (clk        ),/*input            */ .rst_n     (rst_n	 ),        /*input            */ .din_rx    (rx     ),/*output  reg[7:0] */ .dout_data (rx_dout  ),/*output  reg      */ .dout_flag (rx_dout_vld  )
);iic_master iic_master_inst( /*input				  */.clk		(clk    ),/*input				  */.rst_n	    (rst_n  ),/*input       [7:0]   */.din    (rx_dout),/*input               */.send_en    (rx_dout_vld),//开始传输的使能信号/*output  reg         */.iic_scl    (iic_scl),/*inout               */.iic_sda    (iic_sda)
);		iic_slave u_iic_slave( /*input				  */.clk		    (clk    ),/*input				  */.rst_n	        (rst_n  ),/*input               */.iic_scl        (iic_scl),/*inout               */.iic_sda        (iic_sda),/*output   reg  [7:0] */.dout	        (tx_din    ),/*output   reg        */.dout_vld       (tx_din_vld)  
);		uart_tx uart_tx_inst(/*input        */ .clk        (clk      ) ,/*input        */ .rst_n      (rst_n    ) ,/*input   [7:0]*/ .tx_din     (tx_din     ) ,//进入发送模块准备发送的数据/*input        */ .tx_din_vld (tx_din_vld && ~busy    ) ,//要发送数据的有效信号/*output  reg  */ .tx_dout    (tx       ) ,//串行发送出去的数据/*output  reg  */ .busy       (busy   )  //发送一字节完成信号
);
endmodule

4、IIC测试文件的编写

因为串口我们前面已经写了并且测试过,所以这里我们只需要进行IIC的主机和从机进行一个仿真就可以得出结果,只要在这里数据能够正常交互,IIC的数据回环就没啥问题。

新建一个iic_tb.v文件,如下:

`timescale 1ns/1psmodule iic_tb();//激励信号定义 reg				tb_clk  	;reg				tb_rst_n	;reg     [7:0]   data_in     ;reg             send_en     ;reg             sda_in      ;//内部信号定义	 wire	        iic_scl     ;wire            iic_sda     ;
//输出信号定义wire    [7:0]   r_data      ;wire            r_data_vld  ;pullup(iic_sda);//时钟周期参数定义	parameter		CLOCK_CYCLE = 20;   //模块例化iic_master iic_master_inst( /*input				  */.clk	    (tb_clk     ),/*input				  */.rst_n	    (tb_rst_n   ),/*input       [7:0]   */.din		(data_in    ),/*input               */.send_en    (send_en    ),//开始传输的使能信号/*output  reg         */.iic_scl    (iic_scl    ),/*inout               */.iic_sda    (iic_sda    )
);	iic_slave iic_slave_inst( /*input				  */.clk		    (tb_clk     ),/*input				  */.rst_n	        (tb_rst_n   ),//iic_master()/*input               */.iic_scl        (iic_scl    ),.iic_sda        (iic_sda    ),//uart_tx()/*output   reg        */.dout         (r_data     ),/*output   reg        */.dout_vld     (r_data_vld )
);	//产生时钟initial 		tb_clk = 1'b0;always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;//产生激励initial  begin tb_rst_n = 1'b1;data_in = 0;send_en = 0;sda_in = 1'b1;#(CLOCK_CYCLE*2);tb_rst_n = 1'b0;#(CLOCK_CYCLE*20);tb_rst_n = 1'b1;#3;repeat(10)begindata_in = {$random}%256;send_en = 1'b1;#CLOCK_CYCLE;send_en = 1'b0;wait(iic_master_inst.stop2idle);#500;end#2000;$stop;endendmodule 

5、波形仿真

通过观察IIC仿真波形图中的输入和输出数据结果可知,两者是一样的,说明我们设计的IIC主机和从机模块没有问题。

 

6、下板验证

通过串口调试助手我们可以看到数据正常交互。设计完成。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Edge-TTS:微软推出的,免费、开源、支持多种中文语音语色的AI工具[Python代码]
  • Windows连接虚拟机中的mysql5失败
  • 跨域解决 | 面试常问问题
  • openGauss极简版单节点安装
  • 专题---自底向上的计算机网络(数据链路层)
  • 在国产芯片上实现YOLOv5/v8图像AI识别-【2.5】yolov8使用C++部署在RK3588更多内容见视频
  • Swift 可选链
  • 微服务实战系列之玩转Docker(十三)
  • 三种评估金融风险的方法的具体Python实现:Stress Testing、Scenario Analysis和Sensitivity Analysis
  • C#入门(7)显式转换
  • ORM框架:Mybatis与Hibernate
  • HTTP/1和HTTP/2
  • Jenkins发邮件功能如何配置以实现自动化?
  • IDEA工具设置默认使用maven的settings.xml文件
  • 浅谈线性表——栈
  • 【css3】浏览器内核及其兼容性
  • 230. Kth Smallest Element in a BST
  • Android单元测试 - 几个重要问题
  • Apache的基本使用
  • canvas 绘制双线技巧
  • Docker下部署自己的LNMP工作环境
  • ES6核心特性
  • ESLint简单操作
  • export和import的用法总结
  • Java面向对象及其三大特征
  • Js基础知识(一) - 变量
  • PHP CLI应用的调试原理
  • 近期前端发展计划
  • 蓝海存储开关机注意事项总结
  • 前端知识点整理(待续)
  • 三分钟教你同步 Visual Studio Code 设置
  • 深入 Nginx 之配置篇
  • 手机端车牌号码键盘的vue组件
  • 思考 CSS 架构
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 用Python写一份独特的元宵节祝福
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • nb
  • No resource identifier found for attribute,RxJava之zip操作符
  • 容器镜像
  • 选择阿里云数据库HBase版十大理由
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • (02)Hive SQL编译成MapReduce任务的过程
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (20)docke容器
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (附源码)基于SpringBoot和Vue的厨到家服务平台的设计与实现 毕业设计 063133
  • (附源码)计算机毕业设计大学生兼职系统
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (三分钟)速览传统边缘检测算子
  • (自用)gtest单元测试
  • .NET C# 配置 Options
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现