加入星計劃,您可以享受以下權益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 導讀
    • 三、I2C 協(xié)議的具體實現(xiàn)
  • 推薦器件
  • 相關推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

基于 FPGA 的模擬 I2C協(xié)議設計(中)

2023/12/18
2065
閱讀需 45 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

今天給大俠帶來基于FPGA的 模擬 I2C 協(xié)議設計,由于篇幅較長,分三篇。今天帶來第二篇,中篇,I2C 協(xié)議的具體實現(xiàn)。話不多說,上貨。

導讀

I2C(Inter-Integrated Circuit),其實是 I2C Bus簡稱,中文就是集成電路總線,它是一種串行通信總線,使用多主從架構,由飛利浦公司在1980年代為了讓主板、嵌入式系統(tǒng)或手機用以連接低速周邊設備而發(fā)展。I2C的正確讀法為“I平方C”("I-squared-C"),而“I二C”("I-two-C")則是另一種錯誤但被廣泛使用的讀法。自2006年10月1日起,使用 I2C 協(xié)議已經(jīng)不需要支付專利費,但制造商仍然需要付費以獲取 I2C 從屬設備地址。

I2C 簡單來說,就是一種串行通信協(xié)議,I2C的通信協(xié)議和通信接口在很多工程中有廣泛的應用,如數(shù)據(jù)采集領域的串行 AD,圖像處理領域的攝像頭配置,工業(yè)控制領域的 X 射線管配置等等。除此之外,由于 I2C 協(xié)議占用的 IO 資源特別少,連接方便,所以工程中也常選用 I2C 接口做為不同芯片間的通信協(xié)議。I2C 串行總線一般有兩根信號線,一根是雙向的數(shù)據(jù)線SDA,另一根是時鐘線SCL。所有接到 I2C 總線設備上的串行數(shù)據(jù)SDA都接到總線的SDA上,各設備的時鐘線SCL接到總線的SCL上。

在現(xiàn)代電子系統(tǒng)中,有為數(shù)眾多的 IC 需要進行相互之間以及與外界的通信。為了簡化電路的設計,Philips 公司開發(fā)了一種用于內(nèi)部 IC 控制的簡單的雙向兩線串行總線 I2C(Intel-Integrated Circuit bus)。1998 年當推出 I2C 總線協(xié)議 2.0 版本時,I2C 協(xié)議實際上已經(jīng)成為一個國際標準。

在進行 FPGA 設計時,經(jīng)常需要和外圍提供 I2C 接口的芯片通信。例如低功耗CMOS 實時時鐘/日歷芯片 PCF8563、LCD 驅動芯片 PCF8562、并行口擴展芯片 PCF8574、鍵盤/LED 驅動器 ZLG7290 等都提供 I2C 接口。因此在 FPGA 中模擬 I2C 接口已成為 FPGA 開發(fā)必要的步驟。

本篇將詳細講解在 FPGA 芯片中使用 VHDL/Verilog HDL 模擬 I2C 協(xié)議,以及編寫 TestBench仿真和測試程序的方法。

第二篇內(nèi)容摘要:本篇會介紹 I2C 協(xié)議的具體實現(xiàn),包括位傳輸?shù)膶崿F(xiàn)、字節(jié)傳輸?shù)膶崿F(xiàn)以及程序主體的實現(xiàn)等相關內(nèi)容。

三、I2C 協(xié)議的具體實現(xiàn)

FPGA 設計一般按照從頂向下的模式進行:首先設計芯片功能,規(guī)劃各個模塊功能;然后按照規(guī)劃實現(xiàn)各個模塊。本篇由 3 個代碼文件組成:i2c_master_bit_ctrl.v 完成位傳輸?shù)墓δ?、i2c_master_byte_ctrl.v 完成字節(jié)傳輸?shù)墓δ?、i2c_master_top.v 完成整個程序的控制功能,并提供給外部程序的接口。在 ISE 中創(chuàng)建一個項目,然后加入上面 3 個文件。下面依次介紹 3 個文件的內(nèi)容。本篇講解采用 Verilog HDL。

