很多數字傳感器、數字控制的芯片(DDS、串行ADC、串行DAC)都是通過IIC總線來和控制器通信的。不過IIC協議仍然是一種慢速的通信方式,標準IIC速率為100kbit/s,快速模式速率為400kbit/s。本文致力于講述如何用計數器控制和分頻時鐘控制兩種方式完成IIC的讀寫操作。
IIC協議
IIC協議是一種多機通訊,由SDA數據線和SCL時鐘線構成串行總線,所有的IIC設備都可以掛載到總線上,但每個設備都有唯一的設備讀地址和設備寫地址。在使用IIC作為數字接口的芯片datasheet中都可以看到該設備的設備讀/寫地址情況,并可以查找到相應的讀寫時序,以及對速率的要求。下圖是一個通用的IIC協議時序:
我們可以總結出五種IIC協議的時序狀態:
??1. 空閑狀態,當SDA和SCL兩條信號線都處于高電平時總線處于空閑狀態。
??2. 開始信號,SCL為高電平期間SDA信號線上產生了下降沿標志著的一次數據傳輸的開始。開始信號應當由主機發起。
??3. 數據傳輸,在SCL同步控制下SDA串行的傳送每一位,因此傳送8bits的數據需要8個SCL時鐘。SCL為高電平時期SDA電平狀態必須穩定;SCL為低電平期間才允許SDA改變狀態。
??4. 應答信號,IIC總線上每傳送一個8位字節,第9個脈沖期間便會釋放總線,由接收器發出一個應答信號,反饋有沒有成功接收。
??5. 停止信號,在SCL保持高電平期間,將SDA信號線釋放恢復到高電平,標志一次數據傳輸的結束,IIC總線也重新回到了空閑狀態。
計數器控制IIC讀寫
在“FPGA基礎設計(三):UART串口通信”中已經接觸到了使用計數器控制時序的方法,這個方法在控制IIC通信時同樣實用。一次完整的寫入操作如下所示:
case( i )
0: // iic Start
begin
isOut 《= 1; //SDA端口輸出
if( C1 == 0 ) rSCL 《= 1‘b1;
else if( C1 == 200 ) rSCL 《= 1’b0; //SCL由高變低
if( C1 == 0 ) rSDA 《= 1‘b1;
else if( C1 == 100 ) rSDA 《= 1’b0; //SDA先由高變低
if( C1 == 250 -1) begin C1 《= 9‘d0; i 《= i + 1’b1; end
else C1 《= C1 + 1‘b1;
end
begin rData 《= {4’b1010, 3‘b000, 1’b0}; i 《= 5‘d7; Go 《= i + 1’b1; end
2: // Wirte Word Addr
begin rData 《= Addr_Sig; i 《= 5‘d7; Go 《= i + 1’b1; end
3: // Write Data
begin rData 《= WrData; i 《= 5‘d7; Go 《= i + 1’b1; end
4: //iic Stop
begin
isOut 《= 1‘b1;
if( C1 == 0 ) rSCL 《= 1’b0;
else if( C1 == 50 ) rSCL 《= 1‘b1; //SCL先由低變高
if( C1 == 0 ) rSDA 《= 1’b0;
else if( C1 == 150 ) rSDA 《= 1‘b1; //SDA由低變高
if( C1 == 250 -1 ) begin C1 《= 9’d0; i 《= i + 1‘b1; end
else C1 《= C1 + 1’b1;
end
5:
begin isDone 《= 1‘b1; i 《= i + 1’b1; end //寫I2C 結束
6:
begin isDone 《= 1‘b0; i 《= 5’d0; end
7,8,9,10,11,12,13,14: //發送Device Addr/Word Addr/Write Data
begin
isOut 《= 1‘b1;
rSDA 《= rData[14-i]; //高位先發送
if( C1 == 0 ) rSCL 《= 1’b0;
else if( C1 == 50 ) rSCL 《= 1‘b1;
else if( C1 == 150 ) rSCL 《= 1’b0;
if( C1 == F250K -1 ) begin C1 《= 9‘d0; i 《= i + 1’b1; end
else C1 《= C1 + 1‘b1;
end
15: // waiting for acknowledge
begin
isOut 《= 1’b0; //SDA端口改為輸入
if( C1 == 100 ) isAck 《= SDA;
if( C1 == 0 ) rSCL 《= 1‘b0;
else if( C1 == 50 ) rSCL 《= 1’b1;
else if( C1 == 150 ) rSCL 《= 1‘b0;
if( C1 == F250K -1 ) begin C1 《= 9’d0; i 《= i + 1‘b1; end
else C1 《= C1 + 1’b1;
end
16:
if( isAck != 0 ) i 《= 5‘d0;
else i 《= Go;
endcase
向IIC總線寫數據時,需要依次寫入待寫入的設備寫地址、設備中的寫地址和待寫入的數據共3個8bits字節數據。i代表總線上不同的狀態,通過計數器來控制狀態之間的跳轉。i為0時發出開始信號;i為7~14時控制8bits數據的發送;i為1、2、3時分別為設備地址、字節地址和數據,依次調用7-14完成數據的傳輸;其余還有停止位、應答位、IIC通信完成置位等狀態。
從器件中讀取數據的方法與此一樣,只不過通常都需要先向IIC總線寫入待讀取的設備地址和器件地址,之后再讀數據。讀數據整體過程比寫數據要麻煩一點,但只要控制好狀態之間跳轉的過程即可。
分頻時鐘控制IIC讀寫
由計數器控制通信時序的方法優點是很靈活,幾乎所有的時序方法都可以用這種方法完成;缺點就是太麻煩,需要控制好狀態之間的跳轉,時序越復雜使用越麻煩,其實在“FPGA采集-傳輸-顯示系統(二):基于FPGA的溫度采集和以太網傳輸”中,我對DS18B20的時序控制就是采用計數器控制的方法。DS18B20的時序要求較多,因此其中的狀態跳轉已經相當復雜。
其實在控制IIC這種時鐘速率固定的串行協議時,還可以在外部分頻或PLL生成一個低頻的通信時鐘,用這個時鐘來控制數據傳輸過程。如下所示:
always@(posedge clock_i2c)
begin
if(reset_n==1’b0) begin
tr_end《=0;
ack1《=1;
ack2《=1;
ack3《=1;
sclk《=1;
reg_sdat《=1;
end
else
case(cyc_count)
0:begin ack1《=1;ack2《=1;tr_end《=0;sclk《=1;reg_sdat《=1;end
1:reg_sdat《=0; //開始傳輸
2:sclk《=0;
3:reg_sdat《=i2c_data[23];
4:reg_sdat《=i2c_data[22];
5:reg_sdat《=i2c_data[21];
6:reg_sdat《=i2c_data[20];
7:reg_sdat《=i2c_data[19];
8:reg_sdat《=i2c_data[18];
9:reg_sdat《=i2c_data[17];
10:reg_sdat《=i2c_data[16];
11:reg_sdat《=1; //應答信號
12:begin reg_sdat《=i2c_data[15];ack1《=i2c_sdat;end
13:reg_sdat《=i2c_data[14];
14:reg_sdat《=i2c_data[13];
15:reg_sdat《=i2c_data[12];
16:reg_sdat《=i2c_data[11];
17:reg_sdat《=i2c_data[10];
18:reg_sdat《=i2c_data[9];
19:reg_sdat《=i2c_data[8];
20:reg_sdat《=1; //應答信號
21:begin reg_sdat《=i2c_data[7];ack2《=i2c_sdat;end
22:reg_sdat《=i2c_data[6];
23:reg_sdat《=i2c_data[5];
24:reg_sdat《=i2c_data[4];
25:reg_sdat《=i2c_data[3];
26:reg_sdat《=i2c_data[2];
27:reg_sdat《=i2c_data[1];
28:reg_sdat《=i2c_data[0];
29:reg_sdat《=1; //應答信號
30:begin ack3《=i2c_sdat;sclk《=0;reg_sdat《=0;end
31:sclk《=1;
32:begin reg_sdat《=1;tr_end《=1;end //IIC傳輸結束
endcase
可以看到這個always塊的敏感目標為分頻后的IIC時鐘信號clock_i2c,整個傳輸過程一目了然。這兩種控制時序的方法不僅適合于IIC協議,還適合于其它的串行協議。
評論
查看更多