基于FPGA控制PCF8591開展ADC采樣
- 前言
- 一、芯片手冊閱讀
- 1.設備地址
- 2.字節地址
- 3.IIC通信協議
- 二、仿真分析
- 三、代碼分析
- 總結
- 視頻演示
前言
這段時間做設計總是遇到一些傳感器模塊輸出模擬電壓,采集模擬電壓進而了解傳感器輸出的濃度占比,在淘寶上找到了一個4通道的ADC采集模塊,用這個來獲取不同傳感器的模擬電壓輸出正好,其實物如圖所示:
該模塊具有ADC采樣以及DAC輸出的功能,其中4路為ADC采樣輸入AIN0-4,一路DAC輸出AOUT。
與FPGA相連接的端口為VCC、GND、SCL、SDA,主要就是通信協議的實現,實現之前需要閱讀芯片手冊,獲取一下有用的關鍵信息,通信速率,從機設備地址、控制指令信息等等
產品參數:
使用注意事項:
供電:5V,你的adc輸入范圍就是0-5V,如果用3.3V供電,你的輸入范圍就是0-3.3V。不同的供電可能效果不同,我目前遇到過好多供電問題,然后換一種供電就工作正常,所以大家遇到問題時要不斷排除問題。
跳帽:你如果需要用adc通道采集外部的模擬電壓,你就需要對應通道的跳帽拔下來。否則數據一直是模塊本身自帶的數據。
一、芯片手冊閱讀
1.設備地址
結合兩個圖,發現還有A0-A2三個未知的比特,通過查看模塊原理圖,發現這三個引腳接地。所以設備地址確定為:0x1001000
寫數據就是0x90 讀數據就是0x91
2.字節地址
一堆英文不想看,看圖就行了,如下圖所示,每個比特干啥的都說的比較清楚。
從低位這邊看,[0:1]是負責說明對哪個通道進行操作的,前面也提到了總共4個通道。
然后[2]這個比特默認為1
然后[3]默認為0
然后[4:5]說明這四個通道的輸入模式,有差分輸入啥的,不選擇,我只需要最普通的那種輸入即可,所以默認00
然后[6]在ADC時為0,在DAC時為1
然后最高位默認為0
比如現在要對通道1進行ADC采樣轉化的功能實現,就要發送0x00000000
比如現在要對通道2進行ADC采樣轉化的功能實現,就要發送0x00000001
比如現在要對通道3進行ADC采樣轉化的功能實現,就要發送0x00000010
比如現在要對通道4進行ADC采樣轉化的功能實現,就要發送0x00000011
比如現在要對通道1進行DAC采樣轉化的功能實現,就要發送0x01000000
3.IIC通信協議
了解一下基本的信息,對后面實現通信流程有所參考。
如果發送完設備地址了,后面采樣轉化可以進行連續操作。
數字量跟模擬量的關系
完整的通信流程應該就是如下圖所示。
二、仿真分析
仿真測試文件忘了放哪里了。就是簡單的iic驅動,設備地址,字節地址,讀取數據的命令都知道了,就是按照時序進行發送就可以了。
三、代碼分析
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/04/01
// Module Name : pcf8591_adda
// Project Name : ad
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : AD電壓測量模塊
//
// Revision : V1.0
// Additional Comments:
// module pcf8591_ad
(input wire sys_clk , //輸入系統時鐘,50MHzinput wire sys_rst_n , //輸入復位信號,低電平有效input wire i2c_end , //i2c設備一次讀/寫操作完成input wire [7:0] rd_data , //輸出i2c設備讀取數據output reg rd_en , //輸入i2c設備讀使能信號output reg i2c_start , //輸入i2c設備觸發信號output reg [15:0] byte_addr , //輸入i2c設備字節地址output wire [15:0] adc_data1 , //adc1數據 output wire [15:0] adc_data2 //adc2數據
);//************************************************************************//
//******************** Parameter and Internal Signal *********************//
//************************************************************************//
//parameter define
parameter CTRL_DATA1 = 8'b0100_0001; //AD/DA控制字
parameter CTRL_DATA2 = 8'b0100_0010; //AD/DA控制字
parameter CNT_WAIT_MAX= 18'd6_9999 ; //采樣間隔計數最大值
parameter IDLE = 3'b001,AD_START1 = 3'b010,AD_CMD1 = 3'b100,WAIT = 3'b101,AD_START2 = 3'b110,AD_CMD2 = 3'b111;//wire define
wire [31:0] data_reg1/* synthesis keep */; //數碼管待顯示數據緩存
wire [31:0] data_reg2/* synthesis keep */; //數碼管待顯示數據緩存
//reg define
reg [17:0] cnt_wait; //采樣間隔計數器
reg [4:0] state ; //狀態機狀態變量
reg [7:0] ad_data1 ; //AD數據
reg [7:0] ad_data2 ; //AD數據wire [23:0] data_temp;
wire [23:0] data_temp1;wire [23:0] data_temp2;
wire [23:0] data_temp3;wire [23:0] po_data1 ;
wire [23:0] po_data2 ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_wait:采樣間隔計數器
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_wait <= 18'd0;else if(state == IDLE || state == WAIT )if(cnt_wait == CNT_WAIT_MAX)cnt_wait <= 18'd0;elsecnt_wait <= cnt_wait + 18'd1;elsecnt_wait <= 18'd0;//state:狀態機狀態變量
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginstate <= IDLE;byte_addr <= 16'b0;endelsecase(state)IDLE:beginbyte_addr <= 16'b0;if(cnt_wait == CNT_WAIT_MAX)state <= AD_START1;elsestate <= IDLE;endAD_START1:beginstate <= AD_CMD1;byte_addr <= CTRL_DATA1;endAD_CMD1:if(i2c_end == 1'b1)state <= WAIT;elsestate <= AD_CMD1;WAIT:beginif(cnt_wait == CNT_WAIT_MAX)beginstate <= AD_START2;endelsestate <= WAIT;endAD_START2:beginstate <= AD_CMD2;byte_addr <= CTRL_DATA2;endAD_CMD2:beginif(i2c_end == 1'b1)state <= IDLE;elsestate <= AD_CMD2;enddefault:state <= IDLE;endcase//i2c_start:輸入i2c設備觸發信號
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)i2c_start <= 1'b0;else if(state == AD_START1 || state == AD_START2)i2c_start <= 1'b1;elsei2c_start <= 1'b0;//rd_en:輸入i2c設備讀使能信號
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rd_en <= 1'b0;else if(state == AD_CMD1 || state == AD_CMD2)rd_en <= 1'b1;elserd_en <= 1'b0;byte_addr:輸入i2c設備字節地址
//always@(posedge sys_clk or negedge sys_rst_n)
// if(sys_rst_n == 1'b0)
// byte_addr <= 16'b0;
// else
// byte_addr <= CTRL_DATA;//ad_data:AD數據
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)ad_data1 <= 8'b0;else if((state == AD_CMD1) && (i2c_end == 1'b1)) //(state == AD_CMD) && (i2c_end == 1'b1))ad_data1 <= rd_data;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)ad_data2 <= 8'b0;else if((state == AD_CMD2) && (i2c_end == 1'b1)) //(state == AD_CMD) && (i2c_end == 1'b1))ad_data2 <= rd_data;//MQ-2:ppm = pow(11.5428 * 35.904 * Vrl/(25.5-5.1* Vrl),(1/0.6549));
//(1/0.6549)=1.53
// 11.5428 * 35.904 * Vrl/(25.5-5.1* Vrl)=(414*vrl)/(25.5-5.1* Vrl)
//
//data_reg:數碼管待顯示數據緩存assign data_reg1 = ((ad_data1 * 3300) >> 4'd8);
assign data_reg2 = ((ad_data2 * 3300) >> 4'd8);assign data_temp= (414*data_reg1);
assign data_temp1= 25500-(5*data_reg1);
assign data_temp2= (414*data_reg2);
assign data_temp3= 25500-(5*data_reg2);
//po_data:數碼管待顯示數據
assign po_data1 = data_temp2/data_temp3;
//轉換后的煙霧濃度0-150
assign po_data2 = data_temp/data_temp1;assign adc_data1 = po_data1[15:0];
assign adc_data2 = po_data2[15:0];
endmodule
我這是采集兩個通道的adc,對控制字節進行了切換,使用狀態機。
邏輯分析儀讀取的正常時序。
該說不說Quartus的邏輯分析儀使用感差于vivado。代碼兩個平臺是通用的
總結
視頻演示
FPGA環境監測火災監測家居監測(溫濕度模塊、煙霧模塊、PM2.5模塊等等)藍牙通信