基于接近式傳感器的智能接近系統(tǒng)設(shè)計
實驗任務(wù)
實驗?zāi)康?/h4>本節(jié)實驗主要學習I2C總線工作原理、協(xié)議及相關(guān)知識,掌握FPGA驅(qū)動I2C設(shè)備的原理及方法,了解輸入輸出型端口的模型及控制實現(xiàn),最終實現(xiàn)智能接近系統(tǒng)的總體設(shè)計。
本文引用地址:http://butianyuan.cn/article/202312/453885.htm設(shè)計框圖
根據(jù)前面的實驗解析我們可以得知,該設(shè)計可以拆分成兩個功能模塊實現(xiàn),
實驗原理
I2C總線介紹
I2C總線是由Philips公司開發(fā)的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接于總線上的器件之間傳送信息。
主器件用于啟動總線傳送數(shù)據(jù),并產(chǎn)生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件.在總線上主和從、發(fā)和收的關(guān)系不是恒定的,而取決于此時數(shù)據(jù)傳送方向。如果主機要發(fā)送數(shù)據(jù)給從器件,則主機首先尋址從器件,然后主動發(fā)送數(shù)據(jù)至從器件,最后由主機終止數(shù)據(jù)傳送;如果主機要接收從器件的數(shù)據(jù),首先由主器件尋址從器件.然后主機接收從器件發(fā)送的數(shù)據(jù),最后由主機終止接收過程。在這種情況下.主機負責產(chǎn)生定時時鐘和終止數(shù)據(jù)傳送。
字節(jié)格式
發(fā)送到SDA 線上的每個字節(jié)必須為8 位,每次傳輸可以發(fā)送的字節(jié)數(shù)量不受限制。每個字節(jié)后必須跟一個響應(yīng)位。首先傳輸?shù)氖菙?shù)據(jù)的最高位(MSB),如果從機要完成一些其他功能后(例如一個內(nèi)部中斷服務(wù)程序)才能接收或發(fā)送下一個完整的數(shù)據(jù)字節(jié),可以使時鐘線SCL 保持低電平,迫使主機進入等待狀態(tài),當從機準備好接收下一個數(shù)據(jù)字節(jié)并釋放時鐘線SCL 后數(shù)據(jù)傳輸繼續(xù)。
啟動和停止
在時鐘線SCL保持高電平期間,數(shù)據(jù)線SDA上的電平被拉低(即負跳變),定義為I2C總線總線的啟動信號,它標志著一次數(shù)據(jù)傳輸?shù)拈_始。啟動信號是一種電平跳變時序信號,而不是一個電平信號。啟動信號是由主控器主動建立的,在建立該信號之前I2C總線必須處于空閑狀態(tài)。
在時鐘線SCL保持高電平期間,數(shù)據(jù)線SDA被釋放,使得SDA返回高電平(即正跳變),稱為I2C總線的停止信號,它標志著一次數(shù)據(jù)傳輸?shù)慕K止。停止信號也是一種電平跳變時序信號,而不是一個電平信號,停止信號也是由主控器主動建立的,建立該信號之后,I2C總線將返回空閑狀態(tài)。
應(yīng)答響應(yīng)
數(shù)據(jù)傳輸必須帶響應(yīng),相關(guān)的響應(yīng)時鐘脈沖由主機產(chǎn)生。在響應(yīng)的時鐘脈沖期間,發(fā)送器釋放SDA 線(上拉電阻拉高),接收器必須將SDA 線拉低,使它在這個時鐘脈沖的高電平期間保持穩(wěn)定的低電平,這種情況下是應(yīng)答,如果在這個時鐘脈沖的高電平期間SDA線沒有被拉低則表示沒有應(yīng)答。通常被尋址的接收器在接收到的每個字節(jié)后,必須產(chǎn)生一個應(yīng)答。當從機接收器不應(yīng)答時,主機產(chǎn)生一個停止或重復(fù)起始條件。
通信速率
常見的I2C總線依傳輸速率的不同而有不同的模式:標準模式(100 Kbit/s)、低速模式(10 Kbit/s),但時鐘頻率可被允許下降至零,這代表可以暫停通信。而新一代的I2C總線可以和更多的節(jié)點(支持10比特長度的地址空間)以更快的速率通信:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。
APDS-9901模塊連接
STEP BaseBoard V3.0底板上的接近光傳感器APDS-9901模塊電路圖如下(上拉電阻未顯示):
上圖為接近光傳感器APDS-9901模塊電路,與FPGA硬件接口有I2C總線(SCL、SDA)和中斷信號INT,APDS-9901是博通公司的集成環(huán)境光ALS、紅外光IR和接近距離傳感器,具有體積小、低功耗等優(yōu)點,被大量應(yīng)用于手機、筆記本、相機、液晶顯示器等電子產(chǎn)品上,環(huán)境光ALS可以根據(jù)外部環(huán)境調(diào)節(jié)設(shè)備屏幕顯示亮度,接近距離傳感器可以根據(jù)應(yīng)用場景實現(xiàn)產(chǎn)品對應(yīng)應(yīng)用,例如接聽電話時控制手機關(guān)閉顯示等,接口采用I2C總線能夠支持400KHz的I2C快速模式。
雙向端口設(shè)計
可綜合Verilog模塊設(shè)計中必須有端口存在,端口有輸入input,輸出output,雙向inout,對于輸入和輸出型端口我們很好理解,我們來了解一下雙向端口信號的處理。
在芯片中為了管腳復(fù)用,很多管腳都是雙向的,既可以輸入也可以輸出。在Verilog中即為inout型端口。Inout端口的實現(xiàn)是使用三態(tài)門,三態(tài)門的第三個狀態(tài)是高阻態(tài)Z。在實際電路中高阻態(tài)意味著響應(yīng)的管腳懸空、斷開。
當inout用作輸出時,就像平常一樣。當inout用作輸入時,需要設(shè)為高阻態(tài),這樣其電平就可以由外部輸入信號決定了(這是高阻態(tài)的特性)。
雙向端口應(yīng)用案例:
module bid
(
input out_en,
input a,
inout b,
output c
);
assign b = out_en? a : 1'bz;
assign c = b;
endmodule
APDS-9901驅(qū)動設(shè)計
通過前面的了解,我們對于整個I2C總線的驅(qū)動原理有了一定的了解,接下來我們根據(jù)APDS-9901的芯片手冊了解其驅(qū)動方法及參數(shù)要點。
通過APDS-9901時序參數(shù)了解,APDS-9901支持I2C通信400KHz快速模式同時兼容100KHz的標準模式,還有兩種模式下時序中的各種時間參數(shù),本例中我們就采用標準模式完成驅(qū)動設(shè)計。
首先我們分頻得到400KHz的時鐘,整個設(shè)計都基于該時鐘完成,程序?qū)崿F(xiàn)如下:
//使用計數(shù)器分頻產(chǎn)生400KHz時鐘信號
clk_400khzreg clk_400khz;
reg [9:0] cnt_400khz;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_400khz <= 10'd0;
clk_400khz <= 1'b0;
end else if(cnt_400khz >= CNT_NUM-1) begin
cnt_400khz <= 10'd0;
clk_400khz <= ~clk_400khz;
end else begin
cnt_400khz <= cnt_400khz + 1'b1;
end
end
I2C時序可以分解成基本單元(啟動、停止、發(fā)送、接收、發(fā)應(yīng)答、讀應(yīng)答),整個I2C通信都是由這些單元按照不同的順序組合,我們設(shè)計一個狀態(tài)機,將這些基本單元做成狀態(tài),控制狀態(tài)機的跳轉(zhuǎn)就能實現(xiàn)I2C通信時序。主機每次發(fā)送數(shù)據(jù)都要接收判斷從機的響應(yīng),每次接收數(shù)據(jù)也要向從機發(fā)送響應(yīng),所以發(fā)送單元和讀應(yīng)答單元可以合并,接收單元和寫應(yīng)答單元可以合并。
啟動時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
START:begin //I2C通信時序中的起始START
if(cnt_start >= 3'd5) cnt_start <= 1'b0; //對START中的子狀態(tài)執(zhí)行控制cnt_start
else cnt_start <= cnt_start + 1'b1;
case(cnt_start)
3'd0: begin sda <= 1'b1;
scl <= 1'b1;
end //將SCL和SDA拉高,保持4.7us以上
3'd1: begin sda <= 1'b1;
scl <= 1'b1;
end //每個周期2.5us,需要兩個周期
3'd2: begin sda <= 1'b0;
end //SDA拉低到SCL拉低,保持4.0us以上
3'd3: begin sda <= 1'b0;
end //clk_400khz每個周期2.5us,需要兩個周期
3'd4: begin scl <= 1'b0;
end //SCL拉低,保持4.7us以上
3'd5: begin scl <= 1'b0;
state <= state_back;
end //每個周期2.5us,兩個周期
default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài)
endcase
end
發(fā)送單元和讀應(yīng)答單元合并,時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
WRITE:begin //I2C通信時序中的寫操作WRITE和相應(yīng)判斷操作ACK
if(cnt <= 3'd6) begin //共需要發(fā)送8bit的數(shù)據(jù),這里控制循環(huán)的次數(shù)
if(cnt_write >= 3'd3) begin cnt_write <= 1'b0;
cnt <= cnt + 1'b1;
end
else begin cnt_write <= cnt_write + 1'b1;
cnt <= cnt;
end
end else begin
if(cnt_write >= 3'd7) begin cnt_write <= 1'b0;
cnt <= 1'b0;
end //復(fù)位變量
else begin cnt_write <= cnt_write + 1'b1;
cnt <= cnt;
end
end
case(cnt_write)
//按照I2C的時序傳輸數(shù)據(jù)
3'd0: begin scl <= 1'b0;
sda <= data_wr[7-cnt];
end //SCL拉低,SDA輸出
3'd1: begin scl <= 1'b1;
end //SCL拉高,保持4.0us以上
3'd2: begin scl <= 1'b1;
end //clk_400khz每個周期2.5us,需要兩個周期
3'd3: begin scl <= 1'b0;
end //SCL拉低,準備發(fā)送下1bit的數(shù)據(jù)
//獲取從設(shè)備的響應(yīng)信號并判斷
3'd4: begin sda <= 1'bz;
end //釋放SDA線,準備接收從設(shè)備的響應(yīng)信號
3'd5: begin scl <= 1'b1;
end //SCL拉高,保持4.0us以上
3'd6: begin ack_flag <= i2c_sda;
end //獲取從設(shè)備的響應(yīng)信號
3'd7: begin scl <= 1'b0;
if(ack_flag)state <= state;
else state <= state_back;
end //SCL拉低,如果不應(yīng)答循環(huán)寫
default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài)
endcase
end
接收單元和寫應(yīng)答單元合并,時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
READ:begin //I2C通信時序中的讀操作READ和返回ACK的操作
if(cnt <= 3'd6) begin //共需要接收8bit的數(shù)據(jù),這里控制循環(huán)的次數(shù)
if(cnt_read >= 3'd3) begin cnt_read <= 1'b0;
cnt <= cnt + 1'b1;
end
else begin cnt_read <= cnt_read + 1'b1;
cnt <= cnt;
end
end else begin
if(cnt_read >= 3'd7) begin cnt_read <= 1'b0;
cnt <= 1'b0;
end //復(fù)位變量值
else begin cnt_read <= cnt_read + 1'b1;
cnt <= cnt;
end
end
case(cnt_read)
//按照I2C的時序接收數(shù)據(jù)
3'd0: begin scl <= 1'b0;
sda <= 1'bz;
end //SCL拉低,釋放SDA線
3'd1: begin scl <= 1'b1;
end //SCL拉高,保持4.0us以上
3'd2: begin data_r[7-cnt] <= i2c_sda;
end //讀取從設(shè)備返回的數(shù)據(jù)
3'd3: begin scl <= 1'b0;
end //SCL拉低,準備接收下1bit的數(shù)據(jù)
//向從設(shè)備發(fā)送響應(yīng)信號
3'd4: begin sda <= ack;
end //發(fā)送響應(yīng)信號,將前面接收的數(shù)據(jù)鎖存
3'd5: begin scl <= 1'b1;
end //SCL拉高,保持4.0us以上
3'd6: begin scl <= 1'b1;
end //SCL拉高,保持4.0us以上
3'd7: begin scl <= 1'b0;
state <= state_back;
end //SCL拉低
default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài)
endcase
end
停止時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
STOP:begin //I2C通信時序中的結(jié)束STOP
if(cnt_stop >= 3'd5) cnt_stop <= 1'b0; //對STOP中的子狀態(tài)執(zhí)行控制cnt_stop
else cnt_stop <= cnt_stop + 1'b1;
case(cnt_stop)
3'd0: begin sda <= 1'b0;
end //SDA拉低,準備STOP
3'd1: begin sda <= 1'b0;
end //SDA拉低,準備STOP
3'd2: begin scl <= 1'b1;
end //SCL提前SDA拉高4.0us
3'd3: begin scl <= 1'b1;
end //SCL提前SDA拉高4.0us
3'd4: begin sda <= 1'b1;
end //SDA拉高
3'd5: begin sda <= 1'b1;
state <= state_back;
end //完成STOP操作
default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài)
endcase
end
基本單元都有了,接下來我們需要了解APDS-9901驅(qū)動的流程,手冊上看到APDS-9901芯片有很多寄存器,有的配置工作模式,有的配置功能使能,有的返回結(jié)果數(shù)據(jù),這個需要大家自己查看芯片手冊,這里不作講解。
手冊給用戶提供了基本功能的C實例代碼,流程如下:
WriteRegData (0, 0); //Disable and Powerdown
WriteRegData (1, 0xff); // 2.7 ms – minimum ALS integration time
WriteRegData (2, 0xff); // 2.7 ms – minimum Prox integration time
WriteRegData (3, 0xff); // 2.7 ms – minimum Wait time
WriteRegData (0xe, 1); // Minimum prox pulse count
WriteRegData (0xf, 0x20); // CH1 Diode
WriteRegData (0,0x0f); //Enable WEN PEN AEN PON
Wait(12); //Wait for 12 ms
CH0_data = Read_Word(0x14);
CH1_data = Read_Word(0x16);
Prox_data = Read_Word(0x18);
WriteRegData(uint8 reg, uint8 data)
{
m_I2CBus.WriteI2C(0x39, 0x80 | reg, 1, &data);
}
uint16 Read_Word(uint8 reg)
{
uint8 barr[2];
m_I2CBus.ReadI2C(0x39, 0xA0 | reg, 2, ref barr);
return (uint16)(barr[0] + 256 * barr[1]);
}
根據(jù)手冊提供的軟件操作流程,我們首先有7次向寄存器寫入數(shù)據(jù)的操作,按照時序
向regaddr地址寄存器中寫入數(shù)據(jù)regdata,程序?qū)崿F(xiàn)如下
4'd0: begin state <= START;
end //I2C通信時序中的START
4'd1: begin data_wr <= dev_addr<<1;
state <= WRITE;
end //設(shè)備地址
4'd2: begin data_wr <= reg_addr;
state <= WRITE; end //寄存器地址
4'd3: begin data_wr <= reg_data;
state <= WRITE;
end //寫入數(shù)據(jù)
4'd4: begin state <= STOP;
end //I2C通信時序中的STOP
7次向寄存器寫入數(shù)據(jù)的操作需要7段上面的代碼,羅列起來程序不易讀,干脆我們將1次寫操作做成狀態(tài)機的一個狀態(tài),這樣7次向寄存器寫入數(shù)據(jù)的操作只需要在這個狀態(tài)上循環(huán)執(zhí)行7次就好了,單詞寫操作狀態(tài)程序?qū)崿F(xiàn)如下:
MODE1:begin //單次寫操作
if(cnt_mode1 >= 4'd5) cnt_mode1 <= 1'b0; //對START中的子狀態(tài)執(zhí)行控制cnt_start
else cnt_mode1 <= cnt_mode1 + 1'b1;
state_back <= MODE1;
case(cnt_mode1)
4'd0: begin state <= START; end //I2C通信時序中的START
4'd1: begin data_wr <= dev_addr<<1; state <= WRITE; end //設(shè)備地址
4'd2: begin data_wr <= reg_addr; state <= WRITE; end //寄存器地址
4'd3: begin data_wr <= reg_data; state <= WRITE; end //寫入數(shù)據(jù)
4'd4: begin state <= STOP; end //I2C通信時序中的STOP
4'd5: begin state <= MAIN; end //返回MAIN
default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài)
endcase
end
同理兩字節(jié)數(shù)據(jù)連讀的操作也做成一個狀態(tài)
程序?qū)崿F(xiàn)如下:
MODE2:begin //兩次讀操作
if(cnt_mode2 >= 4'd10) cnt_mode2 <= 1'b0; //對START中的子狀態(tài)執(zhí)行控制cnt_start
else cnt_mode2 <= cnt_mode2 + 1'b1;
state_back <= MODE2;
case(cnt_mode2)
4'd0: begin state <= START; end //I2C通信時序中的START
4'd1: begin data_wr <= dev_addr<<1; state <= WRITE; end //設(shè)備地址
4'd2: begin data_wr <= reg_addr; state <= WRITE; end //寄存器地址
4'd3: begin state <= START; end //I2C通信時序中的START
4'd4: begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end//設(shè)備地址
4'd5: begin ack <= ACK; state <= READ; end //讀寄存器數(shù)據(jù)
4'd6: begin dat_l <= data_r; end
4'd7: begin ack <= NACK; state <= READ; end //讀寄存器數(shù)據(jù)
4'd8: begin dat_h <= data_r; end
4'd9: begin state <= STOP; end //I2C通信時序中的STOP
4'd10: begin state <= MAIN; end //返回MAIN
default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài)
endcase
end
因為用到延時,也設(shè)計成一個狀態(tài),程序?qū)崿F(xiàn)如下:
DELAY:begin //延時模塊
if(cnt_delay >= num_delay) begin
cnt_delay <= 1'b0;
state <= MAIN;
end else cnt_delay <= cnt_delay + 1'b1;
end
最后我們編程控制狀態(tài)機按照驅(qū)動例程代碼中流程運行,程序?qū)崿F(xiàn)如下:
4'd0: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h00;reg_data<=8'h00;state<=MODE1; end
4'd1: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h01;reg_data<=8'hff;state<=MODE1; end
4'd2: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h02;reg_data<=8'hff;state<=MODE1; end
4'd3: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h03;reg_data<=8'hff;state<=MODE1; end
4'd4: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h0e;reg_data<=8'h01;state<=MODE1; end
4'd5: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h0f;reg_data<=8'h20;state<=MODE1; end
4'd6: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h00;reg_data<=8'h0f;state<=MODE1; end
4'd7: begin state <= DELAY; dat_valid <= 1'b0; end //12ms延時
4'd8: begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h14; state <= MODE2; end
4'd9: begin ch0_dat <= {dat_h,dat_l}; end //讀取數(shù)據(jù)
4'd10: begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h16; state <= MODE2; end
4'd11: begin ch1_dat <= {dat_h,dat_l}; end //讀取數(shù)據(jù)
4'd12: begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h18; state <= MODE2; end
4'd13: begin prox_dat <= {dat_h,dat_l}; end //讀取數(shù)據(jù)
4'd14: begin dat_valid <= 1'b1; end //讀取數(shù)據(jù)
系統(tǒng)總體實現(xiàn)
程序中我們做了一個簡單的濾波處理,為了保證數(shù)據(jù)的有效,將瞬間變化太大的采樣數(shù)據(jù)舍棄,程序?qū)崿F(xiàn)如下:
reg [15:0] prox_dat0,prox_dat1,prox_dat2;
always @(posedge dat_valid) begin
prox_dat0 <= prox_dat;
prox_dat1 <= prox_dat0;
if(((prox_dat1-prox_dat0) >= 16'h200)||((prox_dat1-prox_dat0) >= 16'h200)) prox_dat2 <= prox_dat2;
else prox_dat2 <= prox_dat0;
end
我們從傳感器讀取的距離信息為16位數(shù)據(jù),有效范圍Full Scale ADC Counts為0~1023,對應(yīng)0到16‘h3ff,可以設(shè)置一個閾值,當采樣回來的數(shù)據(jù)與閾值比較控制手機屏幕的顯示與否,本實驗要求用能量條的方式顯示距離的遠近,我們設(shè)計一個編碼器將0到16‘h3ff的范圍控制8個led燈的控制,程序?qū)崿F(xiàn)如下:
always@(prox_dat2[9:7])
case (prox_dat2[9:7])
3'b000: Y_out = 8'b11111110;
3'b001: Y_out = 8'b11111100;
3'b010: Y_out = 8'b11111000;
3'b011: Y_out = 8'b11110000;
3'b100: Y_out = 8'b11100000;
3'b101: Y_out = 8'b11000000;
3'b110: Y_out = 8'b10000000;
3'b111: Y_out = 8'b00000000;
default:Y_out = 8'b11111111;
endcase
在頂層設(shè)計中例化兩個模塊,將信號連接,程序?qū)崿F(xiàn)如下:
wire dat_valid;
wire [15:0] ch0_dat, ch1_dat, prox_dat;
APDS_9901_Driver u1(
.clk (clk ), //系統(tǒng)時鐘
.rst_n (rst_n ), //系統(tǒng)復(fù)位,低有效
.i2c_scl (i2c_scl ), //I2C總線SCL
.i2c_sda (i2c_sda ), //I2C總線SDA
.dat_valid (dat_valid ), //數(shù)據(jù)有效脈沖
.ch0_dat (ch0_dat ), //ALS數(shù)據(jù)
.ch1_dat (ch1_dat ), //IR數(shù)據(jù)
.prox_dat (prox_dat ) //Prox數(shù)據(jù)
);
Decoder u2(.dat_valid (dat_valid ),
.prox_dat (prox_dat ),
.Y_out (led )
);
綜合后的設(shè)計框圖如下:
實驗步驟
實驗現(xiàn)象
將設(shè)計加載到FPGA,手指在接近光傳感器上下移動,觀察核心板上8個LED燈的狀態(tài),APDS-9901還是環(huán)境光傳感器,有興趣的同學可以嘗試一下其他應(yīng)用。
本節(jié)實驗主要學習I2C總線工作原理、協(xié)議及相關(guān)知識,掌握FPGA驅(qū)動I2C設(shè)備的原理及方法,了解輸入輸出型端口的模型及控制實現(xiàn),最終實現(xiàn)智能接近系統(tǒng)的總體設(shè)計。
本文引用地址:http://butianyuan.cn/article/202312/453885.htm根據(jù)前面的實驗解析我們可以得知,該設(shè)計可以拆分成兩個功能模塊實現(xiàn),
I2C總線是由Philips公司開發(fā)的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接于總線上的器件之間傳送信息。
主器件用于啟動總線傳送數(shù)據(jù),并產(chǎn)生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件.在總線上主和從、發(fā)和收的關(guān)系不是恒定的,而取決于此時數(shù)據(jù)傳送方向。如果主機要發(fā)送數(shù)據(jù)給從器件,則主機首先尋址從器件,然后主動發(fā)送數(shù)據(jù)至從器件,最后由主機終止數(shù)據(jù)傳送;如果主機要接收從器件的數(shù)據(jù),首先由主器件尋址從器件.然后主機接收從器件發(fā)送的數(shù)據(jù),最后由主機終止接收過程。在這種情況下.主機負責產(chǎn)生定時時鐘和終止數(shù)據(jù)傳送。
字節(jié)格式
發(fā)送到SDA 線上的每個字節(jié)必須為8 位,每次傳輸可以發(fā)送的字節(jié)數(shù)量不受限制。每個字節(jié)后必須跟一個響應(yīng)位。首先傳輸?shù)氖菙?shù)據(jù)的最高位(MSB),如果從機要完成一些其他功能后(例如一個內(nèi)部中斷服務(wù)程序)才能接收或發(fā)送下一個完整的數(shù)據(jù)字節(jié),可以使時鐘線SCL 保持低電平,迫使主機進入等待狀態(tài),當從機準備好接收下一個數(shù)據(jù)字節(jié)并釋放時鐘線SCL 后數(shù)據(jù)傳輸繼續(xù)。
啟動和停止
在時鐘線SCL保持高電平期間,數(shù)據(jù)線SDA上的電平被拉低(即負跳變),定義為I2C總線總線的啟動信號,它標志著一次數(shù)據(jù)傳輸?shù)拈_始。啟動信號是一種電平跳變時序信號,而不是一個電平信號。啟動信號是由主控器主動建立的,在建立該信號之前I2C總線必須處于空閑狀態(tài)。
在時鐘線SCL保持高電平期間,數(shù)據(jù)線SDA被釋放,使得SDA返回高電平(即正跳變),稱為I2C總線的停止信號,它標志著一次數(shù)據(jù)傳輸?shù)慕K止。停止信號也是一種電平跳變時序信號,而不是一個電平信號,停止信號也是由主控器主動建立的,建立該信號之后,I2C總線將返回空閑狀態(tài)。
應(yīng)答響應(yīng)
數(shù)據(jù)傳輸必須帶響應(yīng),相關(guān)的響應(yīng)時鐘脈沖由主機產(chǎn)生。在響應(yīng)的時鐘脈沖期間,發(fā)送器釋放SDA 線(上拉電阻拉高),接收器必須將SDA 線拉低,使它在這個時鐘脈沖的高電平期間保持穩(wěn)定的低電平,這種情況下是應(yīng)答,如果在這個時鐘脈沖的高電平期間SDA線沒有被拉低則表示沒有應(yīng)答。通常被尋址的接收器在接收到的每個字節(jié)后,必須產(chǎn)生一個應(yīng)答。當從機接收器不應(yīng)答時,主機產(chǎn)生一個停止或重復(fù)起始條件。
通信速率
常見的I2C總線依傳輸速率的不同而有不同的模式:標準模式(100 Kbit/s)、低速模式(10 Kbit/s),但時鐘頻率可被允許下降至零,這代表可以暫停通信。而新一代的I2C總線可以和更多的節(jié)點(支持10比特長度的地址空間)以更快的速率通信:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。
STEP BaseBoard V3.0底板上的接近光傳感器APDS-9901模塊電路圖如下(上拉電阻未顯示):
上圖為接近光傳感器APDS-9901模塊電路,與FPGA硬件接口有I2C總線(SCL、SDA)和中斷信號INT,APDS-9901是博通公司的集成環(huán)境光ALS、紅外光IR和接近距離傳感器,具有體積小、低功耗等優(yōu)點,被大量應(yīng)用于手機、筆記本、相機、液晶顯示器等電子產(chǎn)品上,環(huán)境光ALS可以根據(jù)外部環(huán)境調(diào)節(jié)設(shè)備屏幕顯示亮度,接近距離傳感器可以根據(jù)應(yīng)用場景實現(xiàn)產(chǎn)品對應(yīng)應(yīng)用,例如接聽電話時控制手機關(guān)閉顯示等,接口采用I2C總線能夠支持400KHz的I2C快速模式。
可綜合Verilog模塊設(shè)計中必須有端口存在,端口有輸入input,輸出output,雙向inout,對于輸入和輸出型端口我們很好理解,我們來了解一下雙向端口信號的處理。
在芯片中為了管腳復(fù)用,很多管腳都是雙向的,既可以輸入也可以輸出。在Verilog中即為inout型端口。Inout端口的實現(xiàn)是使用三態(tài)門,三態(tài)門的第三個狀態(tài)是高阻態(tài)Z。在實際電路中高阻態(tài)意味著響應(yīng)的管腳懸空、斷開。
當inout用作輸出時,就像平常一樣。當inout用作輸入時,需要設(shè)為高阻態(tài),這樣其電平就可以由外部輸入信號決定了(這是高阻態(tài)的特性)。
雙向端口應(yīng)用案例:
module bid ( input out_en, input a, inout b, output c ); assign b = out_en? a : 1'bz; assign c = b; endmodule
通過前面的了解,我們對于整個I2C總線的驅(qū)動原理有了一定的了解,接下來我們根據(jù)APDS-9901的芯片手冊了解其驅(qū)動方法及參數(shù)要點。
通過APDS-9901時序參數(shù)了解,APDS-9901支持I2C通信400KHz快速模式同時兼容100KHz的標準模式,還有兩種模式下時序中的各種時間參數(shù),本例中我們就采用標準模式完成驅(qū)動設(shè)計。
首先我們分頻得到400KHz的時鐘,整個設(shè)計都基于該時鐘完成,程序?qū)崿F(xiàn)如下:
//使用計數(shù)器分頻產(chǎn)生400KHz時鐘信號 clk_400khzreg clk_400khz; reg [9:0] cnt_400khz; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_400khz <= 10'd0; clk_400khz <= 1'b0; end else if(cnt_400khz >= CNT_NUM-1) begin cnt_400khz <= 10'd0; clk_400khz <= ~clk_400khz; end else begin cnt_400khz <= cnt_400khz + 1'b1; end end
I2C時序可以分解成基本單元(啟動、停止、發(fā)送、接收、發(fā)應(yīng)答、讀應(yīng)答),整個I2C通信都是由這些單元按照不同的順序組合,我們設(shè)計一個狀態(tài)機,將這些基本單元做成狀態(tài),控制狀態(tài)機的跳轉(zhuǎn)就能實現(xiàn)I2C通信時序。主機每次發(fā)送數(shù)據(jù)都要接收判斷從機的響應(yīng),每次接收數(shù)據(jù)也要向從機發(fā)送響應(yīng),所以發(fā)送單元和讀應(yīng)答單元可以合并,接收單元和寫應(yīng)答單元可以合并。
啟動時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
START:begin //I2C通信時序中的起始START if(cnt_start >= 3'd5) cnt_start <= 1'b0; //對START中的子狀態(tài)執(zhí)行控制cnt_start else cnt_start <= cnt_start + 1'b1; case(cnt_start) 3'd0: begin sda <= 1'b1; scl <= 1'b1; end //將SCL和SDA拉高,保持4.7us以上 3'd1: begin sda <= 1'b1; scl <= 1'b1; end //每個周期2.5us,需要兩個周期 3'd2: begin sda <= 1'b0; end //SDA拉低到SCL拉低,保持4.0us以上 3'd3: begin sda <= 1'b0; end //clk_400khz每個周期2.5us,需要兩個周期 3'd4: begin scl <= 1'b0; end //SCL拉低,保持4.7us以上 3'd5: begin scl <= 1'b0; state <= state_back; end //每個周期2.5us,兩個周期 default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end
發(fā)送單元和讀應(yīng)答單元合并,時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
WRITE:begin //I2C通信時序中的寫操作WRITE和相應(yīng)判斷操作ACK if(cnt <= 3'd6) begin //共需要發(fā)送8bit的數(shù)據(jù),這里控制循環(huán)的次數(shù) if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end end else begin if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; cnt <= 1'b0; end //復(fù)位變量 else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end end case(cnt_write) //按照I2C的時序傳輸數(shù)據(jù) 3'd0: begin scl <= 1'b0; sda <= data_wr[7-cnt]; end //SCL拉低,SDA輸出 3'd1: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上 3'd2: begin scl <= 1'b1; end //clk_400khz每個周期2.5us,需要兩個周期 3'd3: begin scl <= 1'b0; end //SCL拉低,準備發(fā)送下1bit的數(shù)據(jù) //獲取從設(shè)備的響應(yīng)信號并判斷 3'd4: begin sda <= 1'bz; end //釋放SDA線,準備接收從設(shè)備的響應(yīng)信號 3'd5: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上 3'd6: begin ack_flag <= i2c_sda; end //獲取從設(shè)備的響應(yīng)信號 3'd7: begin scl <= 1'b0; if(ack_flag)state <= state; else state <= state_back; end //SCL拉低,如果不應(yīng)答循環(huán)寫 default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end
接收單元和寫應(yīng)答單元合并,時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
READ:begin //I2C通信時序中的讀操作READ和返回ACK的操作 if(cnt <= 3'd6) begin //共需要接收8bit的數(shù)據(jù),這里控制循環(huán)的次數(shù) if(cnt_read >= 3'd3) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end end else begin if(cnt_read >= 3'd7) begin cnt_read <= 1'b0; cnt <= 1'b0; end //復(fù)位變量值 else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end end case(cnt_read) //按照I2C的時序接收數(shù)據(jù) 3'd0: begin scl <= 1'b0; sda <= 1'bz; end //SCL拉低,釋放SDA線 3'd1: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上 3'd2: begin data_r[7-cnt] <= i2c_sda; end //讀取從設(shè)備返回的數(shù)據(jù) 3'd3: begin scl <= 1'b0; end //SCL拉低,準備接收下1bit的數(shù)據(jù) //向從設(shè)備發(fā)送響應(yīng)信號 3'd4: begin sda <= ack; end //發(fā)送響應(yīng)信號,將前面接收的數(shù)據(jù)鎖存 3'd5: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上 3'd6: begin scl <= 1'b1; end //SCL拉高,保持4.0us以上 3'd7: begin scl <= 1'b0; state <= state_back; end //SCL拉低 default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end
停止時序狀態(tài)設(shè)計程序?qū)崿F(xiàn)如下:
STOP:begin //I2C通信時序中的結(jié)束STOP if(cnt_stop >= 3'd5) cnt_stop <= 1'b0; //對STOP中的子狀態(tài)執(zhí)行控制cnt_stop else cnt_stop <= cnt_stop + 1'b1; case(cnt_stop) 3'd0: begin sda <= 1'b0; end //SDA拉低,準備STOP 3'd1: begin sda <= 1'b0; end //SDA拉低,準備STOP 3'd2: begin scl <= 1'b1; end //SCL提前SDA拉高4.0us 3'd3: begin scl <= 1'b1; end //SCL提前SDA拉高4.0us 3'd4: begin sda <= 1'b1; end //SDA拉高 3'd5: begin sda <= 1'b1; state <= state_back; end //完成STOP操作 default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end
基本單元都有了,接下來我們需要了解APDS-9901驅(qū)動的流程,手冊上看到APDS-9901芯片有很多寄存器,有的配置工作模式,有的配置功能使能,有的返回結(jié)果數(shù)據(jù),這個需要大家自己查看芯片手冊,這里不作講解。
手冊給用戶提供了基本功能的C實例代碼,流程如下:
WriteRegData (0, 0); //Disable and Powerdown WriteRegData (1, 0xff); // 2.7 ms – minimum ALS integration time WriteRegData (2, 0xff); // 2.7 ms – minimum Prox integration time WriteRegData (3, 0xff); // 2.7 ms – minimum Wait time WriteRegData (0xe, 1); // Minimum prox pulse count WriteRegData (0xf, 0x20); // CH1 Diode WriteRegData (0,0x0f); //Enable WEN PEN AEN PON Wait(12); //Wait for 12 ms CH0_data = Read_Word(0x14); CH1_data = Read_Word(0x16); Prox_data = Read_Word(0x18); WriteRegData(uint8 reg, uint8 data) { m_I2CBus.WriteI2C(0x39, 0x80 | reg, 1, &data); } uint16 Read_Word(uint8 reg) { uint8 barr[2]; m_I2CBus.ReadI2C(0x39, 0xA0 | reg, 2, ref barr); return (uint16)(barr[0] + 256 * barr[1]); }
根據(jù)手冊提供的軟件操作流程,我們首先有7次向寄存器寫入數(shù)據(jù)的操作,按照時序
向regaddr地址寄存器中寫入數(shù)據(jù)regdata,程序?qū)崿F(xiàn)如下
4'd0: begin state <= START; end //I2C通信時序中的START 4'd1: begin data_wr <= dev_addr<<1; state <= WRITE; end //設(shè)備地址 4'd2: begin data_wr <= reg_addr; state <= WRITE; end //寄存器地址 4'd3: begin data_wr <= reg_data; state <= WRITE; end //寫入數(shù)據(jù) 4'd4: begin state <= STOP; end //I2C通信時序中的STOP
7次向寄存器寫入數(shù)據(jù)的操作需要7段上面的代碼,羅列起來程序不易讀,干脆我們將1次寫操作做成狀態(tài)機的一個狀態(tài),這樣7次向寄存器寫入數(shù)據(jù)的操作只需要在這個狀態(tài)上循環(huán)執(zhí)行7次就好了,單詞寫操作狀態(tài)程序?qū)崿F(xiàn)如下:
MODE1:begin //單次寫操作 if(cnt_mode1 >= 4'd5) cnt_mode1 <= 1'b0; //對START中的子狀態(tài)執(zhí)行控制cnt_start else cnt_mode1 <= cnt_mode1 + 1'b1; state_back <= MODE1; case(cnt_mode1) 4'd0: begin state <= START; end //I2C通信時序中的START 4'd1: begin data_wr <= dev_addr<<1; state <= WRITE; end //設(shè)備地址 4'd2: begin data_wr <= reg_addr; state <= WRITE; end //寄存器地址 4'd3: begin data_wr <= reg_data; state <= WRITE; end //寫入數(shù)據(jù) 4'd4: begin state <= STOP; end //I2C通信時序中的STOP 4'd5: begin state <= MAIN; end //返回MAIN default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end
同理兩字節(jié)數(shù)據(jù)連讀的操作也做成一個狀態(tài)
程序?qū)崿F(xiàn)如下:
MODE2:begin //兩次讀操作 if(cnt_mode2 >= 4'd10) cnt_mode2 <= 1'b0; //對START中的子狀態(tài)執(zhí)行控制cnt_start else cnt_mode2 <= cnt_mode2 + 1'b1; state_back <= MODE2; case(cnt_mode2) 4'd0: begin state <= START; end //I2C通信時序中的START 4'd1: begin data_wr <= dev_addr<<1; state <= WRITE; end //設(shè)備地址 4'd2: begin data_wr <= reg_addr; state <= WRITE; end //寄存器地址 4'd3: begin state <= START; end //I2C通信時序中的START 4'd4: begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end//設(shè)備地址 4'd5: begin ack <= ACK; state <= READ; end //讀寄存器數(shù)據(jù) 4'd6: begin dat_l <= data_r; end 4'd7: begin ack <= NACK; state <= READ; end //讀寄存器數(shù)據(jù) 4'd8: begin dat_h <= data_r; end 4'd9: begin state <= STOP; end //I2C通信時序中的STOP 4'd10: begin state <= MAIN; end //返回MAIN default: state <= IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end
因為用到延時,也設(shè)計成一個狀態(tài),程序?qū)崿F(xiàn)如下:
DELAY:begin //延時模塊 if(cnt_delay >= num_delay) begin cnt_delay <= 1'b0; state <= MAIN; end else cnt_delay <= cnt_delay + 1'b1; end
最后我們編程控制狀態(tài)機按照驅(qū)動例程代碼中流程運行,程序?qū)崿F(xiàn)如下:
4'd0: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h00;reg_data<=8'h00;state<=MODE1; end 4'd1: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h01;reg_data<=8'hff;state<=MODE1; end 4'd2: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h02;reg_data<=8'hff;state<=MODE1; end 4'd3: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h03;reg_data<=8'hff;state<=MODE1; end 4'd4: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h0e;reg_data<=8'h01;state<=MODE1; end 4'd5: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h0f;reg_data<=8'h20;state<=MODE1; end 4'd6: begin dev_addr<=7'h39;reg_addr<=8'h80|8'h00;reg_data<=8'h0f;state<=MODE1; end 4'd7: begin state <= DELAY; dat_valid <= 1'b0; end //12ms延時 4'd8: begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h14; state <= MODE2; end 4'd9: begin ch0_dat <= {dat_h,dat_l}; end //讀取數(shù)據(jù) 4'd10: begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h16; state <= MODE2; end 4'd11: begin ch1_dat <= {dat_h,dat_l}; end //讀取數(shù)據(jù) 4'd12: begin dev_addr <= 7'h39; reg_addr <= 8'ha0|8'h18; state <= MODE2; end 4'd13: begin prox_dat <= {dat_h,dat_l}; end //讀取數(shù)據(jù) 4'd14: begin dat_valid <= 1'b1; end //讀取數(shù)據(jù)
程序中我們做了一個簡單的濾波處理,為了保證數(shù)據(jù)的有效,將瞬間變化太大的采樣數(shù)據(jù)舍棄,程序?qū)崿F(xiàn)如下:
reg [15:0] prox_dat0,prox_dat1,prox_dat2; always @(posedge dat_valid) begin prox_dat0 <= prox_dat; prox_dat1 <= prox_dat0; if(((prox_dat1-prox_dat0) >= 16'h200)||((prox_dat1-prox_dat0) >= 16'h200)) prox_dat2 <= prox_dat2; else prox_dat2 <= prox_dat0; end
我們從傳感器讀取的距離信息為16位數(shù)據(jù),有效范圍Full Scale ADC Counts為0~1023,對應(yīng)0到16‘h3ff,可以設(shè)置一個閾值,當采樣回來的數(shù)據(jù)與閾值比較控制手機屏幕的顯示與否,本實驗要求用能量條的方式顯示距離的遠近,我們設(shè)計一個編碼器將0到16‘h3ff的范圍控制8個led燈的控制,程序?qū)崿F(xiàn)如下:
always@(prox_dat2[9:7]) case (prox_dat2[9:7]) 3'b000: Y_out = 8'b11111110; 3'b001: Y_out = 8'b11111100; 3'b010: Y_out = 8'b11111000; 3'b011: Y_out = 8'b11110000; 3'b100: Y_out = 8'b11100000; 3'b101: Y_out = 8'b11000000; 3'b110: Y_out = 8'b10000000; 3'b111: Y_out = 8'b00000000; default:Y_out = 8'b11111111; endcase
在頂層設(shè)計中例化兩個模塊,將信號連接,程序?qū)崿F(xiàn)如下:
wire dat_valid; wire [15:0] ch0_dat, ch1_dat, prox_dat; APDS_9901_Driver u1( .clk (clk ), //系統(tǒng)時鐘 .rst_n (rst_n ), //系統(tǒng)復(fù)位,低有效 .i2c_scl (i2c_scl ), //I2C總線SCL .i2c_sda (i2c_sda ), //I2C總線SDA .dat_valid (dat_valid ), //數(shù)據(jù)有效脈沖 .ch0_dat (ch0_dat ), //ALS數(shù)據(jù) .ch1_dat (ch1_dat ), //IR數(shù)據(jù) .prox_dat (prox_dat ) //Prox數(shù)據(jù) ); Decoder u2(.dat_valid (dat_valid ), .prox_dat (prox_dat ), .Y_out (led ) );
綜合后的設(shè)計框圖如下:
將設(shè)計加載到FPGA,手指在接近光傳感器上下移動,觀察核心板上8個LED燈的狀態(tài),APDS-9901還是環(huán)境光傳感器,有興趣的同學可以嘗試一下其他應(yīng)用。
評論