3.1 位傳輸?shù)膶崿F(xiàn)

i2c_master_bit_ctrl.v 完成位傳輸?shù)墓δ?。位傳輸?shù)墓δ馨〝?shù)據(jù)按位傳輸?shù)膶崿F(xiàn)和 I2C協(xié)議各個命令的實現(xiàn)兩部分。

如圖 5 所示開始和重復開始命令的產(chǎn)生包括 5 個階段:idle 和 A、B、C、D 等。停止命令包括 4 個階段:idle 和 A、B、C 等。讀、寫一個字節(jié)通過 8 次位操作完成。

圖 5 位傳輸完成數(shù)據(jù)的傳輸和各個命令的實現(xiàn)

實現(xiàn)代碼如下:

`include "timescale.v"
`include "i2c_master_defines.v"

//模塊名稱及 IO
module i2c_master_bit_ctrl(
clk, rst, nReset,
clk_cnt, ena, cmd, cmd_ack, busy, al, din, dout,
scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen
);

// 輸入、輸出
input clk;
input rst;
input nReset;
input ena; // 模塊使能信號
input [15:0] clk_cnt; // 時鐘分頻系數(shù)
input [3:0] cmd;
output cmd_ack; // 命令完成應答
reg cmd_ack;
output busy; // 總線忙
reg busy;
output al; // 總線仲裁丟失
reg al;
input din;
output dout;
reg dout;

// I2C 連線
input scl_i; // I2C 時鐘輸入
output scl_o; // I2C 時鐘輸出
output scl_oen; // I2C 時鐘輸出使能
reg scl_oen;
input sda_i; //I2C 數(shù)據(jù)輸入
output sda_o; // I2C 數(shù)據(jù)輸出
output sda_oen; // I2C 數(shù)據(jù)輸出使能
reg sda_oen;

// variable declarations
reg sSCL, sSDA; // 同步后的 SCL 和 SDA 輸入
reg dscl_oen; // 延遲后的 scl_oen
reg sda_chk; // 檢 查 后 的 SDA output (Multi-master
arbitration)
reg clk_en; // 時鐘產(chǎn)生信號
wire slave_wait;
reg [15:0] cnt; // 時鐘分頻計數(shù)器

// 模塊主體
// 當從節(jié)點沒有準備好時,下拉 SCL 來延遲周期
// 延遲 scl_oen
always @(posedge clk)
dscl_oen <= #1 scl_oen;

assign slave_wait = dscl_oen && !sSCL;

// 產(chǎn)生時鐘使能信號
always @(posedge clk or negedge nReset)
if(~nReset)
begin
cnt <= #1 16'h0;
clk_en <= #1 1'b1;
end
else if (rst)
begin
cnt <= #1 16'h0;
clk_en <= #1 1'b1;
end
else if ( ~|cnt || ~ena)
if (~slave_wait)
begin
cnt <= #1 clk_cnt;
clk_en <= #1 1'b1;
end
else
begin
cnt <= #1 cnt;
clk_en <= #1 1'b0;
end
else
begin
cnt <= #1 cnt - 16'h1;
clk_en <= #1 1'b0;
end

// 產(chǎn)生總線狀態(tài)控制信號
reg dSCL, dSDA;
reg sta_condition;
reg sto_condition;

// 同步 SCL 和 SDA 輸入信號,減少不穩(wěn)定風險
always @(posedge clk or negedge nReset)
if (~nReset)
begin
sSCL <= #1 1'b1;
sSDA <= #1 1'b1;
dSCL <= #1 1'b1;
dSDA <= #1 1'b1;
end
else if (rst)
begin
sSCL <= #1 1'b1;
sSDA <= #1 1'b1;
dSCL <= #1 1'b1;
dSDA <= #1 1'b1;
end
else
begin
sSCL <= #1 scl_i;
sSDA <= #1 sda_i;
dSCL <= #1 sSCL;
dSDA <= #1 sSDA;
end

