FPGA實作IIC協議--讀取EEPRPM
- IIC協議部分
- EEPROM原理
- 實驗任務
- 程式設計
參考正點原子視頻
IIC協議部分
I2C 即 Inter-Integrated Circuit(集成電路總線),多用于主機和從機在資料量不大且傳輸距離短的場合下的主從通信,主機啟動總線,并產生時鐘用于傳送資料,此時任何接收資料的器件均被認為是從機,
I2C_SCL 是串行時鐘線,I2C_SDA 是串行資料線,由于 I2C 器件一般采用開漏結構與總線相連,所以 I2C_SCL 和 I2C_SDA 均需接上拉電阻,也正因此,當總線空閑時,這兩條線路都處于高電平狀態,當連到總線上的任一器件輸出低電平,都將使總線拉低,即各器件的 SDA 及 SCL 都是“線與”關系,
I2C 總線支持多主和主從兩種作業方式,在主從作業方式中,主機啟動資料的發送(發出啟動信號)并產生時鐘信號,資料發送完成后,發出停止信號,(常用主從作業方式)
I2C 總線結構雖然簡單,使用兩線傳輸,然而要實作器件間的通信,需要通過控制 SCL 和 SDA 的時序,使其滿足 I2C 的總線傳輸協議,方可實作器件間的資料傳輸,那么 I2C 協議的時序是怎樣的呢?

空閑狀態為高電平,起始信號是在SCL為高電平的時候拉低SDA,停止信號則是在SCL為高的時候拉高SDA,
IIC遵循的規則

在SCL為低電平的時候,允許改變SDA的狀態;在SCL為高電平的時候,SDA的狀態保持不變,當經過8個時鐘周期之后(SCL一高一低為一個周期–傳輸一位),**主機釋放SDA(此時SDA變為空閑狀態)**以使從機應答,在第九個時鐘周期的時候,從機將SDA拉低作為應答,若未拉低,則未應答,第九個時鐘末,從機釋放SDA以使主機繼續傳輸資料,若接下來主機發送停止信號,則表示此次傳輸結束,(注:主機先發送的位為高位元組–高高低低原則)
以上為IIC的時序部分,接下來要解決的問題是:器件地址
器件地址:分為固定和不固定,由硬體連接決定,
接下來發送的是字地址,
其中字地址分為單位元組地址和多位元組地址,由從機內部的存盤器決定,字地址代表的也就是存盤器內部的某個存盤位置,

主機發送完字地址,從機應答后就會將存盤器內部指標指向對應存盤單元,如果讀寫控制位為0,那么執行寫的操作,
寫分為單次寫(對于EEPROM也叫位元組寫)和連續寫(對于EEPROM也叫頁寫),區別在于發送完一位元組資料后是發送結束信號還是繼續寫下一個資料,注意:連續寫不能超過一頁(在EEPROM中,一頁代表著32Byte),
如果讀寫控制為1,那么執行讀操作,
讀操作分為當前地址讀、隨機讀和連續讀,此時主機處于接收資料的狀態,當前地址讀是在一次讀/寫操作進行完之后,其內部的地址指標自動加1,這樣就可以讀下一個地址的位置,隨機讀的區別之處在于它是在器件地址和字地址發送完之后,再發送一次起始信號和器件地址,而且第一次的讀寫控制位為0,第二次為1,這就是“虛寫操作”,需要通過一次寫來使指標指向存盤單元內我們想要讀取的位置,等從機地址應答后,就可以以當前地址讀的方式繼續下去了,連續讀則是把主機的非應答信號改成應答信號即可,

EEPROM原理
EEPROM (Electrically Erasable Progammable Read Only Memory E2PROM)即電可擦除可編程只讀存盤器,是一種常用的非易失性存盤器(掉電資料不丟失)器,是一種常用的非易失性存盤器(掉電資料不丟失), EEPROM有多種型別的產品,達芬奇開發板上使用的是 ATMEL公司生產的 AT24C系列的 AT24C64這一型號, AT24C64具有高可靠性 可對所存資料保存 100年,并可多次擦寫,擦寫次數達 一百萬次 ,
一般而言,對于存盤型別的芯片,我們比較關注其存盤容量,我們這次實驗所用的 AT24C64存盤容量為 64Kbit,內部分成 256頁,每頁 32位元組, 共有 8192個 位元組 ,且其讀寫操作都是以位元組為基本單位,可以把 AT24C64看作一本書,那么這本書有 256頁,每頁有 32行,每行有 8個字,總共有256328=65536個字,對應著 AT24C64的 64*1024=65536個 bit,
本開發板采用的iic總線物理拓撲圖如下:

實驗任務
本節的實驗任務是先向EEPROM AT24C64)的存盤器地址 0至 255分別寫入資料 0 ~ 255;寫完之后再讀取存盤器地址0 ~ 255中的資料,若讀取的值全部正確則 LED燈常亮,否則 LED燈閃爍,
硬體原理圖如下:

AT24C64的引腳功能如下:
A2,A1,A0:可編程地址輸入端,
GND:電源地引腳
SDA SDA Serial Data,串行資料 是雙向串行資料輸入 /輸出端,
SCL SCL Serial clock,串行時鐘)串行時鐘輸入端,
WP(寫保護 AT24C64有一個寫保護引腳用于提供資料保護,當寫保護引腳連接至 GND時,芯片可以正常寫,當寫保護引腳連接至 VCC時,使能寫保護功能,此時禁止向芯片寫入資料,只能進行讀操作,
VCC:電源輸入引腳

約束陳述句如下:
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U2 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN R6 IOSTANDARD LVCMOS33} [get_ports iic_scl]
set_property -dict {PACKAGE_PIN T4 IOSTANDARD LVCMOS33} [get_ports iic_sda]
set_property -dict {PACKAGE_PIN R2 IOSTANDARD LVCMOS15} [get_ports led]
程式設計
系統框圖:

頂層模塊圖

分為4個模塊,頂層模塊、i2c_dri模塊、e2prom_rw模塊和led_alarm模塊,
e2prom_top
//parameter define
parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR),1010+0+0+0
parameter BIT_CTRL = 1'b1 ; //字地址位控制引數(16b/8b)
parameter CLK_FREQ = 26'd50_000_000; //i2c_dri模塊的驅動時鐘頻率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL時鐘頻率
parameter L_TIME = 17'd125_000 ; //led閃爍時間引數
//wire define
wire dri_clk ; //I2C操作時鐘
wire i2c_exec ; //I2C觸發控制
wire [15:0] i2c_addr ; //I2C操作地址
wire [ 7:0] i2c_data_w; //I2C寫入的資料
wire i2c_done ; //I2C操作結束標志
wire i2c_ack ; //I2C應答標志 0:應答 1:未應答
wire i2c_rh_wl ; //I2C讀寫控制
wire [ 7:0] i2c_data_r; //I2C讀出的資料
wire rw_done ; //E2PROM讀寫測驗完成
wire rw_result ; //E2PROM讀寫測驗結果 0:失敗 1:成功
i2c_dri
此模塊適合用狀態機撰寫,空閑狀態,經判斷條件后變為執行狀態,之后又回到空閑狀態,采用的也就是三段式狀態機,但其中的狀態細分卻有8種,

