日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

DAC、ADC、FFT使用总结

發布時間:2023/12/9 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 DAC、ADC、FFT使用总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

      • 計算公式
      • 波形生成
      • DAC波形頻率
      • ADC采樣時間
      • 離散傅里葉變換DFT
      • FFT

計算公式

DAC、ADC、FFT之間有些參數環環相扣,所以先整合一下公式。

1.系統時鐘周期72MHZ。

2.定時器的單個時鐘周期。

3.定時器的觸發周期。

4.正弦波一個周期的時間,其中N為正弦波一個周期的點數。

5.正弦波的頻率。

6.adc的采樣頻率,其中n為一個波形周期的采樣點,fsin為被采樣的波形的頻率。

7.為設置adc的采樣頻率,要根據這個公式去配置觸發adc的定時器。

8.進行傅里葉變換后,fft輸出數組下標對應的頻率。其中i為數組下標,fadc為adc的采樣頻率,fftnum為fft計算的點數。

波形生成

x取值范圍[0,2π]

y=sin(x)取值范圍[-1,1]

y=six(x)+1取值范圍[0,2]

DAC輸出電壓范圍[0,Vmax]

將y取值范圍擴大到DAC輸出電壓范圍,只需y=((six(x)+1)/2)*Vmax

周期2π,波形一個周期的點數為N,兩點間距2π/N

下面這個代碼,增加了一個DAClength為的是與DAC的DMA=normal配合,使DAC輸出幾個周期波形后,停止輸出,滿足一些特定電路的需求(有的電路起始階段需要幾個波形之后就停止在一個高電位,并不需持續輸出波形)。

如果僅生成一個波形周期,那么可以無視DAClength參數和下面的復制周期。