// SCL 處于高時檢測到 SDA 的下降沿,即檢測開始狀態(tài)信號
// SCL 處于高時檢測到 SDA 的上升沿,即檢測停止狀態(tài)信號
always @(posedge clk or negedge nReset)
if (~nReset)
begin
sta_condition <= #1 1'b0;
sto_condition <= #1 1'b0;
end
else if (rst)
begin
sta_condition <= #1 1'b0;
sto_condition <= #1 1'b0;
end
else
begin
sta_condition <= #1 ~sSDA & dSDA & sSCL;
sto_condition <= #1 sSDA & ~dSDA & sSCL;
end

// 產(chǎn)生 I2C 總線忙信號
always @(posedge clk or negedge nReset)
if(!nReset)
busy <= #1 1'b0;
else if (rst)
busy <= #1 1'b0;
else
busy <= #1 (sta_condition | busy) & ~sto_condition;

// 產(chǎn)生仲裁丟失信號 generate arbitration lost signal
// 仲裁丟失發(fā)生在:
// 1) 主節(jié)點驅動 SDA 處于高,但是 I2C 總線一直處于低
// 2) 沒有請求時卻檢測到停止狀態(tài)信號
reg cmd_stop, dcmd_stop;

always @(posedge clk or negedge nReset)
if (~nReset)
begin
cmd_stop <= #1 1'b0;
dcmd_stop <= #1 1'b0;
al <= #1 1'b0;
end
else if (rst)
begin
cmd_stop <= #1 1'b0;
dcmd_stop <= #1 1'b0;
al <= #1 1'b0;
end
else
begin
cmd_stop <= #1 cmd == `I2C_CMD_STOP;
dcmd_stop <= #1 cmd_stop;
al <= #1 (sda_chk & ~sSDA & sda_oen) | (sto_condition & ~dcmd_stop);
end

// 產(chǎn)生數(shù)據(jù)輸出信號,在 SCL 信號的上升沿保存 SDA
always @(posedge clk)
if(sSCL & ~dSCL)
dout <= #1 sSDA;

// 產(chǎn)生狀態(tài)機
// 狀態(tài)譯碼
parameter [16:0] idle = 17'b0_0000_0000_0000_0000;
parameter [16:0] start_a = 17'b0_0000_0000_0000_0001;
parameter [16:0] start_b = 17'b0_0000_0000_0000_0010;
parameter [16:0] start_c = 17'b0_0000_0000_0000_0100;
parameter [16:0] start_d = 17'b0_0000_0000_0000_1000;
parameter [16:0] start_e = 17'b0_0000_0000_0001_0000;
parameter [16:0] stop_a = 17'b0_0000_0000_0010_0000;
parameter [16:0] stop_b = 17'b0_0000_0000_0100_0000;
parameter [16:0] stop_c = 17'b0_0000_0000_1000_0000;
parameter [16:0] stop_d = 17'b0_0000_0001_0000_0000;
parameter [16:0] rd_a = 17'b0_0000_0010_0000_0000;
parameter [16:0] rd_b = 17'b0_0000_0100_0000_0000;
parameter [16:0] rd_c = 17'b0_0000_1000_0000_0000;
parameter [16:0] rd_d = 17'b0_0001_0000_0000_0000;
parameter [16:0] wr_a = 17'b0_0010_0000_0000_0000;
parameter [16:0] wr_b = 17'b0_0100_0000_0000_0000;
parameter [16:0] wr_c = 17'b0_1000_0000_0000_0000;
parameter [16:0] wr_d = 17'b1_0000_0000_0000_0000;
reg [16:0] c_state;

