forked from peterhinch/micropython-fourier
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdftclass.py
139 lines (127 loc) · 6.23 KB
/
dftclass.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# dftclass.py Python interface for FFT assembler code
# Author: Peter Hinch
# 5th Feb 2018
# PYBOARD_DBOFFSET calculation.
# dB default scaling is relative to 1.0VRMS on Pyboard. Assume a 1VRMS sinewave.
# Appropriate DC bias is assumed and does not affect calculation below.
# 1.0VRMS = 1.414VP. ADC full scale = 4095 @ 3.25V. Hence ADC will output
# a peak value 0f 4095*1.414/3.25 = 1782.
# DB conversions ignore the conjugates. So the peak value in the frequency bins
# in the 1st half of the array will be 1782/2 = 891. Hence
# dB offset = round(20*math.log10(891)) = 59
PYBOARD_DBOFFSET = const(59)
import array
import math
import pyb
from dft import fft
from uctypes import addressof
from window import winapply, setarray, icopy
from polar import topolar
import utime
# Control: on entry r1 should hold one of these values to determine the direction and scaling
# of the transform. Only bit 0 now used by fft()
REVERSE = const(0) # Inverse transform (frequency to time domain)
FORWARD = const(1) # Forward transform
POLAR = const(3) # bit 2: Polar conversion
DB = const(7) # bit 3: Polar with dB conversion
# Instantiating the class creates the real, imaginary and control arrays, populates real and imaginary
# with zero. Populates the control array with these values:
# ctrl[0] = length of data array
# ctrl[1] = no. of bits to represent the length
# ctrl[2] = Address of real data array
# ctrl[3] = Address of imaginary data array
# ctrl[4] = Byte Offset into entry 0 of complex roots of unity
# ctrl[5] = Address of scratchpad for use by fft code
# After this is an array of seven complex nos followed by one for the roots of unity.
# The first complex no. is initialised to the initial u value. The rest make up a scratchpad used by fft()
# see ctrlmap.ods for more detail.
class DFT(object):
def __init__(self, length, popfunc=None, winfunc=None):
bits = round(math.log(length)/math.log(2))
assert 2**bits == length, "Length must be an integer power of two"
self.dboffset = 0 # Offset for dB calculation
self._length = length
self.popfunc = popfunc # Function to acquire data
self.re = array.array('f', (0 for x in range(self._length)))
self.im = array.array('f', (0 for x in range(self._length)))
if winfunc is not None: # If a window function is provided, create and populate the array
self.windata = array.array('f', (0 for x in range(self._length))) # of window coefficients
for x in range(0, length):
self.windata[x] = winfunc(x, length)
else:
self.windata = None
COMPLEX_NOS = 7 # Size of complex buffer area before roots of unity
ROOTSOFFSET = COMPLEX_NOS*2 # Word offset into complex array of roots of unity
bits = round(math.log(self._length)/math.log(2))
self.ctrl = array.array('i', [0]*6)
self.cmplx = array.array('f', [0.0]*((bits +1 +COMPLEX_NOS)*2))
self.ctrl[0] = self._length
self.ctrl[1] = bits
self.ctrl[2] = addressof(self.re)
self.ctrl[3] = addressof(self.im)
self.ctrl[4] = COMPLEX_NOS*8 # Byte offset into complex array of roots of unity
self.ctrl[5] = addressof(self.cmplx) # Base address
self.cmplx[0] = 1.0 # Initial value of u = [1 +j0]
self.cmplx[1] = 0.0 # Intermediate values are used by fft() and not initialised
self.cmplx[12] = 1.0/self._length # Default scaling multiply by 1/length
self.cmplx[13] = 0.0 # ignored
i = ROOTSOFFSET
creal = -1
cimag = 0
self.cmplx[i] = creal # Complex roots of unity
self.cmplx[i +1] = cimag
i += 2
for x in range(bits):
cimag = math.sqrt((1.0 - creal) / 2.0) # Imaginary part
self.cmplx[i +1] = cimag
creal = math.sqrt((1.0 + creal) / 2.0) # Real part
self.cmplx[i] = creal
i += 2
@property
def scale(self):
return self.cmplx[12]
@scale.setter
def scale(self, value): # Allow user to override default
self.cmplx[12] = value
@property
def length(self):
return self._length # Read only
def run(self, conversion): # Uses assembler for speed
if self.popfunc is not None:
self.popfunc(self) # Populate the data (for fwd transfers, just the real data)
if conversion != REVERSE: # Forward transform: real data assumed
setarray(self.im, 0, self._length)# Fast zero imaginary data
if self.windata is not None: # Fast apply the window function
winapply(self.re, self.windata, self._length)
start = utime.ticks_us()
fft(self.ctrl, conversion)
delta = utime.ticks_diff(utime.ticks_us(), start)
if (conversion & POLAR) == POLAR: # Ignore complex conjugates, convert 1st half of arrays
topolar(self.re, self.im, self._length//2) # Fast
if conversion == DB: # Ignore conjugates: convert 1st half only
for idx, val in enumerate(self.re[0:self._length//2]):
self.re[idx] = -80.0 if val <= 0.0 else 20*math.log10(val) - self.dboffset
return delta
# Subclass for acquiring data from Pyboard ADC using read_timed() method.
class DFTADC(DFT):
def __init__(self, length, adcpin, winfunc=None, timer=6):
super().__init__(length, winfunc = winfunc)
self.buff = array.array('i', (0 for x in range(self._length)))
if isinstance(adcpin, pyb.ADC):
self.adc = adcpin
else:
self.adc = pyb.ADC(adcpin)
if isinstance(timer, pyb.Timer):
self.timer = timer
else:
self.timer = pyb.Timer(timer)
self.dboffset = PYBOARD_DBOFFSET # Value for Pyboard ADC
def run(self, conversion, duration):
tim = self.timer
tim.deinit()
tim.init(freq = int(self._length/duration))
self.adc.read_timed(self.buff, tim) # Note: blocks for duration
start = utime.ticks_us()
icopy(self.buff, self.re, self._length) # Fast copy integer array into real
super().run(conversion)
return utime.ticks_diff(utime.ticks_us(), start)