前言:之前申请了ADI公司的一款ADV7181CBSTZ的视频解码芯片,正好原装DE2板子安的是同系列的ADV7181BBSTZ。虽然都是ADV7181的宗出,但是寄存器配置等等还是有些诧异,引脚也不兼容。而且ADI已经停产了ADV7181B,现在主推C系列的。关于7181不同系列配置和操作问题可以在http://ez.analog.com社区,ADI公司的工程师都会直接帮你回答,我搜索了一部分Q&A,里面的问题基本都能得到不错解决。ps.其实从工程师解答的方式,对比一下自己遇到问题首先该怎么思考解决方法,这一点是可以学到很多知识。

  设计思路:废话多说了,I2C总线协议主要用来给模拟和数字芯片配置内能寄存器的主要方式,因为用ADI的芯片经历比较多,他们的芯片主要是I2C和SPI配置。I2C总线标准细节请参照《i2c总线协议(中文版pdf)》。以下设计的I2C模型肯定没有协议里的功能那么完备,这里主要是考虑到I2C仅仅用做配置寄存器这一点点的通信量,不像早前E2PROM还能涉及到I2C块页读写操作,状态机可以简单到用一个计数器就可以控制完成,没有那么多条件转移。

  所以类似这种应用场合下,I2C、SPI协议可以简单到和UART一样。那么就涉及到状态的颗粒程度了,参考了一篇opencores上发布LGPL开源的I2C控制器,其功能复杂程度不是一般人能写出来的,该控制器将手册读写SDA和SCLK的上升时间、保持时间参数化,细化了一个比特的电平变化。网络上还有一种思路是颗粒化成一比特四个状态(上升沿、高电平、下降沿、低电平)。我选择了后者,设计模块功能够用就行。具体写操作代码比较简单最后附出。

  难点和技巧:

  (a)SDA三态口设计。由于I2C总线里SDA数据线是双向线,多个I2C设备的SDA是想“与”的逻辑,因此在不是拥有控制权的时候应该释放且高阻输出。以前用51单片机配置SA7113时用C写的I2C里面是没有办法输出高阻的,因此在等待从机ACK时候,主机在这个时刻是置SDA为1的。提这一点避免让一些人误会设置高祖就是置1。多建议inout的使用尽量用在最顶层模块,避免综合出错,或者在STP2使用时候出错。我见过Terasic的sdram控制器就是这么做的。

  (b)读操作。注意读操作需要两个START信号。否则读寄存器收不到ACK信号,然后误以为写操作有问题。

  (c)关于ACK和NACK。这里站在主机角度,每次读写都应该等待ACK,这一点肯定是没错的。其实NACK是主机在结束最后一个比特之后结束信号之间由主机产生NACK。关于这一点的解释,这里贴出ADI工程师的回答:”In Read Mode, the highest subaddress register contents continue to be output until the master device issues a no-acknowledge. This indicates the end of a read. A no-acknowledge condition is where the SDA line is not pulled low on the ninth pulse.I think this is what you are referring to.  A master NACK during the read back byte stops the read back.  In absolute I2C standards probably not but for this part it's ok since you can only access one register at a time and the subaddress register does not auto-increment so doing another byte read will just return the same byte you just read.  A stop essentially kills the transaction anyways.  This is an older device.“

  (d)个人习惯。在每个操作结束后产生一个脉冲宽度的模块ack信号(非I2C的ACK),这样做可以将该信号反馈激励该模块,提高模块使用效率。对于读写请求的处理,个人喜欢采信号的边沿变化,因为对于一些前个模块给的是脉冲信号就不一定可以保持到完整激励状态机转移时候的电平宽度。另外:配合这模块ack和读写请求就能完美提高模块效率。

  写操作源码1:

 `timescale  ns /  ps
`define SIM
`define SYS_CLK
`define I2C_CLK
`define I2C_DIV `SYS_CLK/`I2C_CLK
`define ADV7180
`define SCLK_CNT_WIDTH
module i2c_controller(
sys_clk,
sys_rst_n,
sys_wreq_i,
reg_addr_i,
sys_data_i,
i2c_idle_o,
i2c_ack_o,
i2c_sclk,
i2c_sdat
);
input sys_clk;
input sys_rst_n;
input sys_wreq_i;
input [:] reg_addr_i; //从机寄存器地址
input [:] sys_data_i; //待写的数据
output i2c_idle_o; //模块空闲
output i2c_ack_o; //非I2C的ACK,模块的ack
output i2c_sclk;
inout i2c_sdat;
`ifdef ADV7180
parameter DEVICE_READ = 'h40; //器件读操作地址
parameter DEVICE_WRITE = 'h41; //器件写操作地址
`endif `ifdef SIM
parameter ST_WIDTH = ;
parameter IDLE = "IDLE...",
START1 = "START1.",
SET_SLAVE = "SET_SLA",
ACK1 = "ACK1...",
SET_REG = "SET_REG",
ACK2 = "ACK2...",
WR_DATA = "WR_DATA",
ACK3 = "ACK3...",
STOP = "STOP..."; `else
`define FSM
parameter ST_WIDTH = ;
parameter IDLE = `FSM'b0_0000_0001,
START1 = `FSM'b0_0000_0010, //写操作一共有1个start,读操作一共2个start
SET_SLAVE = `FSM'b0_0000_0100,
ACK1 = `FSM'b0_0000_1000,
SET_REG = `FSM'b0_0001_0000,
ACK2 = `FSM'b0_0010_0000,
WR_DATA = `FSM'b0_0100_0000,
ACK3 = `FSM'b0_1000_0000,
STOP = `FSM'b1_0000_0000;
`endif //GENERATE SCLK_R
reg [`SCLK_CNT_WIDTH-:] sclk_cnt = ;
always @ (posedge sys_clk) begin
if('b0 == sys_rst_n) sclk_cnt <= 0;
else if(sclk_cnt < `I2C_DIV-) sclk_cnt <= sclk_cnt + 'd1;
else sclk_cnt <= ;
end `define SCLK_POS (sclk_cnt == `SCLK_CNT_WIDTH'd499)
`define SCLK_HIGH (sclk_cnt == `SCLK_CNT_WIDTH'd124)
`define SCLK_NEG (sclk_cnt == `SCLK_CNT_WIDTH'd249)
`define SCLK_LOW (sclk_cnt == `SCLK_CNT_WIDTH'd374) assign i2c_sclk = (sclk_cnt <= `SCLK_CNT_WIDTH'd249)?1'b1:'b0; //caputre the posedge of sys_wreq_i;
reg sys_wreq_r0 = ;
always @ (posedge sys_clk) begin
if(sys_rst_n == 'b0) sys_wreq_r0 <= 0;
else sys_wreq_r0 <= sys_wreq_i;
end
wire do_wreq = sys_wreq_i & ~sys_wreq_r0;
//generate the wr_start;
reg wr_start = ;
always @ (posedge sys_clk) begin
if(sys_rst_n == 'b0) wr_start <= 0;
else if(i2c_ack_o == 'b1) wr_start <= 0;
else if(do_wreq) wr_start <= ;
else wr_start <= wr_start;
end
//FSM
reg [:] data2slave = ;
reg sdat_r = ;
reg link = ; //控制三态口读写方向,默认为读方向0,写时为1
reg [:] bit_cnt = 'd0;
reg [ST_WIDTH-:] c_st = IDLE;
reg [ST_WIDTH-:] n_st = IDLE;
//FSM-1
always @ (posedge sys_clk) begin
if('b0 == sys_rst_n) c_st <= IDLE;
else c_st <= n_st;
end
//fsm-2
//实际的状态转移中ack[2:0]比物理等待的ack少四分之一
always @ (*) begin
n_st = IDLE;
case(c_st)
IDLE:begin
n_st = ((wr_start == 'b1)&&(`SCLK_HIGH))?START1:IDLE;end
START1:begin
n_st = (`SCLK_LOW)?SET_SLAVE:START1;end //sclk为高电平中心时转移
SET_SLAVE:begin
n_st = ((`SCLK_LOW)&&(bit_cnt == 'd8))?ACK1:SET_SLAVE;end//数据在低电平是更新
ACK1:begin
n_st = (`SCLK_NEG)?SET_REG:ACK1;end//为保证下一步设置寄存器,提前1/4进入下一个状态
SET_REG:begin
n_st = ((`SCLK_LOW)&&(bit_cnt == 'd8))?ACK2:SET_REG;end//数据在低电平是更新
ACK2:begin
n_st = (`SCLK_NEG)?WR_DATA:ACK2;end//为保证下一步设置寄存器,提前1/4进入下一个状态
WR_DATA:begin
n_st = ((`SCLK_LOW)&&(bit_cnt == 'd8))?ACK3:WR_DATA;end
ACK3:begin
n_st = (`SCLK_NEG)?STOP:ACK3;end
STOP:begin
n_st = (`SCLK_NEG)?IDLE:STOP;end
default:begin
n_st = IDLE;end
endcase
end
//FSM-3
always @ (posedge sys_clk) begin
if(sys_rst_n == 'b0) begin
link <= 'd1;
data2slave <= 'd0;
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
else begin
case(c_st)
IDLE:begin
link <= 'd1;
data2slave <= DEVICE_WRITE;
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
START1:begin
link <= 'd1;
bit_cnt <= 'd1;
data2slave <= (`SCLK_LOW)?data2slave<<:data2slave;
sdat_r <= (`SCLK_LOW)?data2slave[]:'d0; //pull down,由于data2slave缓存一级的缘故,需要提前在START里输出第MSB位
end
SET_SLAVE:begin
if(`SCLK_LOW) begin
link <= (bit_cnt == 'd8)?1'b0:'b1; //释放数据总线
bit_cnt <= (bit_cnt == 'd8)?4'd0:bit_cnt+'d1;
data2slave <= {data2slave[:],'d0};//左移一位
sdat_r <= (bit_cnt == 'd8)?1'd1:data2slave[];end
else begin
link <= link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= sdat_r;end
end
ACK1:begin
link <= 'd0;
data2slave <= (`SCLK_POS)?reg_addr_i:data2slave; //读入待写的寄存器地址
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
SET_REG:begin
if(`SCLK_LOW) begin
link <= (bit_cnt == 'd8)?1'b0:'b1; //释放数据总线
bit_cnt <= (bit_cnt == 'd8)?4'd0:bit_cnt+'d1;
data2slave <= {data2slave[:],'d0};//左移一位
sdat_r <= (bit_cnt == 'd8)?1'd1:data2slave[];end
else begin
link <= link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= sdat_r;end
end
ACK2:begin
link <= 'd0;
data2slave <= (`SCLK_POS)?sys_data_i:data2slave; //读入待写的寄存器地址
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
WR_DATA:begin
if(`SCLK_LOW) begin
link <= (bit_cnt == 'd8)?1'b0:'b1; //释放数据总线
bit_cnt <= (bit_cnt == 'd8)?4'd0:bit_cnt+'d1;
data2slave <= {data2slave[:],'d0};//左移一位
sdat_r <= data2slave[];end
else begin
link <= link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= sdat_r;end
end
ACK3:begin
link <= 'd0;
sdat_r <= 'd0;//预先拉低
bit_cnt <= bit_cnt;
data2slave <= data2slave;end
STOP:begin
link <= (`SCLK_LOW)?'b1:link;
bit_cnt <= bit_cnt;
data2slave <= data2slave;
sdat_r <= (`SCLK_HIGH)?'b1:sdat_r;end
default:begin
link <= 'd1;
data2slave <= 'd0;
bit_cnt <= 'd0;
sdat_r <= 'd1;
end
endcase
end
end
//assign
assign i2c_sdat = (link == 'b1)?sdat_r:8'hzz;
assign i2c_idle_o = (c_st == IDLE)?'b1:1'b0;
assign i2c_ack_o = ((c_st == STOP)&&(`SCLK_NEG))?'b1:1'b0; endmodule

写操作源码2:

`timescale  ns /  ps
`define LUT_WIDTH
module adv7180_config(
sys_clk,
sys_rst_n,
i2c_ack_i,
sys_wreq_o,
sys_data_o,
reg_addr_o,
config_done_o
);
input sys_clk;
input sys_rst_n;
input i2c_ack_i;
output sys_wreq_o;
output [:] sys_data_o;
output [:] reg_addr_o;
output config_done_o; //generate wreq_o
reg sys_wreq_o = ;
reg [`LUT_WIDTH-:] lut_index = ;
reg [:] lut_data = ;
always @ (posedge sys_clk) begin
if('b0 == sys_rst_n) begin
sys_wreq_o <= ;
lut_index <= ;end
else if((i2c_ack_i == 'b1)&&(config_done_o == 1'b0)) begin
sys_wreq_o <= ;
lut_index <= lut_index + 'd1;end
else begin
sys_wreq_o <= ;
lut_index <= lut_index;end
end
//assign
assign config_done_o = (lut_index == `LUT_WIDTH'd15)?1'b1:'b0;
assign sys_data_o = lut_data[:];
assign reg_addr_o = lut_data[:];
//lut
always @ (*) begin
case(lut_index)
`LUT_WIDTH'd0:lut_data <= 16'h2330;
`LUT_WIDTH'd1:lut_data <= 16'h4161;
`LUT_WIDTH'd2:lut_data <= 16'hf22a;
`LUT_WIDTH'd3:lut_data <= 16'ha344;
`LUT_WIDTH'd4:lut_data <= 16'h4353;
`LUT_WIDTH'd5:lut_data <= 16'h1325;
`LUT_WIDTH'd6:lut_data <= 16'h6546;
`LUT_WIDTH'd7:lut_data <= 16'h7657;
`LUT_WIDTH'd8:lut_data <= 16'h8565;
`LUT_WIDTH'd9:lut_data <= 16'h9357;
`LUT_WIDTH'd10:lut_data <= 16'h1450;
`LUT_WIDTH'd11:lut_data <= 16'h1311;
`LUT_WIDTH'd12:lut_data <= 16'h1542;
`LUT_WIDTH'd13:lut_data <= 16'h1133;
`LUT_WIDTH'd14:lut_data <= 16'h1134;
`LUT_WIDTH'd15:lut_data <= 16'h1965;
endcase
end endmodule

仿真写操作源码3:

 `timescale  ns /  ps
module tb_i2c();
reg sys_clk;
reg sys_rst_n;
initial begin
sys_clk = ;
sys_rst_n = ;
# sys_rst_n = ;
end always begin
# sys_clk=~sys_clk;end wire i2c_sclk;
wire sys_wreq;
wire [:] reg_addr;
wire [:] sys_data;
wire i2c_idle;
wire i2c_ack;
wire i2c_sdat;
i2c_controller u0(
.sys_clk( sys_clk ),
.sys_rst_n( sys_rst_n ),
.sys_wreq_i( sys_wreq ),
.reg_addr_i( reg_addr ),
.sys_data_i( sys_data ),
.i2c_idle_o( i2c_idle ),
.i2c_ack_o( i2c_ack ),
.i2c_sclk( i2c_sclk),
.i2c_sdat( i2c_sdat )
); wire config_done;
adv7180_config u1(
.sys_clk( sys_clk ),
.sys_rst_n( sys_rst_n ),
.i2c_ack_i( i2c_ack ),
.sys_wreq_o( sys_wreq ),
.sys_data_o( sys_data ),
.reg_addr_o( reg_addr ),
.config_done_o( config_done )
); endmodule

I2C控制器的Verilog建模之一的更多相关文章

  1. I2C控制器的Verilog建模之三(完结版)

    前言:终于到了测试篇,不过悲剧了一下.按照之前<二>里面的思路,在顶层用一个复用器驱动读写独立模块的I2C总线确实失败.虽然综合过去了,不过警告里已经说明:底层的2个原本是inout三态口 ...

  2. I2C控制器的Verilog建模之二

    前言:接着上一篇的I2C写操作,今天要实现一个I2C的读操作.虽然在ADV7181B配置内部寄存器时没有必要使用到读操作,但是为了进一步确认寄存器是否在I2C写模块下被正确配置,这一步是必不可少的. ...

  3. Norflash控制器的Verilog建模之二(仿真)

    前言:经过几天修改,norflash控制器基本已经完成,通过仿真.完整的norflash包含2个模块:直接操作硬件的norflash_ctrl.v与控制ctrl模块的驱动norflash_driver ...

  4. Norflash控制器的Verilog建模之一

    摘要:今天驱动一款SPANSION公司生产的norflash——S29AL032D70,没有别的参考资料,大致了解一下norflash的内部cmos电路架构以及其用途之后,直接看手册吧. 如何看手册: ...

  5. SDRAM控制器的Verilog建模之一

    前言:作为经典存储器的三剑客中的flash和sram已经建模测试过了,虽然现在都已经ddr2,ddr3,667MHZ.1333MHZ的天下了,但是接下这周来准备写一下sdram的controller. ...

  6. 异步SRAM控制器的Verilog建模

    前言:sram顾名思义静态随机存储器,分为asram异步型和ssram同步型.这里驱动DE2上一块ISSI公司的512KB的asram. 设计思路:因为实际应用中单字节读写效率不高,所以本设计中仿照s ...

  7. Norflash控制器的Verilog建模之三(測試)

    前言:回校了,辦好手續就著手寫測試篇.初步的norflash控制器已經完成,通過硬件測試.目前的norflash完成扇区块擦除.单字节写.单字节读3个功能.博文最后附上源码. 总结:和之前的博文一样, ...

  8. VGA逐行扫描控制器的Verilog建模

    前言:因为VGA是一种模拟图像传输数据接口,所要将数字信号用DAC转换成模拟量.本文用的一款ADI公司高精度的视频IC,实则一款高带宽的视频DAC.因为VGA时序较为简单,并且网上的VGA驱动基本大同 ...

  9. Linux i2c子系统(四) _从i2c-s3c24xx.c看i2c控制器驱动的编写

    "./drivers/i2c/busses/i2c-s3c2410.c"是3.14.0内核中三星SoC的i2c控制器驱动程序, 本文试图通过对这个程序的分析, 剥离繁复的细节, 总 ...

随机推荐

  1. Linux I2C总线控制器驱动(S3C2440)

    s3c2440的i2c控制器驱动(精简DIY),直接上代码,注释很详细: #include <linux/kernel.h> #include <linux/module.h> ...

  2. python leetcode 日记 --Contains Duplicate --217

    题目 Given an array of integers, find if the array contains any duplicates. Your function should retur ...

  3. Enable SSHD on Ubuntu

    https://help.ubuntu.com/community/SSH/OpenSSH/Configuring

  4. qt 程序启动参数 -qws  (转至 MrTXK

    运行嵌入式程序 在嵌入式QT版本中,程序需要服务器或自己作为服务器程序.服务器程序构造的方法是构造一个QApplication::GuiServe类型的QApplication对象.或者使用-qws命 ...

  5. HDU 3854 Glorious Array(树状数组)

    题意:给一些结点,每个结点是黑色或白色,并有一个权值.定义两个结点之间的距离为两个结点之间结点的最小权值当两个结点异色时,否则距离为无穷大.给出两种操作,一种是将某个结点改变颜色,另一个操作是询问当前 ...

  6. 如何在Eclipse中设置默认的JSP文件头部编码

    如何在Eclipse中设置默认的JSP文件头部编码 一般,我们为了以后在导入和导出程序的时候(特别是项目较大,文件多)一般都默认文件编码格式为UTF-8 如果你通常都是通过Eclipse来编写程序,那 ...

  7. 浅谈对ECharts的使用

    上个月的项目,其中有一个模块用的是ECharts来实现的,分别用了折线图,环形图,还有漏斗图,这几个都算比较常见的了,尤其是折线图,环形图,用的最多的就是它们了.之前也没怎么接触过ECharts,实际 ...

  8. readonly背景色(css)

    input{ background-color:expression(this.readOnly==true?"#EEEEEE":"#FFFFFF"); } i ...

  9. HDU-2778 DNA Sequence(AC自动机)

    题目大意:统计模式串出现的次数. 题目分析:模板题. 代码如下: # include<iostream> # include<cstdio> # include<queu ...

  10. Fortran学习心得

    编译调试: 服务器上所用的编译器可以编译.F90的文件,同时,经过测试已经知道有两款编译器:gfortran和ifort. 另外,查看编译器的bash命令是:gfortran -v. 编程算法思想与C ...