//狀態(tài)機
always @(posedge clk or negedge nReset)
if (!nReset)
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b0;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end
else if (rst | al)
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b0;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end
else
begin
cmd_ack <= #1 1'b0;
if (clk_en)
case (c_state)
// idle 狀態(tài)
idle:
begin
case (cmd)
`I2C_CMD_START:
c_state <= #1 start_a;
`I2C_CMD_STOP:
c_state <= #1 stop_a;
`I2C_CMD_WRITE:
c_state <= #1 wr_a;
`I2C_CMD_READ:
c_state <= #1 rd_a;
default:
c_state <= #1 idle;
endcase
scl_oen <= #1 scl_oen; // 保持 SCL 在同一狀態(tài)
sda_oen <= #1 sda_oen; // 保持 SDA 在同一狀態(tài)
sda_chk <= #1 1'b0; // 不檢查 SDA 輸出
end
// 開始狀態(tài)
start_a:
begin
c_state <= #1 start_b;
scl_oen <= #1 scl_oen; // 保持 SCL 在同一狀態(tài)
sda_oen <= #1 1'b1; // 保持 SDA 處于高
sda_chk <= #1 1'b0; // 不檢查 SDA 的輸出
end

start_b:
begin
c_state <= #1 start_c;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end

start_c:
begin
c_state <= #1 start_d;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b0;
sda_chk <= #1 1'b0;
end

start_d:
begin
c_state <= #1 start_e;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b0;
sda_chk <= #1 1'b0;
end

start_e:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b0;
sda_oen <= #1 1'b0;
sda_chk <= #1 1'b0;
end

// 停止狀態(tài)
stop_a:
begin
c_state <= #1 stop_b;
scl_oen <= #1 1'b0;
sda_oen <= #1 1'b0;
sda_chk <= #1 1'b0;
end

stop_b:
begin
c_state <= #1 stop_c;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b0;
sda_chk <= #1 1'b0;
end

stop_c:
begin
c_state <= #1 stop_d;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b0;
sda_chk <= #1 1'b0;
end

stop_d:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end

// 讀狀態(tài)
rd_a:
begin
c_state <= #1 rd_b;
scl_oen <= #1 1'b0; //保持 SCL 處于低
sda_oen <= #1 1'b1; // SDA 處于三態(tài)
sda_chk <= #1 1'b0; // 不檢查 SDA 輸出
end

rd_b:
begin
c_state <= #1 rd_c;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end

rd_c:
begin
c_state <= #1 rd_d;
scl_oen <= #1 1'b1;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end

rd_d:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b0;
sda_oen <= #1 1'b1;
sda_chk <= #1 1'b0;
end

// 寫狀態(tài)
wr_a:
begin
c_state <= #1 wr_b;
scl_oen <= #1 1'b0;
sda_oen <= #1 din;
sda_chk <= #1 1'b0;
end

wr_b:
begin
c_state <= #1 wr_c;
scl_oen <= #1 1'b1;
sda_oen <= #1 din;
sda_chk <= #1 1'b1;
end

wr_c:
begin
c_state <= #1 wr_d;
scl_oen <= #1 1'b1;
sda_oen <= #1 din;
sda_chk <= #1 1'b1;
end

wr_d:
begin
c_state <= #1 idle;
cmd_ack <= #1 1'b1;
scl_oen <= #1 1'b0;
sda_oen <= #1 din;
sda_chk <= #1 1'b0;
end

endcase
end

// 分配 SCL 和 SDA 輸出一直處于低
assign scl_o = 1'b0;
assign sda_o = 1'

endmodule

3.2 字節(jié)傳輸?shù)膶崿F(xiàn)

字節(jié)傳輸?shù)木唧w實現(xiàn)流程如圖 6 所示。

圖 6 字節(jié)傳輸控制模塊流程圖

字節(jié)傳輸控制模塊控制以字節(jié)為單位的數(shù)據(jù)傳輸。它根據(jù)命令寄存器的設置將數(shù)據(jù)傳輸寄存器中的內(nèi)容傳輸?shù)酵獠抗?jié)點,將外部節(jié)點的數(shù)據(jù)接收到數(shù)據(jù)接收寄存器中。

實現(xiàn)代碼如下:

`include "timescale.v"
`include "i2c_master_defines.v"
//模塊
module i2c_master_byte_ctrl (
clk, rst, nReset, ena, clk_cnt, start, stop, read, write, ack_in, din,
cmd_ack, ack_out, dout, i2c_busy, i2c_al, scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen );

// 輸入、輸出
input clk; // 主時鐘
input rst; // 同步 RESET,高有效
input nReset; // 異步 RESET,低有效
input ena; // 模塊使能信號
input [15:0] clk_cnt; // 4 倍 SCL 信號

// 控制信號輸入
input start;
input stop;
input read;
input write;
input ack_in;
input [7:0] din;

// 狀態(tài)信號輸出
output cmd_ack;
reg cmd_ack;
output ack_out;
reg ack_out;
output i2c_busy;
output i2c_al;
output [7:0] dout;

// I2C 信號
input scl_i;
output scl_o;
output scl_oen;
input sda_i;
output sda_o;
output sda_oen;

// 變量申明
// 狀態(tài)機
parameter [4:0] ST_IDLE = 5'b0_0000;
parameter [4:0] ST_START = 5'b0_0001;
parameter [4:0] ST_READ = 5'b0_0010;
parameter [4:0] ST_WRITE = 5'b0_0100;
parameter [4:0] ST_ACK = 5'b0_1000;
parameter [4:0] ST_STOP = 5'b1_0000;

// 位控制模塊的信號
reg [3:0] core_cmd;
reg core_txd;
wire core_ack, core_rxd;

// 移位寄存器信號
reg [7:0] sr; //8 位移位寄存器
reg shift, ld;

// 狀態(tài)機信號
wire go;
reg [2:0] dcnt;
wire cnt_done;

// 模塊主體
// 連接位控制模塊
i2c_master_bit_ctrl bit_controller (
.clk ( clk ),
.rst ( rst ),
.nReset ( nReset ),
.ena ( ena ),
.clk_cnt ( clk_cnt ),
.cmd ( core_cmd ),
.cmd_ack ( core_ack ),
.busy ( i2c_busy ),
.al ( i2c_al ),
.din ( core_txd ),
.dout ( core_rxd ),
.scl_i ( scl_i ),
.scl_o ( scl_o ),
.scl_oen ( scl_oen ),
.sda_i ( sda_i ),
.sda_o ( sda_o ),
.sda_oen ( sda_oen )
);

// 產(chǎn)生 GO 信號,當讀/寫/停止/應答時發(fā)生
assign go = (read | write | stop) & ~cmd_ack;

// 分配輸出到移位寄存器
assign dout = sr;

// 產(chǎn)生移位寄存器
always @(posedge clk or negedge nReset)
if (!nReset)
sr <= #1 8'h0;
else if (rst)
sr <= #1 8'h0;
else if (ld)
sr <= #1 din;
else if (shift)
sr <= #1 {sr[6:0], core_rxd};

// 產(chǎn)生計數(shù)器
always @(posedge clk or negedge nReset)
if (!nReset)
dcnt <= #1 3'h0;
else if (rst)
dcnt <= #1 3'h0;
else if (ld)
dcnt <= #1 3'h7;
else if (shift)
dcnt <= #1 dcnt - 3'h1;

assign cnt_done = ~(|dcnt);

// 狀態(tài)機
reg [4:0] c_state;
always @(posedge clk or negedge nReset)
if (!nReset)
begin
core_cmd <= #1 `I2C_CMD_NOP;
core_txd <= #1 1'b0;
shift <= #1 1'b0;
ld <= #1 1'b0;
cmd_ack <= #1 1'b0;
c_state <= #1 ST_IDLE;
ack_out <= #1 1'b0;
end
else if (rst | i2c_al)
begin
core_cmd <= #1 `I2C_CMD_NOP;
core_txd <= #1 1'b0;
shift <= #1 1'b0;
ld <= #1 1'b0;
cmd_ack <= #1 1'b0;
c_state <= #1 ST_IDLE;
ack_out <= #1 1'b0;
end
else
begin
// 初始化所有信號
core_txd <= #1 sr[7];
shift <= #1 1'b0;
ld <= #1 1'b0;
cmd_ack <= #1 1'b0;
case (c_state)
//IDLE 狀態(tài)
ST_IDLE:
if (go)
begin
if (start)
begin
c_state <= #1 ST_START;
core_cmd <= #1 `I2C_CMD_START;
end
else if (read)

