[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-18 I2C MASTER控制器驱动设计
软件版本:Anlogic -TD5.9.1-DR1_ES1.1
操作系统:WIN10 64bit
硬件平台:适用安路(Anlogic)FPGA
实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板
板卡获取平台:https://milianke.tmall.com/
登录"米联客"FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
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总线忙
1.1 程序设计
1.1.1空闲控制忙指示模块
为了方便使用该驱动程序,和前面学习的驱动一样,IIC master也使用I_iic_req以及O_iic_busy用于信号的握手。用户程序通过设置I_iic_req为高,请求读/写数据;设置O_iic_busy为1,表示IIC总线正忙,这时用户程序需要等待非忙的时候,请求读/写下一个数据。
1 reg scl_clk = 1'b0; //I2C控制器内部SCL时钟,与外部时钟存在OFFSET参数设置的相位偏移 2 reg rd_req = 1'b0; //读请求,当判断到需要读数据,内部状态机中设置1 3 4 //总线忙状态 5 always @(posedge scl_clk or negedge I_rstn )begin 6 if(I_rstn == 1'b0) 7 O_iic_busy <= 1'b0; 8 else begin 9 if((I_iic_req == 1'b1 || rd_req == 1'b1 || O_iic_bus_error))//I_iic_req == 1'b1 || rd_req == 1'b1总线进入忙状态 10 O_iic_busy <= 1'b1; 11 else if(IIC_S == IDLE) 12 O_iic_busy <= 1'b0; 13 end 14 end
1.1.2 SCL时钟分频器:
下面我们以简单写1个字节来说明关键的时序顺序设计。
所有的控制逻辑以IIC_S状态机的状态,以及内部时钟scl_clk为主要时序来控制。写操作内部同步时序全部以scl_clk的上升沿进行,为了满足数据Tsu和Thd,设O_iic_scl延迟于scl_r半周期的四分之三 OFFSET = CLK_DIV - CLK_DIV/4。这样对于SLAVE接收来说具有足够的Tsu和Thd。对于读操作,每个scl_sck的下降沿采集总线,由于scl完成了相位调整,也是非常容易满足Tsu和Thd。
1 localparam SCL_DIV = CLK_DIV/2; 2 localparam OFFSET = SCL_DIV - SCL_DIV/4;//设置I2C总线的SCL时钟的偏移,以满足SCL和SDA的时序要求,外部的SCL延迟内部的半周期的四分之三 3 4 reg [15:0] clkdiv = 16'd0; //I2C 时钟分频寄存器 5 reg scl_r = 1'b1; //I2C控制器的SCL内部时钟 6 reg scl_clk = 1'b0; //I2C控制器内部SCL时钟,与外部时钟存在OFFSET参数设置的相位偏移 7 8 wire scl_offset; //scl 时钟偏移控制 9 10 //scl 时钟分频器 11 always@(posedge I_clk) 12 if(clkdiv < SCL_DIV) 13 clkdiv <= clkdiv + 1'b1; 14 else begin 15 clkdiv <= 16'd0; 16 scl_clk <= !scl_clk; 17 end 18 19 assign scl_offset = (clkdiv == OFFSET);//设置scl_offset的时间参数 20 21 always @(posedge I_clk) O_iic_scl <= scl_offset ? scl_r : O_iic_scl; //O_iic_scl延迟scl_offset时间的scl_r
1.1.3开始条件/停止条件
SDA:总线空闲状态sda_o保持高电平,sda_o拉低,代表启动传输。
1 //当进入IIC_S状态为启动、停止设置sda=0,结合scl产生起始位,或者(IIC_S == R_ACK && (rcnt != I_rd_cnt) sda=0,用于产生读操作的ACK 2 always @(*) 3 if(IIC_S == START || IIC_S == STOP1 || (IIC_S == R_ACK && (rcnt != I_rd_cnt))) 4 sda_o <= 1'b0; 5 else if(IIC_S == W_WAIT) 6 sda_o <= sda_r[7]; 7 else sda_o <= 1'b1; //否则其他状态都为1,当(IIC_S == R_ACK && (rcnt == I_rd_cnt) 产生一个NACK
SCL:设置scl_r开始和结束的条件为高电平,数据传输过程scl_r为内部时钟scl_clk
1 //当IIC_S状态机处于,同时空闲状态,设置SCL为高电平,同时也是空闲,停止状态,用于产生起始位和停止位时序,否则寄存scl_clk时钟 2 always @(*) 3 if(IIC_S == IDLE || IIC_S == STOP1 || IIC_S == STOP2) 4 scl_r <= 1'b1; 5 else 6 scl_r <= scl_clk;
1.1.4发送移位模块
当总线进行写操作时,将I_wr_data的数据通过发送移位模块传送到IIC总线sda_r上。
1 localparam START = 4'd1;//I2C 总线启动 2 localparam W_WAIT = 4'd2;//I2C 总线等待写完成 3 localparam W_ACK = 4'd3;//I2C 总线等待写WACK 4 5 6 reg [2:0] IIC_S = 4'd0; //I2C 状态机 7 reg [7:0] wcnt = 8'd0; //发送数据计数器,以byte为单位 8 9 //I2C数据发送模块,所有的写数据都通过此模块发送 10 always @(posedge scl_clk) 11 if(IIC_S == W_ACK || IIC_S == START)begin//IIC_S=START和W_ACK,把需要发送的数据,寄存到sda_r 12 sda_r <= I_wr_data[(wcnt*8) +: 8];//寄存需要发发送的数据到sda_r 13 if( rd_req ) sda_r <= {I_wr_data[7:1],1'b1};//对于读操作,rd_req由内部代码产生,当写完第一个数据(器件地址),后通过判断I_rd_cnt,确认是否数据需要读 14 end 15 else if(IIC_S == W_WAIT)//当W_WAT状态,通过移位操作,把数据发送到数据总线 16 sda_r <= {sda_r[6:0],1'b1};//移位操作 17 else 18 sda_r <= sda_r;
1.1.5接收移位模块
当总线进行读操作时,将IIC总线上的数据sda_i_r通过接收移位模块传送到O_rd_data上。
1 localparam IDLE = 4'd0;//I2C 总线空闲状态 2 localparam R_WAIT = 4'd4;//I2C 总线等待读完成 3 localparam R_ACK = 4'd5;//I2C 总线等待读RACK 4 5 reg [7:0] sda_i_r = 8'd0; //接收寄存器 6 reg [7:0] rcnt = 8'd0; //接收数据计数器,以byte为单位 7 8 //I2C数据接收模块,I2C读期间,把数据通过移位操作,移入O_rd_data 9 always @(negedge scl_clk)begin 10 if(IIC_S == R_WAIT ) //当IIC_S == R_WAIT ||IIC_S == W_ACK(如果读操作,第1个BIT是W_ACK这个状态读)启动移位操作 11 sda_i_r <= {sda_i_r[6:0],sda_i}; 12 else if(IIC_S == R_ACK)//当IIC_S == R_ACK,完成一个BYTE读,把数据保存到O_rd_data 13 O_rd_data[((rcnt-1'b1)*8) +: 8] <= sda_i_r[7:0]; 14 else if(IIC_S == IDLE)//空闲状态,重置sda_i_r 15 sda_i_r <= 8'd0; 16 end
1.1.6总线错误指示模块
进行写操作时,I2C总线无法读到正确ACK,拉高O_iic_bus_error
1 //总线错误指示状态 2 always @(negedge scl_clk or negedge I_rstn )begin 3 if(I_rstn == 1'b0) 4 O_iic_bus_error <= 1'b0; 5 else begin 6 if(IIC_S == W_ACK && sda_i == 1'b1)//I_iic_req == 1'b1 || rd_req == 1'b1总线进入错误状态 7 O_iic_bus_error <= 1'b1; 8 else if(I_iic_req == 0) 9 O_iic_bus_error <= 1'b0; 10 end 11 end
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程序源码
1 `timescale 1ns / 1ns //仿真刻度/精度 2 3 module uii2c# 4 ( 5 parameter integer WMEN_LEN = 8'd0,//写长度,以字节为单位,包含器件地址 6 parameter integer RMEN_LEN = 8'd0,//读长度,以字节为单位,不包含器件地址 7 parameter integer CLK_DIV = 16'd499// I2C时钟分频系数 8 ) 9 ( 10 input wire I_clk,//系统时钟输入 11 input wire I_rstn,//系统复位,低电平有效 12 output reg O_iic_scl = 1'b0,//I2C时钟SCL 13 inout wire IO_iic_sda,//I2C 数据总线 14 input wire [WMEN_LEN*8-1'b1:0]I_wr_data,//写数据寄存器,其中WMEN_LEN设置了最大支持的数据字节数,越大占用的FPGA资源越多 15 input wire [7:0]I_wr_cnt,//写数据计数器,代表写了多少个字节 16 output reg [RMEN_LEN*8-1'b1:0]O_rd_data = 0,//读数据寄存器,其中RMEN_LEN设置了最大支持的数据字节数,越大占用的FPGA资源越多 17 input wire [7:0]I_rd_cnt,//读数据计数器 18 input wire I_iic_req,//I_iic_req == 1 使能I2C传输 19 input wire I_iic_mode,//I_iic_mode = 1 随机读 I_iic_mode = 0 读当前寄存器或者页读 20 output reg O_iic_busy = 1'b0,//I2C控制器忙 21 output reg O_iic_bus_error, //I2C总线,无法读到正确ACK出错 22 output reg IO_iic_sda_dg 23 ); 24 25 localparam IDLE = 4'd0;//I2C 总线空闲状态 26 localparam START = 4'd1;//I2C 总线启动 27 localparam W_WAIT = 4'd2;//I2C 总线等待写完成 28 localparam W_ACK = 4'd3;//I2C 总线等待写WACK 29 localparam R_WAIT = 4'd4;//I2C 总线等待读完成 30 localparam R_ACK = 4'd5;//I2C 总线等待读RACK 31 localparam STOP1 = 4'd6;//I2C 总线产生停止位 32 localparam STOP2 = 4'd7;//I2C 总线产生停止位 33 34 localparam SCL_DIV = CLK_DIV/2; 35 36 localparam OFFSET = SCL_DIV - SCL_DIV/4;//设置I2C总线的SCL时钟的偏移,以满足SCL和SDA的时序要求,外部的SCL延迟内部的半周期的四分之三 37 38 reg [2:0] IIC_S = 4'd0; //I2C 状态机 39 //generate scl 40 reg [15:0] clkdiv = 16'd0; //I2C 时钟分频寄存器 41 reg scl_r = 1'b1; //I2C控制器的SCL内部时钟 42 reg sda_o = 1'b0; //I2C控制器的SDA 43 reg scl_clk = 1'b0; //I2C控制器内部SCL时钟,与外部时钟存在OFFSET参数设置的相位偏移 44 reg [7:0] sda_r = 8'd0; //发送寄存器 45 reg [7:0] sda_i_r = 8'd0; //接收寄存器 46 reg [7:0] wcnt = 8'd0; //发送数据计数器,以byte为单位 47 reg [7:0] rcnt = 8'd0; //接收数据计数器,以byte为单位 48 reg [2:0] bcnt = 3'd0; //bit计数器 49 reg rd_req = 1'b0; //读请求,当判断到需要读数据,内部状态机中设置1 50 wire sda_i; //sda 输入 51 wire scl_offset; //scl 时钟偏移控制 52 53 assign sda_i = (IO_iic_sda == 1'b0) ? 1'b0 : 1'b1; //读总线 54 assign IO_iic_sda = (sda_o == 1'b0) ? 1'b0 : 1'bz; //写总线,1'bz代表高阻,I2C外部通过上拉电阻,实现总线的高电平 55 56 //scl 时钟分频器 57 always@(posedge I_clk) 58 if(clkdiv < SCL_DIV) 59 clkdiv <= clkdiv + 1'b1; 60 else begin 61 clkdiv <= 16'd0; 62 scl_clk <= !scl_clk; 63 end 64 65 assign scl_offset = (clkdiv == OFFSET);//设置scl_offset的时间参数 66 always @(posedge I_clk) O_iic_scl <= scl_offset ? scl_r : O_iic_scl; //O_iic_scl延迟scl_offset时间的scl_r 67 68 //采集I2C 数据总线sda 69 always @(posedge I_clk) IO_iic_sda_dg <= sda_i; 70 71 //当IIC_S状态机处于,同时空闲状态,设置SCL为高电平,同时也是空闲,停止状态,用于产生起始位和停止位时序,否则寄存scl_clk时钟 72 always @(*) 73 if(IIC_S == IDLE || IIC_S == STOP1 || IIC_S == STOP2) 74 scl_r <= 1'b1; 75 else 76 scl_r <= scl_clk; 77 78 79 //当进入IIC_S状态为启动、停止设置sda=0,结合scl产生起始位,或者(IIC_S == R_ACK && (rcnt != I_rd_cnt) sda=0,用于产生读操作的ACK 80 always @(*) 81 if(IIC_S == START || IIC_S == STOP1 || (IIC_S == R_ACK && (rcnt != I_rd_cnt))) 82 sda_o <= 1'b0; 83 else if(IIC_S == W_WAIT) 84 sda_o <= sda_r[7]; 85 else sda_o <= 1'b1; //否则其他状态都为1,当(IIC_S == R_ACK && (rcnt == I_rd_cnt) 产生一个NACK 86 87 //I2C数据发送模块,所有的写数据都通过此模块发送 88 always @(posedge scl_clk) 89 if(IIC_S == W_ACK || IIC_S == START)begin//IIC_S=START和W_ACK,把需要发送的数据,寄存到sda_r 90 sda_r <= I_wr_data[(wcnt*8) +: 8];//寄存需要发发送的数据到sda_r 91 if( rd_req ) sda_r <= {I_wr_data[7:1],1'b1};//对于读操作,rd_req由内部代码产生,当写完第一个数据(器件地址),后通过判断I_rd_cnt,确认是否数据需要读 92 end 93 else if(IIC_S == W_WAIT)//当W_WAT状态,通过移位操作,把数据发送到数据总线 94 sda_r <= {sda_r[6:0],1'b1};//移位操作 95 else 96 sda_r <= sda_r; 97 98 //sda data bus read and hold data to O_rd_data register when IIC_S=R_ACK 99 //I2C数据接收模块,I2C读期间,把数据通过移位操作,移入O_rd_data 100 always @(negedge scl_clk)begin 101 if(IIC_S == R_WAIT ) //当IIC_S == R_WAIT ||IIC_S == W_ACK(如果读操作,第1个BIT是W_ACK这个状态读)启动移位操作 102 sda_i_r <= {sda_i_r[6:0],sda_i}; 103 else if(IIC_S == R_ACK)//当IIC_S == R_ACK,完成一个BYTE读,把数据保存到O_rd_data 104 O_rd_data[((rcnt-1'b1)*8) +: 8] <= sda_i_r[7:0]; 105 else if(IIC_S == IDLE)//空闲状态,重置sda_i_r 106 sda_i_r <= 8'd0; 107 end 108 109 //总线忙状态 110 always @(posedge scl_clk or negedge I_rstn )begin 111 if(I_rstn == 1'b0) 112 O_iic_busy <= 1'b0; 113 else begin 114 if((I_iic_req == 1'b1 || rd_req == 1'b1 || O_iic_bus_error))//I_iic_req == 1'b1 || rd_req == 1'b1总线进入忙状态 115 O_iic_busy <= 1'b1; 116 else if(IIC_S == IDLE) 117 O_iic_busy <= 1'b0; 118 end 119 end 120 121 //总线忙状态 122 always @(negedge scl_clk or negedge I_rstn )begin 123 if(I_rstn == 1'b0) 124 O_iic_bus_error <= 1'b0; 125 else begin 126 if(IIC_S == W_ACK && sda_i == 1'b1)//I_iic_req == 1'b1 || rd_req == 1'b1总线进入忙状态 127 O_iic_bus_error <= 1'b1; 128 else if(I_iic_req == 0) 129 O_iic_bus_error <= 1'b0; 130 end 131 end 132 133 //I2C Master控制器状态机 134 always @(posedge scl_clk or negedge I_rstn )begin 135 if(I_rstn == 1'b0)begin //异步复位,复位相关寄存器 136 wcnt <= 8'd0; 137 rcnt <= 8'd0; 138 rd_req <= 1'b0; 139 IIC_S <= IDLE; 140 end 141 else begin 142 case(IIC_S) //sda = 1 scl =1 143 IDLE:begin//在空闲状态,sda=1 scl=1 144 if(I_iic_req == 1'b1 || rd_req == 1'b1) //当I_iic_req == 1'b1代表启动传输 当 rd_req == 1'b1 代表读操作需要产生repeated start 重复启动 145 IIC_S <= START; //进入START状态 146 else begin 147 wcnt <= 8'd0; //复位计数器 148 rcnt <= 8'd0; //复位计数器 149 end 150 end 151 START:begin //这个状态,前面的代码,先设置sda = 0,scl_offset参数设置了scl_clk时钟的偏移,之后 scl_clk =0 即scl =0 产生起始位或者重复起始位 152 bcnt <= 3'd7; //设置bcnt的初值 153 IIC_S <= W_WAIT;//进入发送等待 154 end 155 W_WAIT://等待发送完成,这里发送8bits 数据,写器件地址,写寄存器地址,写数据,都在这个状态完成 156 begin 157 if(bcnt > 3'd0)//如果8bits没发送完,直到发送完 158 bcnt <= bcnt - 1'b1; //bcnt计数器,每发送1bit减1 159 else begin //8bits发送完毕 160 wcnt <= wcnt + 1'b1; //wcnt计数器,用于记录已经写了多少字节 161 IIC_S <= W_ACK;//进入W_ACK状态 162 end 163 end 164 W_ACK://等待WACK,此阶段,也判断是否有读操作 165 begin 166 if(wcnt < I_wr_cnt)begin //判断是否所有数据发送(写)完成 167 bcnt <= 3'd7; //如果没有写完,重置bcnt 168 IIC_S <= W_WAIT;//继续回到W_WAIT等待数据发送(写)完成 169 end 170 else if(I_rd_cnt > 3'd0)begin//I_rd_cnt > 0代表有数据需要读,I_rd_cnt决定了有多少数据需要读 171 if(rd_req == 1'b0 && I_iic_mode == 1'b1)begin //对于第一次写完器件地址,如果I_iic_mode==1代表支持随机读 172 rd_req <= 1'b1;//设置rd_req=1,请求读操作 173 IIC_S <= IDLE; //设置状态进入IDLE,根据rd_req的值会重新产生一次为读操作进行的repeated重复start 174 end 175 else //如果之前已经完成了repeated重复start,那么读操作进入读数据阶段 176 IIC_S <= R_WAIT;//进入读等待 177 bcnt <= 3'd7;//设置bcnt的初值 178 end 179 else //如果所有的发送完成,也没数据需要读,进入停止状态 180 IIC_S <= STOP1; 181 end 182 R_WAIT://等待读操作完成 183 begin 184 rd_req <= 1'b0;//重置读请求rd_req=0 185 bcnt <= bcnt - 1'b1; //bit 计数器 186 if(bcnt == 3'd0)begin //当8bits数据读完 187 rcnt <= (rcnt < I_rd_cnt) ? (rcnt + 1'b1) : rcnt;//判断是否还有数据需要读 188 IIC_S <= R_ACK;//进入R_ACK 189 end 190 end 191 R_ACK://R_ACK状态产生NACK 192 begin 193 bcnt <= 3'd7;//重置读请求bcnt计数器 194 IIC_S <= (rcnt < I_rd_cnt) ? R_WAIT : STOP1; //如果所有数据读完,进入停止状态 195 end 196 STOP1:begin//产生停止位 sda = 0 scl = 1 197 rd_req <= 1'b0; 198 IIC_S <= STOP2; 199 end 200 STOP2://产生停止位 sda = 1 scl = 1 201 IIC_S <= IDLE; 202 default: 203 IIC_S <= IDLE; 204 endcase 205 end 206 end 207 208 endmodule
本文来米联客(milianke),作者:米联客(milianke),转载请注明原文链接:https://www.cnblogs.com/milianke/p/18330437