Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.
|
Forum Index : Microcontroller and PC projects : Raspbery Pi Pico FFT Routines
Author | Message | ||||
asknik2022 Regular Member Joined: 26/03/2022 Location: United KingdomPosts: 92 |
Has anybody created a FFT Code/prgram for creating audio spectrum using Raspberry pi pico with MMBASIC to display on a LCD TFT Display. |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4255 |
Not specific for audio, but my mains monitor has 1 window showing the spectrum. PicomiteVGA PETSCII ROBOTS |
||||
asknik2022 Regular Member Joined: 26/03/2022 Location: United KingdomPosts: 92 |
Hi Volhout Where can I find it |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4255 |
Not specific for audio, but my mains monitor has 1 window showing the spectrum. PicomiteVGA PETSCII ROBOTS |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4255 |
hi asknik, This is the thread, but I realize, reading it, that I never published the code. When I get the the installation where the monitor works(this weekend), I will get the code and add it. The code reads ADC in bursts in alternating buffers (one fills, while the other is processed), and calculates RMS and FFT from the data buffers. Using MATH commands. mains monitor Volhout Edited 2023-04-06 16:15 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4255 |
This is the code of the last version that used FFT (it is removed now). '+-------------------------------------------------------------------+ '| | '| mains voltage monitor | '| | '| | '| Version 0.11 05-09-2022 Volhout@thebackshed.com | '+---------------------------------'+-------------------------------------------------------------------+ ' using background logging from ADC pingpong-ing in arrays a() and b() ' pre-processing in interrupt routine to offload main from time critical ' functions. ' main handles display functions and file handling ' Version control ' 0.01 Initial version with RMS running in main ' 0.02 Version with RMS and PEAK in interrupt ' 0.03 Trend buffers, PEAK removed, math(sd a()) for RMS, ignore offset ' 0.04 Trend graph drawn and peak indicator shown ' 0.05 Show actual RMS value in top left screen ' 0.06 Show date$/time$, plot waveform ' 0.07 Add FFT screen ' 0.08 remove many variables, hard coded values ' 0.09 make trend graph a rolling graph ' 0.10 adapted cal value 230V, remove duplicates from list,2 wave periods ' 0.11 trend graph 5 hours ' to do ' calculate distortion ' write logs to SD ' detect events ' write events to SD '---------------- definitions ------------------- 'init time get_time 'hardware parameters------------------------------------------ ' scale = 265.2'322.8 'transformer gain (tune) ' 'tune this value with your hardware--------------------------- 'sampling core values fm=50 'intende mains frequency n=512 'samples multiple of 2 for fft freq = 2000 'sampling frequency target = 40 samples per period calcfreq=fm 'start value for frequency calculation 'adjust the sampling frequency so the sampling more or less 'lines up with the mains frequency to avoid resampling math 'this is not super accurate, but gets the accuracy within 0.5% freq = freq*n/(n\fm*fm) 'corrected sampling freq to sync 50Hz Print "sampling frequency set to :";freq;" Hz" 'trend buffer 'there are 2 buffers, 1 with actual data, one with display coordinates m=Int(60*(freq/n)) 'size collect samples for 60 seconds 'Print "trend buffer :";m 'm=4 'dummy, for sampling every second t=MM.HRes-2 'size trend plot data buffers 'display definitions 'trend graph rmsColor=RGB(green) 'line color rmsmin=210 'graph window in voltage rmsmax=250 trendpeak=RGB(red) 'colors for peak trendvaley=RGB(cyan) 'and valey trp=1 'trend display pointer pointer 'current value curColor=RGB(white) 'text color 'event list evColor=RGB(cyan) 'text color normal evColorAlarm=RGB(red) 'text color alarm AlarmLevel=230*1.04 'when to change color (230+4%) 'wave graph wavColor=RGB(green) 'line color wp=1 'wave pointer 'fft graph fftColor=RGB(green) 'line color avg%=0 'running average for mains frequency measfreq=0 'frequency measured on GP8 from zero crossing detector 'memory Option base 1 Dim a(n),b(n) 'sample ping pong buffers Dim c(n),d(n) 'sample processing buffer 'Dim pk(m),rm(m) 'buffers for peak and rms values for 1 minute Dim rmt(t),rmd(t) 'buffers for trend capture and trend display tp=1 'trend index pointer ap=0 'trend averaging pointer Dim rmp(5)=(0,0,0,0,0) 'array with 5 peak values Dim rmh$(5) 'string array with associated time/date Dim rmdummy(5),rmindex%(5) 'help arrays for sorting 'open the ADC and start first conversion, from here on the ADC is 'restarted in the interrupt routine. adcRange=3.3 ADC open freq,1,INT_RDY ready=0:pingpong=1 ADC start a() 'connect zero crossing detector to GP6 SetPin gp8,fin 'use build in frequency measurment Pause 1000 'skip 1 measurement period dummy=Pin(gp8) 'make sure there is a value 'main routine CLS Do drawCurrent drawTrend drawList drawWave drawFFT Pause 1000 Loop End Sub drawCurrent 'draw the outline Box 1,1,158,78,1,RGB(white) 'write current value Text 6,6,Left$(Str$(rms),5),"L",4,3,curColor Text 79,48,"Vrms","C",1,2,curColor End Sub Sub drawList 'draw the outline Box 1,80,158,78,1,RGB(white) Text 6,84," VRMS WHEN","L",1,1,RGB(yellow) Math add rmp(),0,rmdummy() 'make a local copy Sort rmdummy(),rmindex%() 'mak a index list For i=1 To 5 j=rmindex%(i) If rmp(j)>AlarmLevel Then colr=evColorAlarm Else colr=evColor EndIf Text 6,156-(12*i),Left$(Str$(rmp(j)),6),"L",1,1,colr Text 66,156-(12*i),rmh$(j),"L",1,1,colr Next i End Sub Sub drawWave 'convert the analog data to display data gain=-76/adcRange 'ADC values between 0 and 3.3V plot over 76 pixels Math scale c(),gain,d() 'use d() to double buffer c() Math add d(),76,d() 'find first zero crossing index wp (vWSize/2) typically wp < 40 'with f=50Hz and 2kHz sampling. The graphs will display phase aligned. wp=0:th=76/2 Do wp=wp+1 If wp > 50 Then wp=1:Exit Loop Until ((d(wp) > th) And (d(wp+1) < th)) 'and plot the graph roughly 2 periodes For i=1 To 156/2 '156 'erase previous pixel by eraing a whole vertical line Line 2*i+160,2,2*i+160,76,1,RGB(black) Line 2*i+161,2,2*i+161,76,1,RGB(BLACK) 'Line i+161,2,i+161,76,1,rgb(BLACK) 'plot the new pixel/line Line 2*i+160,d(i+wp-1),2*i+162,d(i+wp),1,wavColor 'Line i+160,d(i+wp-1),i+161,d(i+wp),1,wavColor Next i Box 161,1,158,78,1,RGB(white) End Sub Sub drawFFT 'draw the outline Box 161,80,158,78,1,RGB(white) ' Text 3*curHSz/2,3*curVSz/2,"FFT","C",1,1,RGB(cyan) Math add c(),-adcRange/2,c() 'remove offset before fft Math fft magnitude c(),d() 'do fft 'scale the graph to fit the window fftMax=n*adcRange/8 'not sure why this is /8 gain=-58/fftMax 'pure sine has all n samples in 1 peak Math scale d(),gain,d() 'use d() to double buffer c() Math add d(),154,d() 'now plot the first n/2 samples or as much as fit the graph size fftRange = Min(n/2,158)-3 For i=1 To fftRange 'erase previous pixel by eraing a whole line Line i+162,94,i+162,155,1,RGB(black) 'plot the new pixel Line i+161,d(i),i+162,d(i+1),1,fftColor Next i 'find the highest peak (since d() contains plot values (screen origin is top 'so data is inverted) >= find min. 'dummy = Math(min d(),p%) 'find what sample represents the peak to align text 'If p% < n/2 And p% > 1 Then 'not calc error or offset ' calcfreq=(127*calcfreq+(p%-1)/n*freq)/128 ' 'Print "freq ";calcfreq;" Hz" 'EndIf 'Text 163+p%,83,Left$(Str$(calcfreq),4)+" Hz","L",1,1,fftColor 'alternative frequency indication through gp8 measfreq=(Pin(gp8)+avg%*measfreq)/(avg%+1) avg%=Min(avg%+1,15) 'max 15 averages Text 163,83,Left$(Str$(measfreq),4)+" Hz","L",1,1,fftColor End Sub 'interrupt routine gets data from ADC and does pre-processing 'interrups happen every n/freq seconds Sub INT_RDY 'Timer =0 'restart adc with new buffer If pingpong Then 'start filling b() and process a() ADC start b() rms = Math(SD a()) 'Math add b(),0,c() 'make a copy for wave and fft Else 'start filling a() and process b() ADC start a() rms = Math(SD b()) Math add b(),0,c() 'make a copy for wave and fft EndIf pingpong=1-pingpong 'calculate rms rms = scale * rms 'proces values in trend sample timeframe rms_high = Max(rms, rms_high) 'rms_min = Min(rms, rms_min) ap=ap+1 If ap=m Then 'fill circular buffer rmt(tp)=rms_high tp=(tp Mod t)+1 ap=0 rms_high=rms EndIf 'check rms value against event list If rms > Math(max rmp()) Then 'check if it is a milivolt duplicate Local isnew=1 For i=1 To 5 If rmh$(i)=Left$(Time$,5)+" "+Left$(Date$,5) Then rmp(i)=rms 'do not add a new entry, but update value old isnew=0 EndIf Next i If isnew Then dummy=Math(min rmp(),p%) 'remove the lowest in the list rmp(p%)=rms rmh$(p%)=Left$(Time$,5)+" "+Left$(Date$,5) EndIf ' Math v_print rmp() EndIf 'Print Timer 'ready=1 End Sub ' Draw the trend graph at the lower half of the screen ' scale the trend buffer to the window defined Sub drawTrend 'convert the rmt data to display data (rmd) gain=76/(rmsmin-rmsmax) Math add rmt(),-rmsmax,rmd() 'rmd is also a double buffer Math scale rmd(),gain,rmd() Math add rmd(),161,rmd() 'put the data in the window 'find the peaks in this data array pk = Math(max rmt(),pp%) mn = Math(min rmt(),pv%) 'and plot the graph starting from tp (the last data entered) trp = tp For i=2 To t 'erase previous pixel by erasing a whole vertical line Line i,162,i,238,1,RGB(black) 'plot the new pixel Line i-1,rmd(trp),i,rmd((trp Mod t)+1),1,rmsColor trp=(trp Mod t)+1 'map indicators to rolling trend Next i 'map indicatorss to rolling graph pp%=((t+pp%-tp) Mod t)+1 pv%=((t+pv%-tp) Mod t)+1 'plot the peak in the graph Line pp%,163,pp%,238,1,trendpeak Line pv%,163,pv%,238,1,trendvaley 'write the value in place position depending location peak If pp% < 160 Then Text pp%+2,163,Left$(Str$(pk),5)+" V","L",1,1,trendpeak Else Text pp%-2,163,Left$(Str$(pk),5)+" V","R",1,1,trendpeak EndIf If pv% < 160 Then Text pv%+2,227,Left$(Str$(mn),5)+" V","L",1,1,trendvaley Else Text pv%-2,227,Left$(Str$(mn),5)+" V","R",1,1,trendvaley EndIf 'draw box Box 1,161,319,118,1,RGB(white) End Sub Sub get_time 'debug not needed with OPTION RTC AUTO ENABLE 'Time$="10:10:10" 'Date$="20-10-2010" Exit Sub 'for manual entry Local string dummy$ Input "Set time HH:MM:SS ";dummy$ Time$ = dummy$ Input "Set date DD-MM-YYYY ";dummy$ Date$ = dummy$ End Sub This is a 512 sample FFT The key parts are : Get ADC samples 'open the ADC and start first conversion, from here on the ADC is 'restarted in the interrupt routine. adcRange=3.3 ADC open freq,1,INT_RDY ready=0:pingpong=1 ADC start a() 'interrupt routine gets data from ADC and does pre-processing 'interrups happen every n/freq seconds Sub INT_RDY 'restart adc with new buffer If pingpong Then 'start filling b() and process a() ADC start b() Math add b(),0,c() 'make a copy for wave and fft Else 'start filling a() and process b() ADC start a() Math add b(),0,c() 'make a copy for wave and fft EndIf pingpong=1-pingpong End Sub Create FFT: Sub drawFFT 'draw the outline Box 161,80,158,78,1,RGB(white) ' Text 3*curHSz/2,3*curVSz/2,"FFT","C",1,1,RGB(cyan) Math add c(),-adcRange/2,c() 'remove offset before fft Math fft magnitude c(),d() 'do fft 'scale the graph to fit the window fftMax=n*adcRange/8 'not sure why this is /8 gain=-58/fftMax 'pure sine has all n samples in 1 peak Math scale d(),gain,d() 'use d() to double buffer c() Math add d(),154,d() 'now plot the first n/2 samples or as much as fit the graph size fftRange = Min(n/2,158)-3 For i=1 To fftRange 'erase previous pixel by eraing a whole line Line i+162,94,i+162,155,1,RGB(black) 'plot the new pixel Line i+161,d(i),i+162,d(i+1),1,fftColor Next i 'find the highest peak (since d() contains plot values (screen origin is top 'so data is inverted) >= find min. dummy = Math(min d(),p%) 'find what sample represents the peak to align text If p% < n/2 And p% > 1 Then 'not calc error or offset calcfreq=(127*calcfreq+(p%-1)/n*freq)/128 'Print "freq ";calcfreq;" Hz" EndIf Text 163+p%,83,Left$(Str$(calcfreq),4)+" Hz","L",1,1,fftColor End Sub Edited 2023-04-07 02:47 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
Amnesie Guru Joined: 30/06/2020 Location: GermanyPosts: 396 |
Hi Volhout, interestingly enough, I searched for your mains monitor program a few days ago but couldn't find it because I forgot how it's called (mains monitor obviously!). I would like to build this project, too. It is really really interesting. I saw in your linked topic a schematic; is it still "up-to-date"? Why is the FFT removed in your latest version? I am just curious Will there be another topic (or a ressurection of the old one) with your latest program code? Questions over questions, but it is such a nice little device, which I would like to build after your original draft, since Netherlands and Germany have the same mains voltage. Greetings Daniel |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4255 |
Hi Daniel, I created the mains monitor full of idea's of analysis tools. But after using it for few months I decided the fft was not used at all, and I would like to have more information about minimum and maximum voltages. Maybe I would look at the THD (harmonic distortion, or "klirrfactor"), that could be calculated from FFT, but the graph did not say much. I also thought about registering (log files) ADC samples of each peak, so I could reconstruct what happened. But I did not look at it ever. So the mains monitor changed. Simpler, and printing trend files (BMP's) to SD card. That gives far more insight in far less time. No more FFT's, no log files with samples, no long lists of events, simply only trend graphs. The hardware (the opamp circuit) is still used. It gives less distortion. Remember when you build the opamp circuit that the 2 input resistors provide the isolation from the mains. They are high voltage resistors, not the common 1/4 watt carbo resitors. Since typical 1/4 watt resistors are 250V max, you should at least put 4 normal resistors in series to achieve the same safety. Volhout Edited 2023-04-07 07:07 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
Amnesie Guru Joined: 30/06/2020 Location: GermanyPosts: 396 |
Hi Volhout, thanks for your response! I think you are right. FFT is really nice but as you wrote, it is better to have some hard facts like minium / max voltages etc. Following the mains trend via images on sd card sounds good. Since I am originally coming from the tube amp world I am somewhat experienced with high voltage. I built a lot of tube amplifiers (but not Hifi, I am a guitar player), so I have all high voltage resistors. My geiger counter uses 10M ohms on the geiger tube, too. It would be nice, if you'd keep us updated how things are going. I definitly plan to build it! Greetings Daniel |
||||
Print this page |