/*** 生成正弦波數據點函數* @param NPoints 一個周期內的點數* @param DAClength 目的輸出點數總數,DAClength為NPoints整數倍* @param VMaxRange 輸出的電壓最大值,取值范圍0~3.3V* @param SineWaveTable 存放生成的數據點*/ void SineWaveGen(uint32_t NPoints, uint32_t DAClength, float VMaxRange, uint16_t* SineWaveTable) { #ifndef PI #define PI 3.14159265358979323846 #endifint i = 0;int j = 0; int k = DAClength/NPoints; //增加波形后的周期數double radian = 0; // 弧度double setup = 0; // 弧度和弧度之間的大小double voltage = 0; // 輸出電壓setup = (2 * PI) / NPoints; // 兩點之間的間距while (i < NPoints){voltage = VMaxRange / 2.0 * (sin(radian) + 1.0); // 計算電壓//printf("%d %lf\r\n",i,voltage);SineWaveTable[i] = (uint16_t)(voltage * 4095 / 3.3); // 電壓轉為DAC數值//printf("%d %d\r\n",i,SineWaveTable[i]);radian += setup; // 下一個點的弧度i++;}for(j=1; j<k;++j)//復制k-1個周期{for(i=0;i<NPoints;++i){SineWaveTable[NPoints*j+i]=SineWaveTable[i];//printf("%d %d\r\n",NPoints*j+i,SineWaveTable[NPoints*j+i]);}}}

DAC波形頻率

設置波形一個周期的點數,會影響DAC輸出波形頻率。

如果波形一個周期128個點,10k頻率。不改變定時器設置的話,波形一個周期256個點,輸出波形頻率就變成了5k。

如果按下面配置定時器觸發DAC,觸發頻率為72M/141=510638HZ。

波形一個周期點數為128,那么波形頻率為510638HZ/128=3,989HZ,約為4k。

如果編寫如下代碼,波形一個周期點數為128,但是復制了九個周期的波形到dac數組里。

并且設置DMA的數據傳輸數量(0至65535)為128*9,最終得到的波形仍然是4k。

如果配置DMA Mode為Normal的話,那么可以發現觸發一次DAC,輸出了九個周期的頻率為4k的波形。

#define POINTS 128 #define DAC_length 1152 uint16_t SineWaveTable[DAC_length]; SineWaveGen(POINTS,DAC_length, 2, SineWaveTable);//points擴展到daclength HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)SineWaveTable, DAC_length, DAC_ALIGN_12B_R);

ADC采樣時間

ADC使用若干個ADC_CLK周期對輸入電壓采樣,采樣周期數目可以通過ADC_SMPR1和ADC_SMPR2寄存器中的SMP[2:0]位更改。每個通道可以分別用不同的時間采樣 。

總轉換時間 :TCONV = 采樣時間+ 12.5個周期

設置ADC輸入時鐘為12Mhz,那么1個ADC周期占用的時間=1 / 12MHZ = 0.0833334 uS

如果設置采樣時間為1.5個周期,那么一次采樣總的時間 = 采樣時間 + 12.5個周期 = 1.5周期 + 12.5周期 = 14周期 = 14 * 0.0833334 = 1.166667 uS

兩次采樣間隔時間 = 1.166667 uS (ADCCLK為12MHZ時的最小采樣間隔時間)

下面是ADC轉換的時序圖,可知,定時器觸發adc轉換的時間間隔需要大于adc兩次采樣間隔時間。

如果按照下面這樣配置觸發adc的定時器,72M/141=510638HZ

轉換成時間就是1.95us,大于adc兩次采樣間隔時間,所以ok。

離散傅里葉變換DFT

一開始其實是打算手寫一個DFT,但是實際用的時候無法滿足單片機性能需求。單片機里面的算法,最好只有加減乘,不能有除。

離散傅里葉變換:

N為時域離散信號的點數,n為時域離散信號的編號(取值范圍為0 ~ N-1),m為頻域信號的編號(取值范圍為0 ~ N-1),頻域信號的點數也為N。

離散傅里葉變換的輸入為N個離散的點(時域信號),輸出為N個離散的點(頻域信號,頻域信號的每個點都用一個復數表示)。

那么可以根據以上公式寫一個dft:

#include<iostream> #include<cmath> using namespace std; double a[128] = {2.028, 2.038, 2.041, 2.032, 2.020, 2.026, 2.070, 2.070, 2.060, 2.061, 2.042, 2.037, 2.072, 2.080, 2.064, 2.063, 2.037, 2.032, 2.060, 2.051, 2.038, 2.034, 2.003, 1.990, 2.007, 2.005, 1.988, 1.978, 1.942, 1.936, 1.951, 1.940, 1.914, 1.902, 1.862, 1.850, 1.858, 1.852, 1.822, 1.810, 1.760, 1.755, 1.767, 1.751, 1.725, 1.716, 1.659, 1.648, 1.659, 1.652, 1.625, 1.607, 1.560, 1.544, 1.566, 1.550, 1.525, 1.510, 1.469, 1.454, 1.481, 1.469, 1.440, 1.434, 1.389, 1.380, 1.406, 1.401, 1.383, 1.377, 1.337, 1.333, 1.365, 1.360, 1.345, 1.339, 1.309, 1.310, 1.345, 1.348, 1.342, 1.347, 1.316, 1.322, 1.359, 1.358, 1.352, 1.355, 1.338, 1.347, 1.393, 1.397, 1.395, 1.406, 1.388, 1.403, 1.452, 1.459, 1.464, 1.474, 1.460, 1.479, 1.522, 1.535, 1.547, 1.561, 1.545, 1.562, 1.611, 1.625, 1.633, 1.651, 1.640, 1.659, 1.707, 1.718, 1.728, 1.743, 1.727, 1.738, 1.792, 1.803, 1.798, 1.818, 1.807, 1.816, 1.865, 1.873,}; double b[128]; const double PI = acos(-1.0); //定義一個結構體來描述一個復數 typedef struct {float re;// reallyfloat im;// imaginary } complex,*pcomplex;//構建并初始化一個復數結構體 complex complexBuild(float re,float im) {complex cx;cx.re=re;cx.im=im;return cx; }//復數加法 complex complexAdd(complex a,complex b) {complex ret;ret.re=a.re+b.re;ret.im=a.im+b.im;return ret; } //復數乘法 complex complexMult(complex a,complex b) {complex ret;ret.im=a.im*b.re+a.re*b.im; ret.re=a.re*b.re-a.im*b.im;return ret; }void DFT(complex x[],complex X[],int N) {int k,n;complex Wnk;for (k=0; k<N; k++) {X[k].re=0;X[k].im=0;for (n=0; n<N; n++) {//帶公式 Wnk.re=(float)cos(2*PI*k*n/N);Wnk.im=(float)-sin(2*PI*k*n/N);X[k]=complexAdd(X[k],complexMult(x[n],Wnk));}} }int main() {complex samples[128],_out[128];double _out2[128];int i;for(int i=0; i<128; ++i) {b[i]=a[i];samples[i].re=b[i];samples[i].im=0;printf("%.3f\n",b[i]);//printf("%.3f\n",b[i]);} // for (i=0; i<120; i++) { // samples[i].re=i; // samples[i].im=0; // }DFT(samples,_out,128);//求幅值 for(i=0;i<128;++i){_out2[i]=sqrt(_out[i].re*_out[i].re+_out[i].im*_out[i].im);} // for (i=0; i<120; i++) { // if(i==0) // printf("(%f,%f)\n",_out[i].re/128,_out[i].im); // else // printf("(%f,%f)\n",_out[i].re/64,_out[i].im); // }//數據處理 for (i=0; i<128; i++) {if(i==0)printf("%f\n",_out2[i]/128);elseprintf("%f\n",_out2[i]/64);} } /* int main() {//memset(b,0,sizeof(b));for(int i=0; i<120; ++i) {b[i]=a[i]/4096.0*3.3;printf("%.3f\n",b[i]);}} */

由dft變換后的幅度可以看出波形的一些特征。比如直流偏置為1.674602。

fi是,進行傅里葉變換后,fft輸出數組下標對應的頻率。其中i為數組下標,fadc為adc的采樣頻率,fftnum為fft計算的點數。

按照之前的設置,adc的采樣頻率為510638HZ,fft計算的點數為128,那么

i=1時,f1=510638HZ/128=3989HZ。約等于4k,剛好對應上dac輸出的正弦波的頻率。

FFT

最后選擇使用dsp庫里面的fft進行傅里葉變換。

下面是一部分核心代碼。直接做1024個點的fft。

#include "arm_math.h" #include "arm_const_structs.h" #define FFT_LENGTH 1024 float fft_inputbuf[FFT_LENGTH * 2]; float fft_outputbuf[FFT_LENGTH]; //部分代碼:for (int i = 0; i < FFT_LENGTH; i++){fft_inputbuf[i * 2] = adc1_buff[i] * 3.0 / 4095;//實部賦值,* 3 / 4096是為了將ADC采集到的值轉換成實際電壓//printf("%.4f\r\n",fft_inputbuf[i * 2]);fft_inputbuf[i * 2 + 1] = 0;//虛部賦值,固定為0.}arm_cfft_f32(&arm_cfft_sR_f32_len1024, fft_inputbuf, 0, 1);arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH); /*處理變換結果*/fft_outputbuf[0] /= FFT_LENGTH;for (int i = 1; i < FFT_LENGTH; i++)//輸出各次諧波幅值{fft_outputbuf[i] /= FFT_LENGTH/2;}/*打印結果*/printf("FFT Result:\r\n");for (int i = 0; i < FFT_LENGTH; i++)//輸出各次諧波幅值{printf("%d:\t%.4f\r\n", i, fft_outputbuf[i]);}

但是實際操作過程中,模擬電路設計的有問題,導致adc讀取數據在一定范圍向上偏斜。

所以改變思路,做128個點的fft,去掉最大最小取平均。

adc讀取數據一直向上偏,是電路本身存在問題,如果用算法去抵消這個影響,其實并沒有解決本質問題。

int bnum = FFT_LENGTH / POINTS;//倍數int inum = 1;//4k對應fft128下標float resultzhi[bnum];//直流float resultfen[bnum];//分流for(int j=0;j<bnum;++j){for(int i=0;i<POINTS;++i){fft_inputpoint[i*2] = adc1_buff[j*POINTS+i] * 3.0 / 4095;fft_inputpoint[i*2+1] = 0;}arm_cfft_f32(&arm_cfft_sR_f32_len128, fft_inputpoint, 0, 1);arm_cmplx_mag_f32(fft_inputpoint, fft_outputpoint, POINTS); resultzhi[j]=fft_outputpoint[0]/POINTS;resultfen[j]=fft_outputpoint[inum]/(POINTS/2);//fft_outputpoint[inum]/=POINTS/2;//resultfen[j]=fft_outputpoint[inum];}float zhi=0,fen=0;for(int i=1;i<bnum-1;++i)//一定是首位最值{zhi+=resultzhi[i];fen+=resultfen[i];}zhi=zhi/(bnum-2);fen=fen/(bnum-2);printf("%.4f\r\n",zhi);printf("%.4f\r\n",fen);

后面經過一系列測試,得到一系列非線性的公式,無論選用何種擬合手段,都無法滿足精度需求,所以,只能繼續修改模擬電路。

由此,感悟就是,測量得到兩個值之間不是線性關系。此時很多人會從算法層面切入。

但是還有一種方式就是從硬件層面切入,使用合適的電路,讓兩個值之間是線性關系。

總結

以上是生活随笔為你收集整理的DAC、ADC、FFT使用总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。