[米联客-XILINX-H3_CZ08_7100] FPGA程序设计基础实验连载-20 I2C MASTER控制器驱动设计

news2024/9/21 4:21:02

软件版本:VIVADO2021.1

操作系统:WIN10 64bit

硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA

实验平台:米联客-MLK-H3-CZ08-7100开发板

板卡获取平台:https://milianke.tmall.com/

登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

目录

1系统框图

2状态机设计

3程序源码

4程序分析


1系统框图

I2C Master控制器主要包含I2C收发数据状态机,SCL时钟分频器、发送移位模块、接收移位模块、空闲控制忙指示模块。SCL和SDA的输出逻辑和时序通过SCL和I2C状态机控制。

重点介绍关键信号:

IO_sda为I2C双向数据总线

O_scl为I2C时钟

I_wr_cnt写数据字节长度,包含了器件地址,发送I_iic_req前,预设该值

I_rd_cnt读数据字节长度,仅包含读回有效部分,发送I_iic_req前,预设该值

I_wr_data写入的数据

O_rd_data读出的数据,如果是读请求,当O_iic_busy从高变低代表数据读回有效

I_iic_req I2C操作请求,根据I_rd_cnt是否大于0决定是否有读请求

I_iic_mode是否支持随机读写,发送I_iic_req前,预设该值

O_iic_busy总线忙

2状态机设计

所有的SDA和SCL控制依据状态机设计。

IDLE:在IDLE状态,当iic_req请求有效代表一次全新的传输,进入I2C START启动传输阶段,如果rd_req有效代表目前要进行repeated start,也是进入START状态。

START:在START状态对bcnt进行初始化,设置需要发送的bit数量,因为不管是写操作,还是随机读操作,I2C总线协议要求,都要发送器件地址。因此在START后发送7Bit的器件地址和1bit的读/写位。

W_WAIT:在此阶段发送一个完整的8bit数据,发送移位模块也会在此阶段对数据移位。

W_ACK:对于写操作,SLAVE设备响应ACK,如果还有数据需要些,则回到W_WAIT;如果还要进行读操作,则回到IDLE产生一次Repeated Start;如果已经完成所有数据发送,也没有数据需要读,则进入STOP1

R_WAIT:在此阶段完成8bits数据接收,接收移位模块工作,之后进入R_ACK

R_ACK:响应NACK,如果还有数据需要接收,则再次进入R_WAIT,否则进入STOP1,完成本次传输。

STOP1:产生停止位,SDA=0 SDA=1,进入STOP1

SOTP2:产生停止位,SDA=1 SDA=1,回到IDLE

3程序源码

/*******************************I2C控制器 MASTER*********************
--以下是米联客设计的I2C总线MASTER控制器
--1.代码简洁,占用极少逻辑资源,代码结构清晰,逻辑设计严谨
--2.使用方便,只需要把发送的数据放入寄存器,就可以自动支持任意长度的写数据
--3.使用方便,只需要把读的数据告知控制器,就可以自动支持任意长度的读数据
--4.1.1版本,升级了O_iic_bus_error标志信号,当总线写操作没有ACK返回,设置O_iic_bus_error,可以通过观察O_iic_bus_error查看总线是否有错误
*********************************************************************/

