簡易電壓表設計
實驗任務
實驗目的
在基礎數字電路實驗部分我們已經掌握了FPGA驅動獨立數碼管的原理及方法,本實驗主要學習模數轉換器ADC的相關知識,串行(SPI接口)ADC芯片ADC081S101的驅動設計,同時學習二進制數轉換BCD碼的設計方法。
本文引用地址:http://www.ex-cimer.com/article/202312/453845.htm設計框圖
根據前面的實驗解析我們可以得知,該設計可以拆分成三個功能模塊實現,
實驗原理
ADC介紹
數字系統,是用數字信號完成對數字量進行算術運算和邏輯運算的電路稱為數字電路,或數字系統。而我們生活的世界是模擬的,想要讓數字系統幫我們處理我們模擬世界的問題,就需要一個橋梁來溝通數字系統和模擬系統。
模數轉換器即A/D轉換器,或簡稱ADC,通常是指一個將模擬信號轉變為數字信號的電子元件。通常的模數轉換器是將一個輸入電壓信號轉換為一個輸出的數字信號。由于數字信號本身不具有實際意義,僅僅表示一個相對大小。故任何一個模數轉換器都需要一個參考模擬量作為轉換的標準,比較常見的參考標準為最大的可轉換信號大小。而輸出的數字量則表示輸入信號相對于參考信號的大小。
數模轉換器,又稱D/A轉換器,簡稱DAC,它是把數字量轉變成模擬的器件。D/A轉換器基本上由4個部分組成,即權電阻網絡、運算放大器、基準電源和模擬開關。模數轉換器中一般都要用到數模轉換器,模數轉換器即A/D轉換器,簡稱ADC,它是把連續的模擬信號轉變為離散的數字信號的器件。
作為模擬系統與數字系統轉換的橋梁,ADC和DAC有很多參數指標來標識其性能:
上圖兩個都是8位ADC模型,分辨率為 2的8次方等于256,即將Vref分成256份,能夠分辨的模擬步進為Vref / 256,量化數據N = 256 * Vin / Vref 。
ADC模塊電路連接
這里我們以STEP BaseBoard V3.0底板上的ADC模塊電路,其電路圖如下:
如ADC模塊電路所示,FPGA直接連接ADC081S101芯片的控制端,ADC有6個管腳,3腳Vin為VCC和Vref功能復用,即Vin = VCC = Vref。ADC前端是運放電路LMV721,運放模塊為電壓跟隨電路,再往前端是一個跳冒排針,用來選擇ADC采樣信號的來源,當短路帽將1、2腳短路時,ADC采集電位計電壓,當短路帽將2、3腳短路時,ADC采射頻端子或P4排針信號。本設計我們是采樣旋轉編碼器的電壓,所以需要用短路帽將1、2腳短路。
ADC模塊驅動設計
前面我們了解ADC081S101芯片和FPGA之間連接有三根線(cs、clk、din),兼容SPI總線,SPI是串行外設接口(Serial Peripheral Interface)的縮寫。SPI是一種高速的,全雙工,同步的通信總線,并且在芯片的管腳上只占用四根線(cs、sck、mosi、miso),事實上3根也可以(單向傳輸時),占用管腳少節約了芯片的管腳,同時為PCB的布局上節省空間,正是出于這種簡單易用的特性,如今越來越多的芯片集成這種通信協議。
SPI設備分為主設備和從設備,設備之間共用sck、mosi和miso,另外每個從設備有一根cs線(不共用),通信在主設備和從設備之間進行,從設備與從設備之間無法直接通信,主設備可以同時連接多個從設備,當主設備和某個從設備通信時,先控制該從設備cs信號拉低,然后通過sck、mosi和miso進行數據傳輸。
為了讓SPI總線更加靈活應用,SPI總線分為4種模式,由兩個參數控制:
SPI總線協議4種模式
ADC081S101管腳說明表:
注:SDATA信號在SCLK的節拍下傳輸數據,當SCLK下降沿時SDATA更新數據輸出,當驅動程序編程時我們要在上升沿采樣數據可以得到穩定的輸出。
ADC081S101串行通信時序如下圖:
注:
針對ADC081S101時序,我們用Verilog設計一個計數器,當計數器值不同時完成不同操作,實現一次ADC采樣,程序實現如下:
reg [7:0] cnt; //計數器 always @(posedge clk or negedge rst_n) if(!rst_n) cnt <= 1'b0; else if(cnt >= 8'd34) cnt <= 1'b0; else cnt <= cnt + 1'b1; reg [7:0] data;always @(posedge clk or negedge rst_n) if(!rst_n) begin adc_cs <= HIGH; adc_clk <= HIGH; end else case(cnt) 8'd0 : begin adc_cs <= HIGH; adc_clk <= HIGH; end 8'd1 : begin adc_cs <= LOW; adc_clk <= HIGH; end 8'd2,8'd4,8'd6,8'd8,8'd10,8'd12,8'd14,8'd16, 8'd18,8'd20,8'd22,8'd24,8'd26,8'd28,8'd30,8'd32: begin adc_cs <= LOW; adc_clk <= LOW; end 8'd3 : begin adc_cs <= LOW; adc_clk <= HIGH; end //0 8'd5 : begin adc_cs <= LOW; adc_clk <= HIGH; end //1 8'd7 : begin adc_cs <= LOW; adc_clk <= HIGH; end //2 8'd9 : begin adc_cs <= LOW; adc_clk <= HIGH; data[7] <= adc_dat; end //3 8'd11 : begin adc_cs <= LOW; adc_clk <= HIGH; data[6] <= adc_dat; end //4 8'd13 : begin adc_cs <= LOW; adc_clk <= HIGH; data[5] <= adc_dat; end //5 8'd15 : begin adc_cs <= LOW; adc_clk <= HIGH; data[4] <= adc_dat; end //6 8'd17 : begin adc_cs <= LOW; adc_clk <= HIGH; data[3] <= adc_dat; end //7 8'd19 : begin adc_cs <= LOW; adc_clk <= HIGH; data[2] <= adc_dat; end //8 8'd21 : begin adc_cs <= LOW; adc_clk <= HIGH; data[1] <= adc_dat; end //9 8'd23 : begin adc_cs <= LOW; adc_clk <= HIGH; data[0] <= adc_dat; end //10 8'd25 : begin adc_cs <= LOW; adc_clk <= HIGH; adc_data <= data; end //11 8'd27 : begin adc_cs <= LOW; adc_clk <= HIGH; adc_done <= HIGH; end //12 8'd29 : begin adc_cs <= LOW; adc_clk <= HIGH; adc_done <= LOW; end //13 8'd31 : begin adc_cs <= LOW; adc_clk <= HIGH; end //14 8'd33 : begin adc_cs <= LOW; adc_clk <= HIGH; end //15 8'd34 : begin adc_cs <= HIGH; adc_clk <= HIGH; end default : begin adc_cs <= HIGH; adc_clk <= HIGH; end endcase
到這我們就完成了串行ADC芯片ADC081S101的驅動設計,整個采樣周期用了35個系統時鐘,如果我們采用12MHz時鐘作為該模塊系統時鐘,采樣率Fs = 12M/35 = 343Ksps,ADC主頻Fsclk = 12 MHz /2 = 6MHz。
ADC081S101主頻及采樣率要求如下,按照要求我們當前的主頻和采樣率不足,所以在使用該模塊時,可以使用更高的時鐘(比如24MHz)以達到芯片的要求
注:時鐘頻率Fsclk,最小值為10MHz,最大值為20MHz,采樣率在500Ksps~1Msps
模塊接口如下:clk和rstn為系統時鐘及復位,adccs,adcclk和adcdat為ADC控制管腳,adcdata為ADC采樣數據,adcdone產生一個脈沖對應adc_data得到一個有效數據
module ADC081S101_driver ( input clk, //系統時鐘 input rst_n, //系統復位,低有效 output reg adc_cs, //SPI總線CS output reg adc_clk, //SPI總線SCK input adc_dat, //SPI總線SDA output reg adc_done, //ADC采樣完成標志 output reg [7:0] adc_data //ADC采樣數據 );
系統總體實現
因為需要更高的時鐘供ADC模塊使用,我們例化pll核得到24MHz時鐘,例化PLL的方法我們在基礎數字電路實驗部分已經練習過,這里就簡單描述一下過程
打開Tools菜單下的IP Catalog工具,依次找到Libraty → Basic Functions → Clocks; PLLs and Resets → PLL → ALTPLL,打開ALTPLL彈出配置界面,配置inclk0輸入為12MHz,配置c0的時鐘輸出為24MHz,其他所有選項全部默認,點擊Finish完成pll的IP核例化。
在頂層模塊VoltageMeas中,同時例化pll模塊和ADC081S101driver模塊,并將pll的c0輸出與ADC081S101_driver模塊的clk連線。
Pll模塊和ADC081S101_driver模塊的連接程序實現如下:
wire clk_24mhz,locked; pll u1 ( .areset (!rst_n ), //pll模塊的復位為高有效 .inclk0 (clk ), //12MHz系統時鐘輸入 .c0 (clk_24mhz ), //24MHz時鐘輸出 .locked (locked ) //pll lock信號輸出 ); wire adc_done; wire [7:0] adc_data;//使用I2C總線驅動PCF8591的ADC功能,例化 ADC081S101_driver u2(.clk (clk_24mhz ), //系統時鐘 .rst_n (rst_n ), //系統復位,低有效 .adc_cs (adc_cs ), //SPI總線CS .adc_clk (adc_clk ), //SPI總線SCK .adc_dat (adc_dat ), //SPI總線SDA .adc_done (adc_done ), //ADC采樣完成標志 .adc_data (adc_data ) //ADC采樣數據 );
現在可以得到ADC采樣數據了,假設ADC模擬輸入電壓為3.3V,理論上我們得到的采樣數據adc_data應該為8’hff,而電壓表最終顯示在數碼管上的數據應該為3.3,我們如何將8’hff轉換成可以顯示的3.3數據呢?這就設計到ADC量化數據的逆向運算了,
我們知道量化運算 N = 256 * Vin / Vref,
那么逆向運算為Vin = N * Vref / 256,其中Vref = 3.3V,所以Vin = N * 0.0129
所以我們需要用FPGA計算adc_data * 0.0129的結果,然后為了使用十進制的顯示,先將結果進行BCD轉碼,然后顯示在數碼管上。
將ADC采樣數據按規則轉換為電壓數據(乘以0.0129),這里我們直接乘以129,得到的數據經過BCD轉碼后小數點左移4位即可,程序實現如下:
wire [15:0] bin_code = adc_data * 16'd129;
將二進制數轉換成BCD碼的形式,采用左移加三的算法(以8’hff為例): 1、左移要轉換的二進制碼1位 2、左移之后,BCD碼分別置于百位、十位、個位 3、如果移位后所在的BCD碼列大于或等于5,則對該值加3 4、繼續左移的過程直至全部移位完成
二進制轉BCD碼程序實現如下:
reg [35:0] shift_reg; always@(bin_code or rst_n)begin shift_reg = {20'h0,bin_code}; if(!rst_n) bcd_code = 0; else begin repeat(16) begin //循環16次 //BCD碼各位數據作滿5加3操作, if (shift_reg[19:16] >= 5) shift_reg[19:16] = shift_reg[19:16] + 2'b11; if (shift_reg[23:20] >= 5) shift_reg[23:20] = shift_reg[23:20] + 2'b11; if (shift_reg[27:24] >= 5) shift_reg[27:24] = shift_reg[27:24] + 2'b11; if (shift_reg[31:28] >= 5) shift_reg[31:28] = shift_reg[31:28] + 2'b11; if (shift_reg[35:32] >= 5) shift_reg[35:32] = shift_reg[35:32] + 2'b11; shift_reg = shift_reg << 1; end bcd_code = shift_reg[35:16]; end end
最后得到20位的數據輸出,每4位表示一個BCD碼,所以有5位有效數據,這里我們還需要將小數點左移4位,計算出來的數應該是X.XXXX伏特,1個整數位和4個小數位,核心板上只有兩個數碼管,取最高的兩個BCD碼顯示到數碼管X.X伏特,個位小數點點亮,分位小數點熄滅,程序實現如下:
//個位數碼管模塊例化 Segment_led u4(.seg_dot (1'b1 ), //seg_dot input .seg_data (bcd_code[19:16]), //seg_data input .segment_led (seg_1 ) //MSB~LSB = SEG,DP,G,F,E,D,C,B,A ); //分位數碼管模塊例化 Segment_led u5(.seg_dot (1'b0 ), //seg_dot input .seg_data (bcd_code[15:12]), //seg_data input .segment_led (seg_2 ) //MSB~LSB = SEG,DP,G,F,E,D,C,B,A );
綜合后的設計框圖如下:
實驗步驟
實驗現象
將程序下載到FPGA中,P3接口用短路帽將1、2腳短路,旋轉底板右上角的電位計,觀察核心板數碼管變化,如果有萬用表可以測量P3短路處的電壓,與數碼管顯示對比。
評論