begin
c_state <= #1 ST_READ;
core_cmd <= #1 `I2C_CMD_READ;
end
else if (write)
begin
c_state <= #1 ST_WRITE;
core_cmd <= #1 `I2C_CMD_WRITE;
end
else // 缺省的是 stop 狀態(tài)
begin
c_state <= #1 ST_STOP;
core_cmd <= #1 `I2C_CMD_STOP;
// 產(chǎn)生應答信號
cmd_ack <= #1 1'b1;
end
ld <= #1 1'b1;
end

//開始狀態(tài)
ST_START:
if (core_ack)
begin
if (read)
begin
c_state <= #1 ST_READ;
core_cmd <= #1 `I2C_CMD_READ;
end
else
begin
c_state <= #1 ST_WRITE;
core_cmd <= #1 `I2C_CMD_WRITE;
end
ld <= #1 1'b1;
end

//寫數(shù)據(jù)狀態(tài)
ST_WRITE:
if (core_ack)
if (cnt_done)
begin
c_state <= #1 ST_ACK;
core_cmd <= #1 `I2C_CMD_READ;
end
else
begin
c_state <= #1 ST_WRITE; // 保持在原來狀態(tài)
core_cmd <= #1 `I2C_CMD_WRITE; // 寫下一位數(shù)據(jù)
shift <= #1 1'b1;
end

//讀信號狀態(tài)
ST_READ:
if (core_ack)
begin
if (cnt_done)
begin
c_state <= #1 ST_ACK;
core_cmd <= #1 `I2C_CMD_WRITE;
end
else
begin
c_state <= #1 ST_READ; // 保留在原來狀態(tài)
core_cmd <= #1 `I2C_CMD_READ; // 讀下一位數(shù)據(jù)
end
shift <= #1 1'b1;
core_txd <= #1 ack_in;
end

//應答數(shù)據(jù)狀態(tài)
ST_ACK:
if (core_ack)
begin
if (stop)
begin
c_state <= #1 ST_STOP;
core_cmd <= #1 `I2C_CMD_STOP;
end
else
begin
c_state <= #1 ST_IDLE;
core_cmd <= #1 `I2C_CMD_NOP;
end

// 把應答信號輸出連接到位控制模塊
ack_out <= #1 core_rxd;
// 產(chǎn)生應答信號
cmd_ack <= #1 1'b1;
core_txd <= #1 1'b1;
end
else
core_txd <= #1 ack_in;

//停止狀態(tài)
ST_STOP:
if (core_ack)
begin
c_state <= #1 ST_IDLE;
core_cmd <= #1 `I2C_CMD_NOP;
end
endcase
end

endmodule

3.3 程序主體的實現(xiàn)

程序主體部分完成與外部程序的接口、與總線上外部節(jié)點的連線、完成程序內(nèi)部各個寄存器的構建、控制字節(jié)傳輸控制模塊等功能。代碼如下:

`include "timescale.v"
`include "i2c_master_defines.v"
//模塊定義
module i2c_master_top(
wb_clk_i, wb_rst_i, arst_i, wb_adr_i, wb_dat_i, wb_dat_o,
wb_we_i, wb_stb_i, wb_cyc_i, wb_ack_o, wb_inta_o,
scl_pad_i, scl_pad_o, scl_padoen_o, sda_pad_i, sda_pad_o, sda_padoen_o );

// 參數(shù)
parameter ARST_LVL = 1'b0; // 異步 reset 信號