這就是狀態機的狀態變化程序,
//SDA控制
assign sda = sda_dir ? sda_out : 1'bz; //SDA資料輸出或高阻
assign sda_in = sda ; //SDA資料輸入
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模塊驅動時鐘的分頻系數
//生成I2C的SCL的四倍頻率的驅動時鐘用于驅動i2c的操作
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式狀態機)同步時序描述狀態轉移
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//組合邏輯判斷狀態轉移條件
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle: begin //空閑狀態
if(i2c_exec) begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done) begin
if(bit_ctrl) //判斷是16位還是8位字地址
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin //寫16位字地址
if(st_done) begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8: begin //8位字地址
if(st_done) begin
if(wr_flag==1'b0) //讀寫判斷
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr: begin //寫資料(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd: begin //寫地址以進行讀資料
if(st_done) begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd: begin //讀取資料(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin //結束I2C操作
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default: next_state= st_idle;
endcase
end
//時序電路描述狀態輸出
always @(posedge dri_clk or negedge rst_n) begin
//復位初始化
if(!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r<= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin //空閑狀態
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin
wr_flag <= i2c_rh_wl ;
addr_t <= i2c_addr ;
data_wr_t <= i2c_data_w;
i2c_ack <= 1'b0;
end
end
st_sladdr: begin //寫地址(器件地址和字地址)
case(cnt)
7'd1 : sda_out <= 1'b0; //開始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //傳送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; //0:寫
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //從機應答
st_done <= 1'b1;
if(sda_in == 1'b1) //高電平表示未應答
i2c_ack <= 1'b1; //拉高應答標志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15]; //傳送字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //從機應答
st_done <= 1'b1;
if(sda_in == 1'b1) //高電平表示未應答
i2c_ack <= 1'b1; //拉高應答標志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7]; //字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //從機應答
st_done <= 1'b1;
if(sda_in == 1'b1) //高電平表示未應答
i2c_ack <= 1'b1; //拉高應答標志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin //寫資料(8 bit)
case(cnt)
7'd0: begin
sda_out <= data_wr_t[7]; //I2C寫8位資料
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //從機應答
st_done <= 1'b1;
if(sda_in == 1'b1) //高電平表示未應答
i2c_ack <= 1'b1; //拉高應答標志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr_rd: begin //寫地址以進行讀資料
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; //重新開始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //傳送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; //1:讀
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //從機應答
st_done <= 1'b1;
if(sda_in == 1'b1) //高電平表示未應答
i2c_ack <= 1'b1; //拉高應答標志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin //讀取資料(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1; //非應答
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
st_stop: begin //結束I2C操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1; //結束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; //向上層模塊傳遞I2C結束信號
end
default : ;
endcase
end
endcase
end
end
可以看到分成了3部分來描寫狀態,同步時序、組合邏輯和時序邏輯,這就是三段式狀態機的寫法,
e2p_rw
EEPROM讀寫模塊主 要實作 對 I2C讀寫 程序的控制 ,包括給出字 地址 及需要寫入該地址中的資料 、 啟動I2C讀寫操作 、判斷讀寫資料是否一致等,用到其他傳感器則需要把這一部分替代成適用的模塊,
//parameter define
//EEPROM寫資料需要添加間隔時間,讀資料則不需要
parameter WR_WAIT_TIME = 14'd5000; //寫入間隔時間
parameter MAX_BYTE = 16'd256 ; //讀寫測驗的位元組個數
//reg define
reg [1:0] flow_cnt ; //狀態流控制
reg [13:0] wait_cnt ; //延時計數器
//*****************************************************
//** main code
//*****************************************************
//EEPROM讀寫測驗,先寫后讀,并比較讀出的值與寫入的值是否一致
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flow_cnt <= 2'b0;
i2c_rh_wl <= 1'b0;
i2c_exec <= 1'b0;
i2c_addr <= 16'b0;
i2c_data_w <= 8'b0;
wait_cnt <= 14'b0;
rw_done <= 1'b0;
rw_result <= 1'b0;
end
else begin
i2c_exec <= 1'b0;
rw_done <= 1'b0;
case(flow_cnt)
2'd0 : begin
wait_cnt <= wait_cnt + 1'b1; //延時計數
if(wait_cnt == WR_WAIT_TIME - 1'b1) begin //EEPROM寫操作延時完成
wait_cnt <= 1'b0;
if(i2c_addr == MAX_BYTE) begin //256個位元組寫入完成
i2c_addr <= 1'b0;
i2c_rh_wl <= 1'b1;
flow_cnt <= 2'd2;
end
else begin
flow_cnt <= flow_cnt + 1'b1;
i2c_exec <= 1'b1;
end
end
end
2'd1 : begin
if(i2c_done == 1'b1) begin //EEPROM單次寫入完成
flow_cnt <= 2'd0;
i2c_addr <= i2c_addr + 1'b1; //地址0~255分別寫入
i2c_data_w <= i2c_data_w + 1'b1; //資料0~255
end
end
2'd2 : begin
flow_cnt <= flow_cnt + 1'b1;
i2c_exec <= 1'b1;
end
2'd3 : begin
if(i2c_done == 1'b1) begin //EEPROM單次讀出完成
//讀出的值錯誤或者I2C未應答,讀寫測驗失敗
if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin
rw_done <= 1'b1;
rw_result <= 1'b0;
end
else if(i2c_addr == MAX_BYTE - 1'b1) begin //讀寫測驗成功
rw_done <= 1'b1;
rw_result <= 1'b1;
end
else begin
flow_cnt <= 2'd2;
i2c_addr <= i2c_addr + 1'b1;
end
end
end
default : ;
endcase
end
end
為什么會有個延時呢?這是因為,AT24C64官方手冊對寫入資料后,資料寫入完成的時間最大不超過 10ms,所以為了保證資料能夠正確寫入,單次寫入資料操作完成后,最好延時10ms的時間,本次實驗為了節省資料寫入的時間, WR_WAIT_TIME的值設定為 5000,即 5ms(輸入時鐘的周期為 1us 1us*5000=5ms),實測延時 5ms也可以正確寫入,這里不建議大家將寫入的間隔設定的過于短,否則會導致資料寫入失敗,也可以正確寫入,這里不建議大家將寫入的間隔設定的過于短,否則會導致資料寫入失敗,另外, EEPROM只有對寫操作有時間間隔要求,對讀操作沒有間隔要求,因此讀寫測驗模塊僅對寫操作增加時間間隔,
led_alarm
//reg define
reg rw_done_flag; //讀寫測驗完成標志
reg [24:0] led_cnt ; //led計數
//*****************************************************
//** main code
//*****************************************************
//讀寫測驗完成標志
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
rw_done_flag <= 1'b0;
else if(rw_done)
rw_done_flag <= 1'b1;
end
//錯誤標志為1時PL_LED0閃爍,否則PL_LED0常亮
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
led_cnt <= 25'd0;
led <= 1'b0;
end
else begin
if(rw_done_flag) begin
if(rw_result) //讀寫測驗正確
led <= 1'b1; //led燈常亮
else begin //讀寫測驗錯誤
led_cnt <= led_cnt + 25'd1;
if(led_cnt == L_TIME - 1'b1) begin
led_cnt <= 25'd0;
led <= ~led; //led燈閃爍
end
end
end
else
led <= 1'b0; //讀寫測驗完成之前,led燈熄滅
end
end
led常亮則代表讀寫正常,本實驗到此結束,另,i2c驅動模塊的狀態機寫法是很常用的一種寫法,在很多傳感器也都會有用到,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/413591.html
標籤:其他