`timescale 1ns / 1ns //仿真刻度/精度

module uii2c#
(
parameter integer WMEN_LEN = 8'd0,//写长度,以字节为单位,包含器件地址
parameter integer RMEN_LEN = 8'd0,//读长度,以字节为单位,不包含器件地址
parameter integer CLK_DIV  = 16'd499// I2C时钟分频系数
)
(
input  wire I_clk,//系统时钟输入
input  wire I_rstn,//系统复位,低电平有效
output reg  O_iic_scl = 1'b0,//I2C时钟SCL
inout  wire IO_iic_sda,//I2C 数据总线
input  wire [WMEN_LEN*8-1'b1:0]I_wr_data,//写数据寄存器,其中WMEN_LEN设置了最大支持的数据字节数,越大占用的FPGA资源越多
input  wire [7:0]I_wr_cnt,//写数据计数器,代表写了多少个字节
output reg  [RMEN_LEN*8-1'b1:0]O_rd_data = 0,//读数据寄存器,其中RMEN_LEN设置了最大支持的数据字节数,越大占用的FPGA资源越多
input  wire [7:0]I_rd_cnt,//读数据计数器
input  wire I_iic_req,//I_iic_req == 1 使能I2C传输
input  wire I_iic_mode,//I_iic_mode = 1 随机读   I_iic_mode = 0 读当前寄存器或者页读
output reg  O_iic_busy = 1'b0,//I2C控制器忙
output reg  O_iic_bus_error, //I2C总线,无法读到正确ACK出错
output reg  IO_iic_sda_dg
);

localparam IDLE    = 4'd0;//I2C 总线空闲状态
localparam START   = 4'd1;//I2C 总线启动
localparam W_WAIT  = 4'd2;//I2C 总线等待写完成
localparam W_ACK   = 4'd3;//I2C 总线等待写WACK
localparam R_WAIT  = 4'd4;//I2C 总线等待读完成 
localparam R_ACK   = 4'd5;//I2C 总线等待读RACK 
localparam STOP1   = 4'd6;//I2C 总线产生停止位
localparam STOP2   = 4'd7;//I2C 总线产生停止位   

localparam SCL_DIV = CLK_DIV/2;

localparam OFFSET = SCL_DIV - SCL_DIV/4;//设置I2C总线的SCL时钟的偏移,以满足SCL和SDA的时序要求,外部的SCL延迟内部的半周期的四分之三

reg [2:0] IIC_S = 4'd0; //I2C 状态机
//generate  scl
reg [15:0] clkdiv = 16'd0;   //I2C 时钟分频寄存器
reg scl_r      = 1'b1;       //I2C控制器的SCL内部时钟
reg sda_o      = 1'b0;       //I2C控制器的SDA
reg scl_clk    = 1'b0;       //I2C控制器内部SCL时钟,与外部时钟存在OFFSET参数设置的相位偏移
reg [7:0] sda_r = 8'd0;      //发送寄存器
reg [7:0] sda_i_r = 8'd0;    //接收寄存器
reg [7:0] wcnt = 8'd0;       //发送数据计数器,以byte为单位
reg [7:0] rcnt = 8'd0;       //接收数据计数器,以byte为单位
reg [2:0] bcnt = 3'd0;       //bit计数器
reg  rd_req = 1'b0;          //读请求,当判断到需要读数据,内部状态机中设置1
wire sda_i;                  //sda 输入
wire scl_offset;             //scl 时钟偏移控制

//assign  sda_i   = (IO_iic_sda == 1'b0) ?  1'b0 : 1'b1;  //读总线 
//assign  IO_iic_sda = (sda_o == 1'b0) ?  1'b0 : 1'bz;    //写总线,1'bz代表高阻,I2C外部通过上拉电阻,实现总线的高电平

PULLUP PULLUP_inst (.O(iic_sda));

//XILINX I2C 用IOBUF实现控制
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"),  // Low Power - "TRUE", High Performance = "FALSE" 
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst (
.O(sda_i),    // Buffer output
.IO(IO_iic_sda), // Buffer inout port (connect directly to top-level port)
.I(sda_o),    // Buffer input
.T(sda_o)     // 3-state enable input, high=input, low=output
);

//scl 时钟分频器
always@(posedge I_clk)
    if(clkdiv < SCL_DIV)    
        clkdiv <= clkdiv + 1'b1;
    else begin
        clkdiv <= 16'd0; 
        scl_clk <= !scl_clk;
    end

assign  scl_offset  = (clkdiv == OFFSET);//设置scl_offset的时间参数
always @(posedge I_clk) O_iic_scl <=  scl_offset ?  scl_r : O_iic_scl; //O_iic_scl延迟scl_offset时间的scl_r

//采集I2C 数据总线sda
always @(posedge I_clk) IO_iic_sda_dg <= sda_i;  

//当IIC_S状态机处于,同时空闲状态,设置SCL为高电平,同时也是空闲,停止状态,用于产生起始位和停止位时序,否则寄存scl_clk时钟
always @(*) 
    if(IIC_S == IDLE || IIC_S == STOP1 || IIC_S == STOP2)
        scl_r <= 1'b1;
    else 
        scl_r <= scl_clk;
  
//当进入IIC_S状态为启动、停止设置sda=0,结合scl产生起始位,或者(IIC_S == R_ACK && (rcnt != I_rd_cnt) sda=0,用于产生读操作的ACK
always @(*) 
    if(IIC_S == START || IIC_S == STOP1 || (IIC_S == R_ACK && (rcnt != I_rd_cnt)))
        sda_o <= 1'b0;
    else if(IIC_S == W_WAIT)
        sda_o <= sda_r[7]; 
    else  sda_o <= 1'b1; //否则其他状态都为1,当(IIC_S == R_ACK && (rcnt == I_rd_cnt) 产生一个NACK 

//I2C数据发送模块,所有的写数据都通过此模块发送
always @(posedge scl_clk) 
    if(IIC_S == W_ACK || IIC_S == START)begin//IIC_S=START和W_ACK,把需要发送的数据,寄存到sda_r
        sda_r <= I_wr_data[(wcnt*8) +: 8];//寄存需要发发送的数据到sda_r
        if( rd_req ) sda_r <= {I_wr_data[7:1],1'b1};//对于读操作,rd_req由内部代码产生,当写完第一个数据(器件地址),后通过判断I_rd_cnt,确认是否数据需要读
    end
    else if(IIC_S == W_WAIT)//当W_WAT状态,通过移位操作,把数据发送到数据总线
        sda_r <= {sda_r[6:0],1'b1};//移位操作
    else 
        sda_r <= sda_r;

//sda data bus read and hold data to O_rd_data register when IIC_S=R_ACK
//I2C数据接收模块,I2C读期间,把数据通过移位操作,移入O_rd_data
always @(negedge scl_clk)begin
    if(IIC_S == R_WAIT ) //当IIC_S == R_WAIT ||IIC_S == W_ACK(如果读操作,第1个BIT是W_ACK这个状态读)启动移位操作
        sda_i_r <= {sda_i_r[6:0],sda_i};
    else if(IIC_S == R_ACK)//当IIC_S == R_ACK,完成一个BYTE读,把数据保存到O_rd_data
        O_rd_data[((rcnt-1'b1)*8) +: 8] <= sda_i_r[7:0];
    else if(IIC_S == IDLE)//空闲状态,重置sda_i_r
        sda_i_r <= 8'd0;
end

//总线忙状态
always @(posedge scl_clk or negedge I_rstn )begin
   if(I_rstn == 1'b0)
      O_iic_busy <= 1'b0; 
   else begin
      if((I_iic_req == 1'b1 || rd_req == 1'b1 || O_iic_bus_error))//I_iic_req == 1'b1 || rd_req == 1'b1总线进入忙状态
          O_iic_busy <= 1'b1; 
        else if(IIC_S == IDLE)
         O_iic_busy <= 1'b0;
    end     
end

//总线忙状态
always @(negedge scl_clk or negedge I_rstn )begin
   if(I_rstn == 1'b0)
      O_iic_bus_error <= 1'b0;   
   else begin
      if(IIC_S  == W_ACK && sda_i == 1'b1)//I_iic_req == 1'b1 || rd_req == 1'b1总线进入忙状态
          O_iic_bus_error <= 1'b1; 
        else if(I_iic_req == 0)
         O_iic_bus_error <= 1'b0;
    end     
end

//I2C Master控制器状态机
always @(posedge scl_clk or negedge I_rstn )begin
      if(I_rstn == 1'b0)begin //异步复位,复位相关寄存器
           wcnt   <= 8'd0;
           rcnt   <= 8'd0;
           rd_req    <= 1'b0;   
           IIC_S    <= IDLE;
        end
        else begin
        case(IIC_S) //sda = 1 scl =1
        IDLE:begin//在空闲状态,sda=1 scl=1 
           if(I_iic_req == 1'b1 || rd_req == 1'b1) //当I_iic_req == 1'b1代表启动传输 当 rd_req == 1'b1 代表读操作需要产生repeated start 重复启动  
              IIC_S  <= START; //进入START状态
           else begin
              wcnt <= 8'd0; //复位计数器
              rcnt <= 8'd0; //复位计数器
           end
        end
        START:begin //这个状态,前面的代码,先设置sda = 0,scl_offset参数设置了scl_clk时钟的偏移,之后 scl_clk =0 即scl =0 产生起始位或者重复起始位
           bcnt <= 3'd7; //设置bcnt的初值         
           IIC_S  <= W_WAIT;//进入发送等待
        end           
        W_WAIT://等待发送完成,这里发送8bits 数据,写器件地址,写寄存器地址,写数据,都在这个状态完成
        begin
           if(bcnt > 3'd0)//如果8bits没发送完,直到发送完
               bcnt  <= bcnt - 1'b1; //bcnt计数器,每发送1bit减1
           else begin //8bits发送完毕
               wcnt <= wcnt + 1'b1; //wcnt计数器,用于记录已经写了多少字节
               IIC_S  <= W_ACK;//进入W_ACK状态
           end
        end 
        W_ACK://等待WACK,此阶段,也判断是否有读操作
        begin 
           if(wcnt < I_wr_cnt)begin //判断是否所有数据发送(写)完成
              bcnt <= 3'd7; //如果没有写完,重置bcnt
              IIC_S <= W_WAIT;//继续回到W_WAIT等待数据发送(写)完成
           end
           else if(I_rd_cnt > 3'd0)begin//I_rd_cnt > 0代表有数据需要读,I_rd_cnt决定了有多少数据需要读
              if(rd_req == 1'b0 && I_iic_mode == 1'b1)begin //对于第一次写完器件地址,如果I_iic_mode==1代表支持随机读
                  rd_req <= 1'b1;//设置rd_req=1,请求读操作
                  IIC_S <= IDLE; //设置状态进入IDLE,根据rd_req的值会重新产生一次为读操作进行的repeated重复start
              end
              else //如果之前已经完成了repeated重复start,那么读操作进入读数据阶段
                  IIC_S <= R_WAIT;//进入读等待
                  bcnt <= 3'd7;//设置bcnt的初值  
           end
           else //如果所有的发送完成,也没数据需要读,进入停止状态
              IIC_S <= STOP1; 
        end  
        R_WAIT://等待读操作完成
        begin
           rd_req <= 1'b0;//重置读请求rd_req=0
           bcnt  <= bcnt - 1'b1; //bit 计数器
           if(bcnt == 3'd0)begin //当8bits数据读完
              rcnt <= (rcnt < I_rd_cnt) ? (rcnt + 1'b1) : rcnt;//判断是否还有数据需要读
              IIC_S  <= R_ACK;//进入R_ACK
           end
        end
        R_ACK://R_ACK状态产生NACK
        begin
           bcnt <= 3'd7;//重置读请求bcnt计数器
           IIC_S <= (rcnt < I_rd_cnt) ? R_WAIT : STOP1; //如果所有数据读完,进入停止状态
        end  
        STOP1:begin//产生停止位 sda = 0 scl = 1
            rd_req   <= 1'b0;              
            IIC_S <= STOP2;
        end
        STOP2://产生停止位  sda = 1 scl = 1
            IIC_S <= IDLE;          
        default:
            IIC_S <= IDLE;
      endcase
    end
end

endmodule

4程序分析

以简单写1个字节来说明关键的顺序设计。

所有的控制逻辑以IIC_S状态机的状态,以及内部时钟scl_clk为主要时序来控制。写操作内部同步时序全部以scl_clk的上升沿进行,为了满足数据Tsu和Thd,设计scl延迟于scl_r半周期的四分之三 OFFSET = CLK_DIV - CLK_DIV/4。这样对于SLAVE接收来说具有足够的Tsu和Thd

对于读操作,每个scl_sck的下降沿采集总线,由于scl完成了相位调整,也是非常容易满足Tsu和Thd。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pyautogui通过图像获取定位实现计算器自动计算

使用 pyautogui.locateCenterOnScreen 能够在屏幕上搜索给定图像的位置&#xff0c;并准确地返回该图像的中心点坐标。 &#x1f33f;使用 pyautogui 实现计算器自动计算 准备工作&#xff0c;把计算器的按钮截图保存下来。例如&#xff1a; 计算“75”&#xff0c;实现代码如…

【网络】WebSocket协议详解

WebSocket协议详解 一 、WebSocket 诞生背景二、WebSocket 特点三、WebSocket 的握手环节四、WebSokect 的数据格式1、 第一个字节2、第二个字节3、Masking-key4、playload Data5、一些注意细节 WebSocket 的官方文档 WebSocket 的中文文档(非官方) 一 、WebSocket 诞生背景 在…

深度学习基础—简单的卷积神经网络

3.1.卷积层 下面以卷积神经网络的某一层为例&#xff0c;详解一下网络的结构。 假设当前位于l层&#xff0c;则输入6*6*3的彩色图片&#xff0c;有两个3*3*3的过滤器&#xff0c;卷积操作后将输出2个4*4的图片。如果把过滤器看成权重w&#xff0c;卷积这一步操作其实就是w*a&am…

消息称华为纯血鸿蒙部分应用采用虚拟机方案

华为预计在11月发布正式版纯血鸿蒙&#xff0c;为了能够适配更多的App&#xff0c;官方也是有了新的解决方案。报道中提到&#xff0c;纯血鸿蒙设备对有些还没上架的应用会使用虚拟机方案过渡。据悉&#xff0c;华为的虚拟机方案作为过渡措施&#xff0c;首先能确保用户在鸿蒙系…

概率论与编程的联系及数据科学应用

目录 引言 第一章 概率模拟与编程实现 1.1 随机数生成与蒙特卡罗模拟 1.1.2 蒙特卡罗模拟 第二章 统计建模与数据分析 2.1 统计模型实现 2.2 概率图模型 第三章 概率论在机器学习中的应用 3.1 随机森林与决策树 3.2 贝叶斯分类器 总结与展望 引言 在大数据和人工智…

学习node.js 十 redis的基本语法

redis Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据结构存储系统&#xff0c;它提供了一个高效的键值存储解决方案&#xff0c;并支持多种数据结构&#xff0c;如字符串&#xff08;Strings&#xff09;、哈希&#xff08;Hashes&#xff09;、…

素数之和(c语言)

1./描述 //牛牛刚刚学了素数的定义&#xff1a;素数值指在大于1的自然数中&#xff0c;除了1和它本身以外不再有其他因数的自然数 //牛牛想知道在[l, r] 范围内全部素数的和 //输入描述&#xff1a; //输入两个正整数 l&#xff0c;r 表示闭区间范围 //输出描述&#xff1a; //…

sqli-labs靶场通关攻略 46-50

主页有sqli-labs靶场通关攻略 1-45 第四六关 less-46 步骤一&#xff1a;利用报错注入查询库 ?sort1 and updatexml(1,concat(0x7e,database(),0x7e),1) 步骤二&#xff1a;查询表名 ?sort1 and updatexml(1,concat(0x7e,(select group_concat(table_name)from informatio…

如何通过日志或gv$sql_audit,分析OceanBase运行时的异常SQL

本文作者&#xff1a;郑增权&#xff0c;爱可生 DBA 团队成员&#xff0c;OceanBase 和 MySQL 数据库技术爱好者。本文约 2000 字&#xff0c;预计阅读需要 8 分钟。 简介 在 OCP 云平台的 Top SQL 界面中&#xff0c;能观察到异常SQL&#xff0c;但这些SQL并未明确显示具体的…

防泄密的方法都有哪些?

一、防泄密的方法都有哪些&#xff1f;使用安全通讯工具&#xff1a;采用加密通讯工具&#xff0c;确保敏感信息在传输过程中不被窃取或篡改。定期安全审计&#xff1a;对系统和数据进行定期的安全审计和检查&#xff0c;发现潜在的泄密风险并及时处理。文件加密&#xff1a;对…

光伏电站的施工步骤

施工准备&#xff1a;在施工前&#xff0c;需要进行现场勘查&#xff0c;了解施工场地的地形、地貌、气候等情况&#xff0c;制定施工方案和安全措施。同时&#xff0c;还需要准备好施工所需的材料和设备&#xff0c;如光伏组件、支架、电缆、逆变器等 。基础施工&#xff1a;根…

“面试宝典:高频算法题目详解与总结”

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

鸿蒙Harmony开发实战:自定义圆形组件-Canvas

在采用Java配合xml布局编写鸿蒙app页面的时候&#xff0c;发现sdk自带的Image组件并不能将图片设置成圆形&#xff0c;反复了翻阅了官方API手册&#xff08;主要查阅了Compont和Image相关的API&#xff09;&#xff0c;起初发现了一个setCornerRadius方法&#xff0c;于是想着将…

高职院校人工智能训练师边缘计算实训室建设方案

一、引言 随着人工智能技术的飞速发展&#xff0c;边缘计算在提升数据处理效率、降低延迟、保护数据安全等方面展现出巨大潜力。高职院校作为技能型人才培养的重要基地&#xff0c;建设人工智能训练师边缘计算实训室&#xff0c;旨在培养掌握前沿技术、具备实战能力的复合型人才…

pnpm国内源设置

一、背景 在国内使用pnpm时&#xff0c;由于网络问题&#xff0c;经常会遇到速度慢或无法访问的问题。为了提高效率&#xff0c;可以将pnpm的源设置为国内的镜像源。以下是一些常用的国内pnpm镜像源以及如何设置它们的方法。 二、国内可用源 2.1 淘宝pnpm源 https://registry…

神经网络卷积层

一、卷积操作 对应位置相乘相加&#xff0c;最终组成一个新的矩阵&#xff0c;实现了降维。 二、代码 import torch import torchvision from torch import nn from torch.nn import Conv2d from torch.utils.data import DataLoaderdataset torchvision.datasets.CIFAR10(&…

三级_网络技术_54_应用题

一、 请根据下图所示网络结构回答下列问题。 1.填写路由器RG的路由表项。 目的网络/掩码长度输出端口__________S0&#xff08;直接连接&#xff09;__________S1&#xff08;直接连接&#xff09;__________S0__________S1__________S0__________S1 2.如果将10.10.67.128/2…

C++----简单了解vector

大家好&#xff0c;今天我们来讲讲与string相似的向量类型。之所以说他们是相似的原因是他们其中的数据类型有些效果都是一样的。当然大家不能说&#xff0c;既然是差不多的干嘛还有一个这个啊。不如直接用string就可以了。当然世界名言存在即合理。既然我们都能想到的东西&…

Docker 部署 net6 webapi项目

摘要&#xff1a;记录 net6 webapi 项目在 docker 上部署步骤&#xff0c;方便自己后面查看&#xff0c;也方便他人学习。 1. 创建 webapi 项目 点击创建新项目 选择 ASP.NET Core Web API 项目&#xff0c;点击下一步。 给项目命名&#xff0c;然后勾选将解决方案和项目放在同…

C++初学(18)

18.1、读取数字的循环 假设要编写一个将一系列的数字读入到数组中的程序&#xff0c;并允许用户在数组填满之前结束输入。一种方法是利用cin&#xff1a; int n; cin>>n; 如果用户输入的是一个单词&#xff0c;而不是一个数字将会怎么样&#xff1f;可能会发生这些情况…