// 輸入、輸出信號
// 連接到外部接口的信號
input clk_i; // 主節(jié)點時鐘信號
input rst_i; // 同步 reset 信號,高有效
input arst_i; // 異步 reset 信號
input [2:0] adr_i; // 低位地址信號
input [7:0] dat_i; // 數(shù)據(jù)總線輸入
output [7:0] dat_o; // 數(shù)據(jù)總線輸出
input we_i; // 輸入使能信號
input stb_i; // 觸發(fā)信號
input cyc_i; // 總線周期輸入
output ack_o; // 應答信號輸出
output inta_o; // 中斷請求信號輸出
reg [7:0] wb_dat_o;
reg wb_ack_o;
reg wb_inta_o;

// I2C 信號
// I2C 時鐘信號線
input scl_pad_i; // SCL 輸入
output scl_pad_o; // SCL 輸出
output scl_padoen_o; // SCL 輸出使能

// I2C 數(shù)據(jù)線
input sda_pad_i; // SDA 輸入
output sda_pad_o; // SDA 輸出
output sda_padoen_o; // SDA 輸出使能

// 變量申明
// 寄存器
reg [15:0] prer; // 時鐘分頻寄存器
reg [ 7:0] ctr; // 控制寄存器
reg [ 7:0] txr; // 數(shù)據(jù)傳輸寄存器
wire [ 7:0] rxr; // 數(shù)據(jù)接收寄存器
reg [ 7:0] cr; // 命令寄存器
wire [ 7:0] sr; // 狀態(tài)寄存器

// 完成信號,命令完成后清除命令寄存器
wire done;

// 模塊使能信號
wire core_en;
wire ien;

// 狀態(tài)寄存器信號
wire irxack;
reg rxack; // 從從節(jié)點接收應答信號
reg tip; // 傳輸進行標志
reg irq_flag; // 中斷掛起標志
wire i2c_busy; // 總線忙標志
wire i2c_al; // 總線仲裁丟失
reg al; // 狀態(tài)寄存器仲裁丟失位

// 模塊主體
// 產(chǎn)生內(nèi)部 reset
wire rst_i = arst_i ^ ARST_LVL;
wire wacc = cyc_i & stb_i & we_i;

// 產(chǎn)生應答輸出信號
always @(posedge clk_i)
wb_ack_o <= #1 cyc_i & stb_i & ~ack_o;

// 數(shù)據(jù)輸出
always @(posedge clk_i)
begin
case (adr_i)
3'b000: wb_dat_o = prer[ 7:0];
3'b001: wb_dat_o = prer[15:8];
3'b010: wb_dat_o = ctr;
3'b011: wb_dat_o = rxr; // 寫數(shù)據(jù)傳輸寄存器
3'b100: wb_dat_o = sr; // 寫命令寄存器
3'b101: wb_dat_o = txr;
3'b110: wb_dat_o = cr;
3'b111: wb_dat_o = 0; // 保留位
endcase
end

// 產(chǎn)生寄存器
always @(posedge wb_clk_i or negedge rst_i)
if (!rst_i)
begin
prer <= #1 16'hffff;
ctr <= #1 8'h0;
txr <= #1 8'h0;
end
else if (wb_rst_i)
begin
prer <= #1 16'hffff;
ctr <= #1 8'h0;
txr <= #1 8'h0;
end
else
if (wb_wacc)
case (wb_adr_i) // synopsis full_case parallel_case
3'b000 : prer [ 7:0] <= #1 wb_dat_i;
3'b001 : prer [15:8] <= #1 wb_dat_i;
3'b010 : ctr <= #1 wb_dat_i;
3'b011 : txr <= #1 wb_dat_i;
endcase

