新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 基于STEP FPGA的PCF8591的ADC(I2C)功能驅動

        基于STEP FPGA的PCF8591的ADC(I2C)功能驅動

        作者: 時間:2023-12-04 來源:電子森林 收藏

        硬件說明

        PCF8591是集成了4路ADC和1路DAC的芯片,使用I2C總線通信。
        I2C總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接于總線上的器件之間傳送信息。主器件用于啟動總線傳送數據,并產生時鐘以開放傳送的器件,此時任何被尋址的器件均被認為是從器件。如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件,然后主機接收從器件發送的數據,最后由主機終止接收過程。這里不做過多的講解,硬件連接如下:

        本文引用地址:http://www.104case.com/article/202312/453512.htm

        本設計的硬件連接如下

        本設計中FPGA作為I2C主設備,PCF8591作為I2C從設備,從設備的地址由固定地址和可編程地址組成,我們的外設底板已將可編程地址A0、A1、A2接地,所以7位地址為7'h48,加上最低位的讀寫控制,所以給PCF8591寫數據時的尋址地址為8'h90,對PCF8591讀數據時的尋址地址為8'h91。如下

        PCF8591集成了很多功能,當需要不同的功能時要對PCF8591做相應的配置,配置數據存儲在名為CONTROL BYTE的寄存器中,下圖展示了寄存器中部分bit的功能,詳細請參考PCF8591的datasheet,本設計中我們只使用通道1的ADC功能,配置數據為8'h01。

        本設計中我們需要兩次通信,

        • 第一次為配置數據,具體為:開始–寫尋址–讀響應–寫配置數據–讀響應–結束
        • 第二次為讀ADC數據,具體為:開始–讀尋址–讀響應–[讀ADC數據–寫響應–]循環讀

        第二次的時序如下圖:
        通過上面的介紹大家應該對如何驅動PCF8591進行ADC采樣有了整體的概念,還有一些細節就是I2C通信的時序明細,如下圖


        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,		//系統時鐘
        	input				rst_n_in,	//系統復位,低有效
        	output				scl_out,	//I2C總線SCL
        	inout				sda_out,	//I2C總線SDA
        	output	reg			adc_done,	//ADC采樣完成標志
        	output	reg	[7:0]	adc_data	//ADC采樣數據
        	)
        	; 	
        	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; 	//根據PCF8591的datasheet,I2C的頻率最高為100KHz,
        	//我們準備使用4個節拍完成1bit數據的傳輸,所以需要400KHz的時鐘觸發完成該設計
        	//使用計數器分頻產生400KHz時鐘信號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	//如果按鍵復位,將相關數據初始化
        			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	//軟件自復位,主要用于程序跑飛后的處理
        						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中的子狀態執行控制cnt_main
        						else cnt_main <= cnt_main + 1'b1;
        						case(cnt_main)
        							4'd0:	begin state <= START; 
        							end	//I2C通信時序中的START
        							4'd1:	begin data_wr <= 8'h90; 
        							state <= WRITE; 
        							end	//A0,A1,A2都接了GND,寫地址為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通信時序中的START
        							4'd4:	begin state <= START; 
        							end	//I2C通信時序中的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的采樣數據
        							4'd7:	begin state <= STOP; 
        							adc_done <= 1'b1; 
        							end	//I2C通信時序中的STOP,讀取完成標志
        							4'd8:	begin state <= MAIN; 
        							end	//預留狀態,不執行
        							default: state <= IDLE;	//如果程序失控,進入IDLE自復位狀態
        						endcase
        					end
        				START:begin	//I2C通信時序中的起始START
        						if(cnt_start >= 3'd5) cnt_start <= 1'b0;	
        						//對START中的子狀態執行控制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每個周期2.5us,需要兩個周期
        							3'd2:	begin sda_out_r <= 1'b0; 
        							end	//SDA拉低到SCL拉低,保持4.0us以上
        							3'd3:	begin sda_out_r <= 1'b0; 
        							end	//clk_400khz每個周期2.5us,需要兩個周期
        							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每個周期2.5us,需要兩個周期,返回MAIN
        							default: state <= IDLE;	//如果程序失控,進入IDLE自復位狀態
        						endcase
        					end
        				WRITE:begin	//I2C通信時序中的寫操作WRITE和相應判斷操作ACK
        						if(cnt <= 3'd6) begin	//共需要發送8bit的數據,這里控制循環的次數
        							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	//兩個變量都恢復初值
        							else begin cnt_write <= cnt_write + 1'b1; 
        							cnt <= cnt; end
        						end
        						case(cnt_write)
        							//按照I2C的時序傳輸數據
        							3'd0:	begin scl_out_r <= 1'b0; 
        							sda_out_r <= data_wr[7-cnt]; 
        							end	//SCL拉低,并控制SDA輸出對應的位
        							3'd1:	begin scl_out_r <= 1'b1; 
        							end	//SCL拉高,保持4.0us以上
        							3'd2:	begin scl_out_r <= 1'b1; 
        							end	//clk_400khz每個周期2.5us,需要兩個周期
        							3'd3:	begin scl_out_r <= 1'b0; 
        							end	//SCL拉低,準備發送下1bit的數據
        							//獲取從設備的響應信號并判斷
        							3'd4:	begin sda_out_r <= 1'bz; 
        							end	//釋放SDA線,準備接收從設備的響應信號
        							3'd5:	begin scl_out_r <= 1'b1; 
        							end	//SCL拉高,保持4.0us以上
        							3'd6:	begin if(sda_out) state <= IDLE; 
        							else state <= state; 
        							end	//獲取從設備的響應信號并判斷
        							3'd7:	begin scl_out_r <= 1'b0; 
        							state <= MAIN; 
        							end	//SCL拉低,返回MAIN狀態
        							default: state <= IDLE;	//如果程序失控,進入IDLE自復位狀態
        						endcase
        					end
        				READ:begin	//I2C通信時序中的讀操作READ和返回ACK的操作
        						if(cnt <= 3'd6) begin	//共需要接收8bit的數據,這里控制循環的次數
        							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	//兩個變量都恢復初值
        							else begin cnt_read <= cnt_read + 1'b1; 
        							cnt <= cnt; 
        							end
        						end
        						case(cnt_read)
        							//按照I2C的時序接收數據
        							3'd0:	begin scl_out_r <= 1'b0; 
        							sda_out_r <= 1'bz; 
        							end	//SCL拉低,釋放SDA線,準備接收從設備數據
        							3'd1:	begin scl_out_r <= 1'b1; 
        							end	//SCL拉高,保持4.0us以上
        							3'd2:	begin adc_data_r[7-cnt] <= sda_out; 
        							end	//讀取從設備返回的數據
        							3'd3:	begin scl_out_r <= 1'b0; 
        							end	//SCL拉低,準備接收下1bit的數據
        							//向從設備發送響應信號
        							3'd4:	begin sda_out_r <= 1'b0; a
        							dc_done <= 1'b1; 
        							adc_data <= adc_data_r; 
        							end	//發送響應信號,將前面接收的數據鎖存
        							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狀態
        							default: state <= IDLE;	//如果程序失控,進入IDLE自復位狀態
        						endcase
        					end
        				STOP:begin	//I2C通信時序中的結束STOP
        						if(cnt_stop >= 3'd5) cnt_stop <= 1'b0;	//對STOP中的子狀態執行控制cnt_stop
        						else cnt_stop <= cnt_stop + 1'b1;
        						case(cnt_stop)
        							3'd0:	begin sda_out_r <= 1'b0; 
        							end	//SDA拉低,準備STOP
        							3'd1:	begin sda_out_r <= 1'b0; 
        							end	//SDA拉低,準備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狀態
        							default: state <= IDLE;	//如果程序失控,進入IDLE自復位狀態
        						endcase
        					end
        				default:;
        			endcase
        		end
        	end 	
        	assign	scl_out = scl_out_r;	//對SCL端口賦值
        	assign	sda_out = sda_out_r;	//對SDA端口賦值 
        	endmodule

        小結

        本節主要為大家講解了使用I2C驅動PCF8591的ADC功能的原理及軟件設計,需要大家掌握的同時自己創建工程,通過整個設計流程,生成FPGA配置文件加載測試。



        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 和龙市| 山东省| 清水河县| 江孜县| 田林县| 台安县| 亚东县| 财经| 吉水县| 兴义市| 额尔古纳市| 科技| 大名县| 阿城市| 乌拉特中旗| 岚皋县| 丽江市| 林芝县| 涟源市| 梨树县| 连江县| 南宫市| 庄浪县| 建始县| 临汾市| 绥中县| 河曲县| 甘孜县| 通山县| 大厂| 姜堰市| 湟中县| 金门县| 易门县| 五大连池市| 龙州县| 雅安市| 高尔夫| 乌兰浩特市| 南平市| 华容县|