硬件說明
PCF8591是集成了4路ADC和1路DAC的芯片,使用I2C總線通信。I2C總線是由Philips公司開發(fā)的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接于總線上的器件之間傳送信息。主器件用于啟動總線傳送數(shù)據(jù),并產(chǎn)生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件。如果主機要發(fā)送數(shù)據(jù)給從器件,則主機首先尋址從器件,然后主動發(fā)送數(shù)據(jù)至從器件,最后由主機終止數(shù)據(jù)傳送;如果主機要接收從器件的數(shù)據(jù),首先由主器件尋址從器件,然后主機接收從器件發(fā)送的數(shù)據(jù),最后由主機終止接收過程。這里不做過多的講解,硬件連接如下:
本設(shè)計的硬件連接如下
本設(shè)計中FPGA作為I2C主設(shè)備,PCF8591作為I2C從設(shè)備,從設(shè)備的地址由固定地址和可編程地址組成,我們的外設(shè)底板已將可編程地址A0、A1、A2接地,所以7位地址為7'h48,加上最低位的讀寫控制,所以給PCF8591寫數(shù)據(jù)時的尋址地址為8'h90,對PCF8591讀數(shù)據(jù)時的尋址地址為8'h91。如下
PCF8591集成了很多功能,當需要不同的功能時要對PCF8591做相應(yīng)的配置,配置數(shù)據(jù)存儲在名為CONTROL BYTE的寄存器中,下圖展示了寄存器中部分bit的功能,詳細請參考PCF8591的datasheet,本設(shè)計中我們只使用通道1的ADC功能,配置數(shù)據(jù)為8'h01。
本設(shè)計中我們需要兩次通信,
第二次的時序如下圖:通過上面的介紹大家應(yīng)該對如何驅(qū)動PCF8591進行ADC采樣有了整體的概念,還有一些細節(jié)就是I2C通信的時序明細,如下圖
Verilog代碼
//-------------------------------------------------------------------- //>>>>>>>>>>>>>>>>>>>>>>>>>COPYRIGHTNOTICE<<<<<<<<<<<<<<<<<<<<<<<<< //-------------------------------------------------------------------- //Module:ADC_I2C // //Author:Step // //Description:ADC_I2C // //-------------------------------------------------------------------- //CodeRevisionHistory: //-------------------------------------------------------------------- //Version:|Mod.Date:|ChangesMade: //V1.1|2016/10/30|Initialver //-------------------------------------------------------------------- moduleADC_I2C( input clk_in, //系統(tǒng)時鐘 input rst_n_in, //系統(tǒng)復(fù)位,低有效 output scl_out, //I2C總線SCL inout sda_out, //I2C總線SDA output reg adc_done, //ADC采樣完成標志 output reg [7:0] adc_data //ADC采樣數(shù)據(jù) ) ; parameter CNT_NUM = 15; localparam IDLE = 3'd0; localparam MAIN = 3'd1; localparam START = 3'd2; localparam WRITE = 3'd3; localparam READ = 3'd4; localparam STOP = 3'd5; //根據(jù)PCF8591的datasheet,I2C的頻率最高為100KHz, //我們準備使用4個節(jié)拍完成1bit數(shù)據(jù)的傳輸,所以需要400KHz的時鐘觸發(fā)完成該設(shè)計 //使用計數(shù)器分頻產(chǎn)生400KHz時鐘信號clk_400khz reg clk_400khz; reg [9:0] cnt_400khz; always@(posedgeclk_inornegedgerst_n_in)begin if(!rst_n_in)begin cnt_400khz<=10'd0; clk_400khz<=1'b0; endelseif(cnt_400khz>=CNT_NUM-1)begin cnt_400khz<=10'd0; clk_400khz<=~clk_400khz; endelsebegin cnt_400khz<=cnt_400khz+1'b1; end end reg [7:0] adc_data_r; reg scl_out_r; reg sda_out_r; reg [2:0] cnt; reg [3:0] cnt_main; reg [7:0] data_wr; reg [2:0] cnt_start; reg [2:0] cnt_write; reg [4:0] cnt_read; reg [2:0] cnt_stop; reg [2:0] state; always@(posedgeclk_400khzornegedgerst_n_in)begin if(!rst_n_in)begin //如果按鍵復(fù)位,將相關(guān)數(shù)據(jù)初始化 scl_out_r<=1'd1; sda_out_r<=1'd1; cnt<=1'b0; cnt_main<=4'd0; cnt_start<=3'd0; cnt_write<=3'd0; cnt_read<=5'd0; cnt_stop<=1'd0; adc_done<=1'b0; adc_data<=1'b0; state<=IDLE; endelsebegin case(state) IDLE:begin //軟件自復(fù)位,主要用于程序跑飛后的處理 scl_out_r<=1'd1; sda_out_r<=1'd1; cnt<=1'b0; cnt_main<=4'd0; cnt_start<=3'd0; cnt_write<=3'd0; cnt_read<=5'd0; cnt_stop<=1'd0; adc_done<=1'b0; state<=MAIN; end MAIN:begin if(cnt_main>=4'd6)cnt_main<=4'd6;//對MAIN中的子狀態(tài)執(zhí)行控制cnt_main elsecnt_main<=cnt_main+1'b1; case(cnt_main) 4'd0: beginstate<=START; end //I2C通信時序中的START 4'd1: begindata_wr<=8'h90; state<=WRITE; end //A0,A1,A2都接了GND,寫地址為8'h90 4'd2: begindata_wr<=8'h00; state<=WRITE; end //controlbyte為8'h00,采用4通道ADC中的通道0 4'd3: beginstate<=STOP; end //I2C通信時序中的START 4'd4: beginstate<=START; end //I2C通信時序中的STOP 4'd5: begindata_wr<=8'h91; state<=WRITE; end //A0A1A2都接了GND,讀地址為8'h91 4'd6: beginstate<=READ; adc_done<=1'b0; end //讀取ADC的采樣數(shù)據(jù) 4'd7: beginstate<=STOP; adc_done<=1'b1; end //I2C通信時序中的STOP,讀取完成標志 4'd8: beginstate<=MAIN; end //預(yù)留狀態(tài),不執(zhí)行 default:state<=IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end START:begin //I2C通信時序中的起始START if(cnt_start>=3'd5)cnt_start<=1'b0; //對START中的子狀態(tài)執(zhí)行控制cnt_start elsecnt_start<=cnt_start+1'b1; case(cnt_start) 3'd0: beginsda_out_r<=1'b1; scl_out_r<=1'b1; end //將SCL和SDA拉高,保持4.7us以上 3'd1: beginsda_out_r<=1'b1; scl_out_r<=1'b1; end //clk_400khz每個周期2.5us,需要兩個周期 3'd2: beginsda_out_r<=1'b0; end //SDA拉低到SCL拉低,保持4.0us以上 3'd3: beginsda_out_r<=1'b0; end //clk_400khz每個周期2.5us,需要兩個周期 3'd4: beginscl_out_r<=1'b0; end //SCL拉低,保持4.7us以上 3'd5: beginscl_out_r<=1'b0; state<=MAIN; end //clk_400khz每個周期2.5us,需要兩個周期,返回MAIN default:state<=IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end WRITE:begin //I2C通信時序中的寫操作WRITE和相應(yīng)判斷操作ACK if(cnt<=3'd6)begin //共需要發(fā)送8bit的數(shù)據(jù),這里控制循環(huán)的次數(shù) if(cnt_write>=3'd3)begincnt_write<=1'b0; cnt<=cnt+1'b1;end elsebegincnt_write<=cnt_write+1'b1; cnt<=cnt;end endelsebegin if(cnt_write>=3'd7)begincnt_write<=1'b0; cnt<=1'b0;end //兩個變量都恢復(fù)初值 elsebegincnt_write<=cnt_write+1'b1; cnt<=cnt;end end case(cnt_write) //按照I2C的時序傳輸數(shù)據(jù) 3'd0: beginscl_out_r<=1'b0; sda_out_r<=data_wr[7-cnt]; end //SCL拉低,并控制SDA輸出對應(yīng)的位 3'd1: beginscl_out_r<=1'b1; end //SCL拉高,保持4.0us以上 3'd2: beginscl_out_r<=1'b1; end //clk_400khz每個周期2.5us,需要兩個周期 3'd3: beginscl_out_r<=1'b0; end //SCL拉低,準備發(fā)送下1bit的數(shù)據(jù) //獲取從設(shè)備的響應(yīng)信號并判斷 3'd4: beginsda_out_r<=1'bz; end //釋放SDA線,準備接收從設(shè)備的響應(yīng)信號 3'd5: beginscl_out_r<=1'b1; end //SCL拉高,保持4.0us以上 3'd6: beginif(sda_out)state<=IDLE; elsestate<=state; end //獲取從設(shè)備的響應(yīng)信號并判斷 3'd7: beginscl_out_r<=1'b0; state<=MAIN; end //SCL拉低,返回MAIN狀態(tài) default:state<=IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end READ:begin //I2C通信時序中的讀操作READ和返回ACK的操作 if(cnt<=3'd6)begin //共需要接收8bit的數(shù)據(jù),這里控制循環(huán)的次數(shù) if(cnt_read>=3'd3)begincnt_read<=1'b0; cnt<=cnt+1'b1;end elsebegincnt_read<=cnt_read+1'b1; cnt<=cnt; end endelsebegin if(cnt_read>=3'd7)begincnt_read<=1'b0; cnt<=1'b0; end //兩個變量都恢復(fù)初值 elsebegincnt_read<=cnt_read+1'b1; cnt<=cnt; end end case(cnt_read) //按照I2C的時序接收數(shù)據(jù) 3'd0: beginscl_out_r<=1'b0; sda_out_r<=1'bz; end //SCL拉低,釋放SDA線,準備接收從設(shè)備數(shù)據(jù) 3'd1: beginscl_out_r<=1'b1; end //SCL拉高,保持4.0us以上 3'd2: beginadc_data_r[7-cnt]<=sda_out; end //讀取從設(shè)備返回的數(shù)據(jù) 3'd3: beginscl_out_r<=1'b0; end //SCL拉低,準備接收下1bit的數(shù)據(jù) //向從設(shè)備發(fā)送響應(yīng)信號 3'd4: beginsda_out_r<=1'b0;a dc_done<=1'b1; adc_data<=adc_data_r; end //發(fā)送響應(yīng)信號,將前面接收的數(shù)據(jù)鎖存 3'd5: beginscl_out_r<=1'b1; end //SCL拉高,保持4.0us以上 3'd6: beginscl_out_r<=1'b1; adc_done<=1'b0; end //SCL拉高,保持4.0us以上 3'd7: beginscl_out_r<=1'b0; state<=MAIN; end //SCL拉低,返回MAIN狀態(tài) default:state<=IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end STOP:begin //I2C通信時序中的結(jié)束STOP if(cnt_stop>=3'd5)cnt_stop<=1'b0; //對STOP中的子狀態(tài)執(zhí)行控制cnt_stop elsecnt_stop<=cnt_stop+1'b1; case(cnt_stop) 3'd0: beginsda_out_r<=1'b0; end //SDA拉低,準備STOP 3'd1: beginsda_out_r<=1'b0; end //SDA拉低,準備STOP 3'd2: beginscl_out_r<=1'b1; end //SCL提前SDA拉高4.0us 3'd3: beginscl_out_r<=1'b1; end //SCL提前SDA拉高4.0us 3'd4: beginsda_out_r<=1'b1; end //SDA拉高 3'd5: beginsda_out_r<=1'b1; state<=MAIN; end //完成STOP操作,返回MAIN狀態(tài) default:state<=IDLE; //如果程序失控,進入IDLE自復(fù)位狀態(tài) endcase end default:; endcase end end assign scl_out=scl_out_r; //對SCL端口賦值 assign sda_out=sda_out_r; //對SDA端口賦值 endmodule
小結(jié)
本節(jié)主要為大家講解了使用I2C驅(qū)動PCF8591的ADC功能的原理及軟件設(shè)計,需要大家掌握的同時自己創(chuàng)建工程,通過整個設(shè)計流程,生成FPGA配置文件加載測試。