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