// 產(chǎn)生命令寄存器
always @(posedge wb_clk_i or negedge rst_i)
if (~rst_i)
cr <= #1 8'h0;
else if (wb_rst_i)
cr <= #1 8'h0;
else if (wb_wacc)
begin
if (core_en & (wb_adr_i == 3'b100) )
cr <= #1 wb_dat_i;
end
else
begin
if (done | i2c_al)
cr[7:4] <= #1 4'h0; // 命令完成或者仲裁丟失時清除命令寄存器內(nèi)容
cr[2:1] <= #1 2'b0; // 保留位
cr[0] <= #1 2'b0; // 清除 IRQ_ACK 位
end

// 譯碼命令寄存器
wire sta = cr[7];
wire sto = cr[6];
wire rd = cr[5];
wire wr = cr[4];
wire ack = cr[3];
wire iack = cr[0];

// 譯碼控制寄存器
assign core_en = ctr[7];
assign ien = ctr[6];

// 連接字節(jié)控制模塊
i2c_master_byte_ctrl byte_controller (
.clk ( wb_clk_i ),
.rst ( wb_rst_i ),
.nReset ( rst_i ),
.ena ( core_en ),
.clk_cnt ( prer ),
.start ( sta ),
.stop ( sto ),
.read ( rd ),
.write ( wr ),
.ack_in ( ack ),
.din ( txr ),
.cmd_ack ( done ),
.ack_out ( irxack ),
.dout ( rxr ),
.i2c_busy ( i2c_busy ),
.i2c_al ( i2c_al ),
.scl_i ( scl_pad_i ),
.scl_o ( scl_pad_o ),
.scl_oen ( scl_padoen_o ),
.sda_i ( sda_pad_i ),
.sda_o ( sda_pad_o ),
.sda_oen ( sda_padoen_o )
)

// 狀態(tài)寄存器部分和中斷請求信號
always @(posedge wb_clk_i or negedge rst_i)
if (!rst_i)
begin
al <= #1 1'b0;
rxack <= #1 1'b0;
tip <= #1 1'b0;
irq_flag <= #1 1'b0;
end
else if (wb_rst_i)
begin
al <= #1 1'b0;
rxack <= #1 1'b0;
tip <= #1 1'b0;
irq_flag <= #1 1'b0;
end
else
begin
al <= #1 i2c_al | (al & ~sta);
rxack <= #1 irxack;
tip <= #1 (rd | wr);
irq_flag <= #1 (done | i2c_al | irq_flag) & ~iack;
end

// 中斷請求標志
// 產(chǎn)生中斷請求信號
always @(posedge wb_clk_i or negedge rst_i)
if (!rst_i)
wb_inta_o <= #1 1'b0;
else if (wb_rst_i)
wb_inta_o <= #1 1'b0;
else
wb_inta_o <= #1 irq_flag && ien; //中斷使能位 IEN 設置后產(chǎn)生中斷信號

assign sr[7] = rxack;
assign sr[6] = i2c_busy;
assign sr[5] = al;
assign sr[4:2] = 3'h0; // reserved
assign sr[1] = tip;
assign sr[0] = irq_flag;

endmodule

本篇到此結束,下一篇帶來基于 FPGA 的 模擬 I2C 協(xié)議設計(下),程序的仿真與測試,包括主節(jié)點的仿真、從節(jié)點的仿真、仿真主程序、仿真結果以及總結等相關內(nèi)容。

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風險等級 參考價格 更多信息
EP4CE30F23C8N 1 Altera Corporation Field Programmable Gate Array, 1803 CLBs, 472.5MHz, 28848-Cell, PBGA484, 23 X 23 MM, 1 MM PITCH, LEAD FREE, FBGA-484

ECAD模型

下載ECAD模型
$39.92 查看
XC2C384-10FTG256I 1 AMD Xilinx Flash PLD, 10ns, 384-Cell, CMOS, PBGA256, 17 X 17 MM, 1 MM PITCH, LEAD FREE, FTBGA-256
$596.21 查看
10M08SCE144C8G 1 Intel Corporation Field Programmable Gate Array, 8000-Cell, CMOS, PQFP144, 22 X 22 MM, 0.50 MM PITCH, ROHS COMPLIANT, PLASTIC, EQFP-144

ECAD模型

下載ECAD模型
$17.46 查看

相關推薦

電子產(chǎn)業(yè)圖譜

任何技術的學習就好比一個江湖,對于每一位俠客都需要不斷的歷練,從初入江湖的小白到歸隱山林的隱世高人,需要不斷的自我感悟自己修煉,讓我們一起仗劍闖FPGA乃至更大的江湖。