本文摘要:本文首先對I2C協議的通信模式和AT24C16-EEPROM晶元時序控制進行分析和理解,設計了一個i2c通信方案。人為按下寫操作按鍵後,FPGA(Altera EP4CE10)對EEPROM指定地址寫入位元組數據,並接後按下讀操作按鍵,讀取該地址上的一個位元組數據在數位管低兩位顯示出來。其中包 ...
本文摘要:本文首先對I2C協議的通信模式和AT24C16-EEPROM晶元時序控制進行分析和理解,設計了一個i2c通信方案。人為按下寫操作按鍵後,FPGA(Altera EP4CE10)對EEPROM指定地址寫入位元組數據,並接後按下讀操作按鍵,讀取該地址上的一個位元組數據在數位管低兩位顯示出來。其中包括了對此方案的Modelsim模擬測試,並且接續完成板級驗證。(過程筆記)
關鍵詞:EEPROM、I2C協議、Verilog HDL、FPGA
框圖設計:
輸入埠包括系統時鐘、複位信號、寫操作和讀操作按鍵,輸出埠包括IIC通信介面、數位管段選和位選信號。
共計用到了四種子模塊,分別是按鍵消抖、i2c通信數據處理、i2c通信控制、2khz時鐘分配。按邏輯鏈接成體,完成本文設計的測試方案。在調試過程中,為方便觀察操作反應,另外加入了兩個LED燈介面,具體框圖如下。
【I2C協議通信模式】
I2C協議(通常稱為IIC協議)是一種串列、同步、半雙工通信協議。I2C協議支持多主機功能,允許多個具備主控能力的設備在同一匯流排上競爭控制權,並通過硬體仲裁機制避免衝突。其使用串列數據線(SDA
)和串列時鐘線(SCL
)進行通信,標準模式的傳輸速率為100 kbit/s(Standard-mode, Sm),快速模式下的I2C傳輸速率提高至400 kbit/s(Fast-mode,Fm),在高速模式下可達3.4Mbit/s。
發送到SDA
線上的每個位元組必須為8位,且每次傳輸可以發送的位元組數量不受限制,每個位元組後必須跟一個ACK響應,位首先傳輸的是數據的最高位MSB,SDA線上的數據必須在SCL
時鐘的高電平周期保持穩定,數據線的高或低電平狀態只有在SCL
時鐘是低電平時才能改變。
傳輸信號類型:(見上圖)
- 啟動信號(START)(S條件):在
SCL
線處於高電平,SDA
上的數據由高向低轉換,則表示啟動IIC匯流排; - 應答信號(ACK):在接收到了8bit的信息後, 接收數據一方需要向發送信息的另一方傳遞預設的低電平脈衝作為信號,表明已經獲取了數據;
- 結束信號(STOP)(P條件):在
SCL
線處於高電平,SDA
上的數據由低向高轉換,則表示停止IIC匯流排。
起始(S)和停止(P)條件一般由主機產生,匯流排在起始條件後被認為處於忙的狀態,在停止條件的某段時間後匯流排被認為再次處於空閑狀態 。
由於I2C 匯流排沒有中央控制,其控制只能由地址或主機碼以及競爭主機發送的數據決定,匯流排也沒有任何定製的優先權。所以,當發生多主機通信時,需要一個控制仲裁機制來決定通信優先。
仲裁過程中的時鐘同步:
在I2C通信中,所有主機在SCL
線上生成自己的時鐘信號以傳輸數據。數據僅在時鐘高電平時有效,因此需要同步時鐘以實現逐位仲裁。時鐘同步通過線與連接實現,即SCL
線狀態由所有設備共同決定。
①見上圖中,當SCL線
從高變低時,所有設備開始計數其低電平周期。若某設備時鐘先變低,則它會保持SCL
線在低電平直到其時鐘再次變高。若此時有其他設備仍處於低電平周期,它們的時鐘變化不會改變SCL線
狀態,直至最長低電平周期的設備完成計數。
②一旦所有設備完成低電平周期,SCL
線釋放並變為高電平,隨後所有設備同步開始計數高電平周期。
③首先完成高電平周期的設備將再次拉低SCL
線。因此,SCL
時鐘的低電平周期由最長低電平周期的設備決定
,而高電平周期由最短高電平周期的設備
決定。(巧記:保證最長低電平周期)
仲裁判定優先:
主機只能在匯流排空閑時啟動傳輸,兩個或多個主機可能在起始條件的最小持續時間內產生一個起始條件,結果在匯流排上產生一個規範的起始條件(如下框S條件內)。當SCL
線是高電平時,仲裁在SDA
線發生,在其他主機保持低電平狀態時,首先拉高電平的主機將斷開數據輸出級,如下圖DATA1失去了通信的優先權。
仲裁可以持續多位,第一個階段是比較地址位。 如果各主機都嘗試定址同一器件,仲裁機制會持續到數據位。在仲裁過程中不會丟失信息(仲裁區間有效信息一致),丟失仲裁的主機可以產生時鐘脈衝直到丟失仲裁的該位元組末尾 。
補充:如果主機也結合了從機功能,並且在定址階段丟失仲裁,由於它存在是贏得仲裁的主機所定址的器件。因此,丟失仲裁的主機必須立即切換到從機模式。
【AT24C16時序分析】
AT24C16
EEPROM存儲晶元的相關信息:存儲容量為16Kbit,即2048位元組。晶元內部分成128頁,每頁16位元組,讀寫操作都是以位元組為基本單位。AT24C16具有低電壓工作、高速串列通信和硬體防寫等特點。(下圖為晶元引腳說明)
- 地址組成:AT24C16的器件地址包括高4位固定地址(1010)和用戶需設置的低3位地址(
A0、A1、A2
); - 地址設置:通過連接晶元的
A0、A1、A2
這3個引腳到VCC或GND來實現地址的低3位設置。例如這3個引腳均連接到VCC,則器件地址為1010_111。由於該 3 位只能組合出 8 種情況,因此,一個主機最多可以連接8個AT24C16存儲晶元。
1、EEPROM驅動寫時序進行分析
位元組寫入時序 (Byte Write):通信開始,由主機發送一個起始條件。緊接著,主機發送EEPROM的設備地址,選擇目標EEPROM晶元。如果EEPROM支持字地址定址,主機將接續發送一個或多個位元組的字地址來指定要寫入數據的記憶體位置。然後,主機寫入數據位元組,最高有效位(MSB)首先發送。 EEPROM在接收到每個位元組後,返回一個應答信號(ACK),應答信號是低電平脈衝,表示已成功接收位元組並準備接收下一個位元組或停止信號。待所有數據位元組都發送完畢後,主機發送停止信號(STOP)來結束目前的通信。
頁寫入時序 (Page Write):與位元組寫入時序相比,頁寫入時序類似,但數據被分組為多個位元組,作為一個連續的數據塊發送。 發送起始設備地址後,接續發送一系列的數據位元組(DATA (n), DATA (n + 1),......, DATA (n + x))單個位元組發送完成,EEPROM都會返回一個應答信號(ACK)。
補充:所有I2C設備均支持單位元組數據寫入操作,但只有部分I2C設備支持頁寫操作;且支持頁寫操作的設備,一次頁寫操作寫入的位元組數不能超過設備單頁包含的存儲單元數。
2、讀時序進行分析
IIC協議支持三種EEPROM讀時序。首先是指定地址單位元組讀取方式:操作時序和寫時序類似,不同的是,在寫入目標地址後,主機的操作方式換為讀取。
順序讀取時序:主機發送一個起始地址後,EEPROM開始連續發送數據(DATA n, DATA n+1, DATA n+2, ... DATA n+X)。在每個數據位元組的末尾,EEPROM同樣等待主機的應答信號(ACK)。主機在接收到每個數據位元組後發送ACK信號,直到所有數據都被接收。當所有數據發送完畢或主機決定停止時,它會發送一個停止信號(STOP),結束順序讀取操作。
隨機讀取時序:起始條件後,發送設備地址和讀寫位(R/W=1),接著發送隨機地址。EEPROM發送數據,主機接收後發送ACK。所有數據發送完畢後,發送停止信號。EEPROM在接收到有效地址後,通過SDA線連續發送數據(DATA n, DATA n+1, ...)。在每個數據位元組的末尾,EEPROM等待主機的應答信號(ACK)。主機待接收到數據位元組後,通過發送ACK信號來確認接收,並告知EEPROM可以繼續發送下一個數據位元組。
【時序邏輯設計方案】
1、時鐘處理與i2c通信啟動
系統時鐘頻率為50Mhz,頻率很高,這裡首先需要從系統時鐘分頻提供一個1Mhz的i2c_clk
時鐘用於i2c通信處理。下圖中cnt_clk
為一個時鐘分頻計數器。
以寫操作為例,待寫觸發信號write
發生,拉高寫有效信號write_valid
,並且為使i2c_clk
時鐘信號要在上升沿檢測到其高電平,write
觸發要保持≥2個時鐘周期,對應100個系統時鐘周期。這裡設定有效計數器cnt_wr
,計數150個系統周期,即300ns,滿足要求。計數完成,拉低寫有效信號write_valid
完成觸發操作,具體時序見下圖,當然,寫有效時序也是如此。
寫有效信號write_valid
觸發,在其拉高的下一個i2c_clk
時鐘上升沿,觸發寫使能wr_en
信號,並且,啟動cnt_start
計數器,計數值為5000(1Mhz ->1us)≈ 5ms。這是因為AT64C16單次操作間隔周期需要保持5ms空閑狀態。完成計數後,啟動i2c_start
起始信號觸發,後面就是i2c通信的具體流程。當完成操作後,i2c_end
結束信號標誌通信完成,從而拉低寫使能wr_en
信號。過程中,設定了寫操作的目標地址為16'H00_4D,寫入數據為8'H8A。
註意,大部分信號時鐘觸發源都是i2c_clk
,而不是系統時鐘。當i2c_start
起始信號觸發,i2c_clk
啟動,作為i2c_scl
通信時鐘的觸發源。同時,根據下圖邏輯,狀態機狀態由IDLE
轉到START1
。起始信號僅占一個i2c_clk
周期。cnt_i2c_clk
是i2c_scl
通信時鐘線的計數器,計數值範圍0-3,使得i2c_scl
周期為250khz。
完成後,進入SEND_ADDR
狀態,向IIC匯流排發生器件地址1010011
+控制位0
,表示寫入。比特位計數器cnt_bit
用於比特位0-7的計數。在ACK1
狀態,等待匯流排回應。ACK
是從機,即EEPROM,傳回來的低電平信號。對於主機來說SDA
線此時是高阻態,匯流排的上拉電阻將電位鉗位在高電位(sda
為inout類型)。所以在後面,可以看到這裡是處於高阻態的。通信後面的流程根據狀態機的跳轉,在時序圖表現得明顯。在STOP
停止狀態,FPGA向EEPROM發送停止信號,一次單位元組數據寫操作完成,拉低使能信號i2c_clk_en
和we_en
,並且及時觸發i2c_end
,隨後狀態機跳回IDLE
初始狀態。
讀控制時序處理與寫時序處理流程類似,具體見上圖,不同在ACK3
狀態的跳轉,接收一個周期回應信號後,進入的是起始狀態START2
,從而觸發第二次起始信號,再次寫入器件地址,讀取一位元組數據。rd_data_reg
作為一個暫存器,存儲讀取到的位元組數據,待完成後,轉錄到rd_data
。在N_ACK
等待回應一個高電平回應信號,將i2c_scl
由地拉高產生一個停止信號,同時觸發i2c_end
。兩個操作過程具體狀態判斷條件,需具體分析,但都類似。
2、STATE狀態機轉換邏輯
i2c整個通信的過程可以分為很多階段,將它們細分開:空閑、起始、器件地址寫入、字/位元組地址寫入、各級響應、位元組數據寫入、讀取位元組數據、停止。主機視角對從設備操作過程,具體可見如下。
設備啟動,主機處於空閑狀態IDLE
,待檢測到寫/讀操作信號後,程式轉入到第一個起始狀態START_1
,表現為SDA
線產生一個由高到低的電平轉換信號。接續進入器件地址寫入狀態SEND_ADDR
,根據硬體圖,這裡預先設定器件是1010_011。器件確認後,向主機發生一個低電平回應ACK_1
。寄存器存儲地址類型分為字地址和位元組地址,分別對應不同的狀態切換。完成後ACK_3
響應後,寫操作,進入位元組數據寫入狀態WR_DATA
,寫入一個預先數據,並回應ACK_4
進入結束停止狀態STOP
。而ACK_3
響應後,進入讀操作流程,需再次發生起始START_2
,接後寫入讀取目標地址,等待從機完成數據發送後,進入結束停止狀態STOP
。最終保持一定周期,再次轉換至空閑狀態IDLE
。
根據時序圖邏輯,確定狀態機狀態轉換條件:
當前狀態 | 目標跳轉狀態 | 跳轉條件 | 操作類型 |
---|---|---|---|
IDLE | START1 | i2c_start拉高 | 讀/寫 |
START1 | SEND_ADDR | cnt_i2c_clk == 2'd3(一個i2c_clk周期 ) | 讀/寫 |
SEND_ADDR | ACK1 | (cnt_i2c_clk == 2'd3)&&(!ack) | 讀/寫 |
ACK1 | SEND_BH | (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) | 讀/寫 |
SEND_BH | ACK2 | (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) | 讀/寫 |
ACK2 | SEND_BL | (cnt_i2c_clk == 2'd3)&&(!ack) | 讀/寫 |
SEND_BL | ACK3 | (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) | 讀/寫 |
ACK3 | WR_DATA | (cnt_i2c_clk == 2'd3)&&(!ack) | 讀/寫 |
WR_DATA | ACK4 | (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) | 寫 |
ACK4 | STOP | (cnt_i2c_clk == 2'd3)&&(!ack) | 寫 |
ACK3 | START2 | rd_en拉高 | 讀 |
START2 | SEND_RA | cnt_i2c_clk == 2'd3 | 讀 |
SEND_RA | ACK5 | (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) | 讀 |
ACK5 | RD_DATA | (cnt_i2c_clk == 2'd3)&&(!ack) | 讀 |
RD_DATA | N_ACK | (cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3) | 讀 |
N_ACK | STOP | cnt_i2c_clk == 2'd3 | 讀 |
STOP | IDLE | (cnt_bit == 3'd3)&&(cnt_i2c_clk == 2'd3) | 讀/寫 |
對上面的表格可以總結,ACK1
- ACK5
條件都是相同的,只是跳轉目標不同,單位元組讀/寫結束後狀態跳轉判斷條件相同,兩個起始狀態跳轉判斷條件也是相同的 ,N_ACK
和STOP
的跳轉註意區別。跳轉機Verilog HDL具體程式如下:
//dispose state condition
always @(posedge i2c_clk or negedge sys_rst)begin
if(!sys_rst) state <= IDLE;
else case(state)
IDLE: if(i2c_start) state <= START1;else state<= state;
START1: if(cnt_i2c_clk == 2'd3) state <= SEND_ADDR;
else state <= state;
SEND_ADDR:
if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
state <= ACK1;
else state <= state;
ACK1: if((cnt_i2c_clk == 2'd3)&&(!ack))begin
if(addr_num)state <= SEND_BH;
else state<= SEND_BL;
end else state <= state;
SEND_BH:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
state <= ACK2;
else state<= state;
ACK2: if((cnt_i2c_clk == 2'd3)&&(!ack))
state <= SEND_BL;
else state <= state;
SEND_BL:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
state <= ACK3;
else state <= state;
ACK3: if((cnt_i2c_clk == 2'd3)&&(!ack))begin
if(wr_en) state <= WR_DATA;
else if(rd_en) state <= START2;
end
else state <= state;
WR_DATA:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
state <= ACK4;
else state <= state;
ACK4: if((cnt_i2c_clk == 2'd3)&&(!ack))
state <= STOP;
else state <= state;
START2: if(cnt_i2c_clk == 2'd3) state <= SEND_RA;
else state <= state;
SEND_RA:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
state <= ACK5;
else state <= state;
ACK5: if((cnt_i2c_clk == 2'd3)&&(!ack))
state <= RD_DATA;
else state <= state;
RD_DATA:if((cnt_bit == 3'd7)&&(cnt_i2c_clk == 2'd3))
state <= N_ACK;
else state <= state;
N_ACK: if(cnt_i2c_clk == 2'd3)
state <= STOP;
else state <= state;
STOP: if((cnt_bit == 3'd3)&&(cnt_i2c_clk == 2'd3))
state <= IDLE;
else state <= state;
endcase
end
STATE狀態機在整個程式過程中十分重要,i2c_scl
時鐘電平和i2c_sda
輸出電平情況,也要根據其狀態的不同做出具體分析。
//dispose i2c_scl sequence
always@(*)begin
case(state)
IDLE: i2c_scl <= 1'b1;
START1:
if(cnt_i2c_clk == 2'd3) i2c_scl <= 1'b0;
else i2c_scl <= 1'b1;
SEND_ADDR,ACK1,SEND_BH,ACK2,SEND_BL,
ACK3,WR_DATA,ACK4,START2,SEND_RA,ACK5,RD_DATA,N_ACK:
if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2)) i2c_scl <= 1'b1;
else i2c_scl <= 1'b0;
STOP:
if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0)) i2c_scl <= 1'b0;
else i2c_scl <= 1'b1;
default: i2c_scl <= 1'b1;
endcase
end
//dispose i2c_sda_reg & rd_data_reg sequence
always @(*)begin
case(state)
IDLE: begin
i2c_sda_reg <= 1'b1;
rd_data_reg <= 8'd0;
end
START1: if(cnt_i2c_clk == 2'd0) i2c_sda_reg <= 1'b1;
else i2c_sda_reg <= 1'b0;
SEND_ADDR: if(cnt_bit <= 3'd6) i2c_sda_reg<= DEVICE_ADDR[6-cnt_bit];
else i2c_sda_reg<= 1'b0;
ACK1: i2c_sda_reg<= 1'b1;
SEND_BH: i2c_sda_reg<= byte_addr[15-cnt_bit];
ACK2: i2c_sda_reg<= 1'b1;
SEND_BL: i2c_sda_reg<= byte_addr[7-cnt_bit];
ACK3: i2c_sda_reg<= 1'b1;
WR_DATA: i2c_sda_reg<= wr_data[7-cnt_bit];
ACK4: i2c_sda_reg<= 1'b1;
START2: if(cnt_i2c_clk <= 2'd1)i2c_sda_reg <= 1'b1;
else i2c_sda_reg <= 1'b0;
SEND_RA: if(cnt_bit <= 3'd6)i2c_sda_reg<= DEVICE_ADDR[6-cnt_bit];
else i2c_sda_reg<= 1'b1;
ACK5: i2c_sda_reg<= 1'b1;
RD_DATA: if(cnt_i2c_clk == 2'd2) rd_data_reg[7-cnt_bit] <= sda_in;
else rd_data_reg <= rd_data_reg;
N_ACK: i2c_sda_reg<= 1'b1;
STOP: if((cnt_bit==3'd0)&&(cnt_i2c_clk<2'd3))i2c_sda_reg <= 1'b0;
else i2c_sda_reg<= 1'b1;
default: begin
i2c_sda_reg<= 1'b1;
rd_data_reg <= rd_data_reg;
end
endcase
end
其他信號在此不再列舉,它們的判斷條件相對狀態機來說處理起來比較簡單,邏輯也很清晰。
【階段模擬驗證】
下麵看下程式的模擬結果,查看四個:讀/寫操作啟動時刻、寫操作整體時序、讀操作整體時序、讀/寫操作結束時序。
操作啟動:write
信號發生,write_valid
拉高,併在下一i2c_clk
上升沿拉高wr_en
使能信號,等待cnt_wr
完成計數150後,放低write_valid
。cnt_start
待wr_en
拉高後,開始計數。
寫操作(這裡器件地址設定1010_011):cnt_start
計數到4900處,發起i2c_start
觸發,state
值變為4'h01,表示IDLE
進入START1
狀態,依次完成後續的狀態切換。STOP
狀態下,待cnt_bit
保持到3‘h3,結束停止狀態返回IDLE
。wr_en
拉低,i2c_end
拉高一個周期後放下。從下麵的模擬圖可以看到,整個寫操作的時序表現正常。
讀操作及讀操作結束模擬圖如下:
【最終上機驗證】
Quartus II生成框圖如下,兩個按鍵做為輸入觸發,輸出包括IIC通信匯流排SDA和SCL,數位管段選SEG_SEL和位選SEG_LED,以及外部掛載的兩個LED。程式燒錄後,設備低二位數位管顯示00
,LED均處於熄滅狀態,按下key_wr後,led_wr點亮,表示寫操作啟動;接續,按下key_rd後,led_rd點亮,表示讀操作啟動,並且數位管顯示讀取數據8A
。最終得到的現象與預期一致。
文獻參考:
[1] I2C匯流排規範 (https://sumcu.suda.edu.cn/_upload/article/files/74/e5/d4eb93de45808d71ad8aad542ede/a3cb5873-aaf4-4af0-9e5f-521793fbba46.pdf);
[2] 基於I2C協議的EEPROM驅動控制(https://doc.embedfire.com/fpga/altera/ep4ce10_mini/zh/latest/fpga/I2C.html);
[3] 王榮華. 可配置的IIC協議控制器IP核的設計[D]. 黑龍江:哈爾濱理工大學,2011.( DOI:10.7666/d.y2012472);
本篇文章中使用的Verilog程式模塊,若有需見網頁左欄Gitee倉庫鏈接:https://gitee.com/silly-big-head/little-mouse-funnyhouse/tree/FPGA-Verilog/