Gas station without pumps

2017 August 15

Beacon detector SPI interface

Filed under: Uncategorized — gasstationwithoutpumps @ 12:53
Tags: , , ,

In Beacon detector board, I introduced the hardware for the IR beacon detector, and in Digital tone detection with Goertzel’s algorithm, I discussed the algorithm used for detecting a 2kHz signal in a noisy background.  In this post I’ll talk about the software for interfacing the board to microcontrollers as if it were a standard peripheral device.

When I was designing the hardware, I had to choose between providing a UART interface, an SPI interface, or an I2C interface. At one point, I considered including all three, since I had just enough pins to do that and plenty of board space for connectors.

My son urged me to use a UART interface, since asynchronous communication is much easier work with than the two synchronous interfaces—there are no tight timing constraints. UART communications tends to be slow (115200 baud is a common top speed, resulting in a maximum of about 11.5–12.8kbytes/second transferred), and the two sides must agree on the baud rate to within 2.5%. But there are no latency constraints—you can respond to a request whenever you are finally ready—so there are no worries if you need to wait for a timer task to complete before communicating. The main downside of a UART interface (besides the usually low speed) is that it is a one-to-one interface. Two pins are needed for each UART a microcontroller uses, while the synchronous interfaces provide shared bus communication.

I knew that I could do a UART interface and for the small amounts of data I needed to communicate (10–75 bytes every 60th of a second, or 600–4500 bytes/s) it would be fast enough. But I wanted a challenge that would help me learn something new, so I decided to do a synchronous interface. Of the two choices supported by the Teensy LC hardware, SPI and I2C, the SPI interface looked the simpler to implement in software, as the timing seemed a little less challenging, and there was a FIFO that could be loaded with several bytes of information to transfer, reducing the timing demands. (In retrospect, I2C may have been easier—I’ve not tried it, though, so there may be some unexpected challenges.)

I created the board with only one I/O port (other than the USB port used for programming the board), the SPI0 4-wire interface using pins CS=D10=PTC4, MOSI=D11=PTC6, MISO=D12=PTC7, SCK=D13=PTC5.  I used a 6-pin right-angle header at the edge of the board, so that I would be able to use a ribbon connector to connect to another microcontroller. The order of the pins is the same as Digilent uses for their PMOD connector—an arbitrary choice, but there does not seem to be a commonly accepted standard for SPI connections.

The choice of pins was the first mistake I made in the SPI design—I should have used SPI1 instead of SPI0.  In reading the reference manual for the KL26 chip that is the basis for the Teensy LC, the SPI FIFO is only implemented for SPI1, and not for SPI0.  Also, the legal SPI clock speeds are different for the two interfaces:  SPI0 is clocked off the bus clock (default 24MHz on the Teensy LC) and SPI1 off the system clock (default 48MHz). The external clock SCK is allowed to go from 0Hz to ¼ the module clock, so 6MHz for SPI0 and 12MHz for SPI1.

The lack of a FIFO on SPI0 made the timing for loading the transmit buffer a little tricky.  The hardware has two registers for the transmit: the shift register used for serializing a byte and the transmit buffer that holds the next byte to transfer to the serial register.  If the transmit buffer is not loaded in time for the serial shifting, then the SPI interface will duplicate the previous byte, messing up communication.

The Teensyduino library contains SPI master software, but not SPI slave software.  I was wondering about the reason for that before I started my coding, but I quickly realized why—the timing for SPI is almost entirely under control of the master and the slave is constrained to respond at times specified by the master.  The most common SPI protocols for peripherals consist of the master sending a 1-byte (or longer) command to the slave and getting a response back in the next byte slot(s).  This gives the slave about half a clock period to interpret the command and prepare the response. With a 1MHz SCK, that gives the processor about 500ns to set up the response. That’s plenty of time for an all-hardware system, but is a very tight constraint for a software implementation.  In fact, given that the interrupt latency on the KL26 can be about 800ns, it is an impossible constraint—I don’t even know that I need to send a byte, much less figure out what it is, in the available time.  The more complicated I2C interface allows a slave to stretch out the clock when it can’t respond in time, but the SPI interface doesn’t allow the slave to change the clock rate.

So I couldn’t use the most common SPI interface protocol of command followed by immediate response. I had a few choices of workarounds for the latency problem:

  • Have a standardized packet of material that is sent independent of what the master does.  This seems to be the solution used by SPI slave libraries others have written for the Teensy boards (not part of the standard Teensyduino library).  It allows high throughput, especially if DMA is used to load the transmit information, but is not very flexible.  It is not a commonly used protocol on SPI peripherals.
  • Require the master to wait before asking for the response bytes.  This is very flexible and allows the SPI bus to run at maximum speed during transfers, but is not really in the spirit of a synchronous interface—the slave should respond at a standardized time, and the master should not have to know how long it will take the slave to be ready to send.
  • Send a dummy byte in the slot after the request so that I have a full byte time (8 SCK periods) instead of half a bit time to get the right information ready. A disadvantage is that the throughput is reduced—the dummy bytes take up bus time without communicating useful information. If the protocol calls for a 1-byte command and a 1-byte response, then each transfer would take 3 byte times.  (A 1-byte command and an n-byte response takes n+2 byte times.)

I ended up using a slight variant of the dummy-byte approach: I allow the master to send new commands before the first response comes back.  Dummy bytes are only added when there are no real bytes to send, so a series of n commands requesting 1-byte responses could be sent in a row, with the total time for the transfer taking n+2 byte times—the first two slots of which the slave is sending dummy bytes, and the next n are the requested data.

To make this protocol work, I had to ensure that the SPI interrupt priority is higher than any other interrupts in the program, in particular, higher than the PIT timer that I was using for the 15kHz sampling frequency.  This means that the SPI transmissions will introduce jitter into the ADC sampling—I’ve not yet determined how much of a problem this will be, but I suspect it won’t be much of a problem, since it will only affect one or two samples in the 250-sample filter block.  The default interrupt priority on the KL26 processor in the Teensy LC is for all interrupts to be at the highest priority, so I had to lower the PIT priority rather than raising the SPI priority.

The basic notion is that I keep the SPI transmit buffer always full.  When there is a transmit-buffer-empty interrupt, I load the buffer from a software FIFO I maintain, or with a dummy byte (0xff) if there is nothing in the FIFO.  The responses to a command are loaded into the FIFO.  As long as the SPI interrupt response (including interpreting the command and any FIFO loading or popping) takes less than the time of one byte, the protocol runs smoothly.  With commands that can send up to 4-byte responses, I was able to run SCK up 1.25MHz without losing synchronization.

My current, minimal command set for the beacon detector has the following commands:

  1. Freeze the information from filter so that all subsequent commands refer to the same set of data.  Return 1 on success, 0 on failure.  (The failure is only possible if the request is sent too soon after a reset of the beacon detector board.)
  2. Report which channel has the highest power (1-byte: 0xff if no beacon detected, otherwise 0..7)
  3. Report the estimated angle of the beacon in 0.1° units (2-bytes: 0..3599)
  4. Report the power in the max-power channel (4-bytes: unsigned int)

I could also report the power in specific channels and the power from an alternative (3kHz instead of 2kHz) filter for each channel, which would be another 64 bytes of information, but I’ve not implemented those commands yet.  The minimal set I currently have responds with 8 bytes of information, so takes 10 byte times for a burst of communication. With the highest clock rate I’ve gotten to work (SCK=1.25MHz), a full packet takes 64µs, which would interfere with only one or two samples of the 15kHz sampling.

I may play around with different command sets after I get another board programmed as a master to communicate with the beacon detector and determine what information I really need from the beacon detector.  One possibility is to use just a single 2-byte response that encodes everything but the power: that would allow a 4-byte-time packet (25.6 µs).

Setting up the SPI software ran into all the usual problems with setting up a peripheral on a Kinetis ARM processor—there are many registers that need to be set up correctly, and nothing works until they are all right:

  • Turn on the SPI module clock: SIM_SCGC4 |= SIM_SCGC4_SPI0
  • Set the SPI control registers SPI0_C1 and SPI0_C2 for 4-wire SPI with CPOL=1, CPHA=1, 8-bit mode, interrupt on receiving a byte or on transmit buffer empty.
  • Set up the isr to interpret the recieved bytes and keep the transmit buffer full.                                           
  • Configure the 4 pins used to be ALT 2 (SPI0) rather than the default GPIO.
  • Set the priorities using NVIC_SET_PRIORITY(IRQ_SPI0, 0) and NVIC_SET_PRIORITY(IRQ_PIT_CH1, 64).  The KL26 processors have only 4 interrupt priority levels that are multiples of 64.  Lower numbers are higher priority.
  • Enable the interrupts with NVIC_ENABLE_IRQ(IRQ_SPI0)

I debugged the SPI interface using the protocol and logic analyzer tools in the Analog Discovery 2.  I found the user documentation for these tools really awful (almost no information), but I managed to get things working by trial and error (except for the “sensor” script options for the SPI protocol tool—that always resulted in the spinning beachball and force-quitting Waveforms 2015).  I initially tested with the “Master” tab of the protocol tool, but I fairly quickly switched to using the “Custom” tool which allowed me to set up more complicated sequences of reads and writes, though eventually I simplified back to writes that could be done with the “Master” tab.

The “debug” mode of the protocol tool sets up the logic analyzer to see the signals, but then doesn’t provide the decoding of the protocol in the protocol tool—one has to go to the logic analyzer to set up the triggering, switch to the protocol tool to send the stimulus, then switch back to the logic analyzer to see the results.  It is possible to get the protocol tool and the logic analyzer tool in different windows (double-click on the tab for the tool you want in a new window), but my screen is too small for showing both windows in sufficient resolution at once.

To see when the interrupt service routine for the SPI was active I added CORE_PIN1_PORTSET = CORE_PIN1_BITMASK to the beginning of the isr and CORE_PIN1_PORTCLEAR = CORE_PIN1_BITMASK, and used some wirewrap wire to get the pin 1 signal out to the logic analyzer. I’d not added test points for the unused pins, but because the Teensy board is socketed with female headers, it was easy to wrap a wire around the pin before pushing the board into the female headers, and use a loose header pin to connect the other end of the wire to DIO6 of the logic analyzer.

Here is a result of a test of sending the 4 commands in a row in an 11-byte frame, to make sure that the dummy bytes occur in the right places (in the first 2 and last slots of the 11-byte frame):

The MISO line shows the correct responses from the beacon detector: 0xff, 0xff, 1 (freeze ok), 0 (channel 0), 0x00e1=225=22.5°, power=0x08436020=138633248.

As it turns out, the end of the isr for the 4-byte response comes 0.4µs later than it should if there had not been any other bytes already in the queue. Indeed, if I start with the “4” command, things just barely work, because the isr routine starts a little sooner. The 6.8µs delay to the end of the isr routine suggests to me that the maximum safe rate for SCK is only about 1.15MHz, not 1.25MHz.  I’ll have to do a lot of testing with many data packets, to see what rate is really safe without intermittent errors.  That will have to wait until I’ve written a program to act as the master (unless I can figure out why the “sensor” script keeps crashing Waveforms 2015).

2017 July 13

Analog Discovery 2 oscilloscope input impedance fixed

Filed under: Circuits course,Data acquisition — gasstationwithoutpumps @ 23:25
Tags: , ,

This morning in Analog Discovery 2 oscilloscope input impedance, I wrote

I cannot fit a model based on the input divider circuit to the data—I keep getting a negative capacitance for C9 or C8, so that they can cancel each other.  These models also make C1 around 50pF.

So I can reconcile the DC behavior (1.044MΩ is well within the ±2% measurement error of the nominal 1.04MΩ), but not the AC behavior of the scope inputs.

I must be missing something, but what?  Any useful suggestions (which don’t involve modifying the Analog Discovery 2) are welcome.

This evening I figured out what I was missing. The model I was trying to fit was the following one for the oscilloscope, with a 2MΩ resistor in series as the reference impedance:

There is a natural, internal split into an 820kΩ and 220kΩ resistance in the input voltage divider (component numbers here are for channel 1, but channel 2 is identically designed).

What I was missing was parasitic capacitance from the breadboard and scope wiring. If I model a capacitor (Cref) in parallel with the 2MΩ resistor and another capacitor (Cextra) in parallel with the scope, I can get a good fit.  I can leave all the internal resistors and capacitors at their nominal values, and fit for several different values for the trim capacitor C8:

I can get an excellent fit with Rref being only a little over 1% off and reasonable parasitic capacitance values.

Analog Discovery 2 oscilloscope input impedance

Filed under: Circuits course,Data acquisition — gasstationwithoutpumps @ 11:43
Tags: ,

I have been blindly trusting the documentation for the Analog Discovery 2 that claims that the input impedance of the differential oscilloscope channels is 1MΩ || 24pF.  Yesterday, when my son was measuring the input impedance of the reference inputs of a sigma-delta ADC, we had reason for a while to doubt that claim, so I measured the input impedance in a simple way:  I put a 2MΩ 1% resistor in series with one input channel and used the other input channel to measure the voltage across the series pair. I swept the network analyzer from 10Hz to 1MHz, and recorded the voltage gain (and the phase).  The voltage gain is not well-fit by a simple (1MΩ || 24 pF) model, as the impedance does not keep decreasing.  It seems to be well modeled by a model with Rs+(Cp||Rp), though.

The reported voltage gain for Channel 2/Channel 1 is well fit by a voltage divider.

Because the Analog Discovery 2 reports gain for Channel 2/Channel 1, I had to invert the data to get it in the form I wanted for my model (I could, alternatively, have swapped the legs of the voltage divider in the model).

The phases were also well fit by the models, though I did not use the phase information in the fitting. (I tried refitting using just the phase information, but that did not change the parameters by much, nor did it visibly improve the fit of the measured phases, so I left the parameters with just the amplitude fit.)

The DC impedances are both 1.044MΩ, very close to the specified value, but the capacitor is over twice the specified value, and not directly in parallel with the resistance.  The reference resistor I used is supposed to be a 2MΩ±1% resistor, but it was part of a cheap assortment, and we’ve found these cheap assortments to often be slightly out of spec, so I’d not trust it to be better than ±2%.

The 200kΩ/800kΩ split is not very surprising, when we look at the circuit for the input divider of the oscilloscope (from the hardware reference manual):

There is a natural, internal split into an 820kΩ and 220kΩ resistance in the input voltage divider (component numbers here are for channel 1, but channel 2 is identically designed).

The appearance of the 200kΩ/800kΩ split in the model for the input impedance suggests that the trimmer capacitor C8 is not properly adjusted.  If all capacitance and resistance values were nominal, then C8 should be set to 39.59pF, to provide a flat response from the voltage divider (at the high-gain setting), producing an input impedance of (1.04MΩ || 26pF).  But C8 only has a 5–20pF range, so perhaps there are some other, parasitic capacitances that change the desired trimming.

I cannot fit a model based on the input divider circuit to the data—I keep getting a negative capacitance for C9 or C8, so that they can cancel each other.  These models also make C1 around 50pF.

So I can reconcile the DC behavior (1.044MΩ is well within the ±2% measurement error of the nominal 1.04MΩ), but not the AC behavior of the scope inputs.

I must be missing something, but what?  Any useful suggestions (which don’t involve modifying the Analog Discovery 2) are welcome.

2017 July 1

Multiple-feedback bandpass active filter

Filed under: Uncategorized — gasstationwithoutpumps @ 12:14
Tags: ,

I wanted to improve my understanding of active filters, beyond the very simple ones that I included in my textbook, so I decided to try implementing a bandpass filter using the multiple-feedback topology.  To make the design more concrete, I decided to make one that was centered at 2kHz, but firmly rejected 1.5kHz and 2.5kHz.

Here is the design I came up with. The top part is the filter, while the bottom is just a reference voltage buffer. I’ll derive the values below.

I_1= (V_{in} -V_{m})/R_1
I_2= (V_{ref}-V_{m})C_{2} s = (V_{o}-V_{ref})/R_2
I_3= (V_{ref}-V_{m})/R_3
I_4= (V_{o}-V_{m})C_{1}s
I_1 + I_2 + I_3 +I_4 = 0

We can simplify a bit, by replacing V_{m}-V_{ref} with -(V_{o}-V_{ref})/(\tau_2 s), where \tau_2 = R_2 C_2. Note that this corresponds to replacing the amplifier, R2, and C2 with an inverting high-pass filter.

It is conventional to recenter the voltages so that V_{ref} is zero, which simplifies the algebra.  Doing that gives us

(V_{in}+V_{o}/(\tau_2 s))/R_1 + V_{o}/R_2 + V_{o}/(\tau_2 s R_3) + V_{o}(1+1/(\tau_2 s))C_{1}s=0

Multiplying both sides by \tau_2 s and rearranging gives us

-V_{in}\tau_2 s/R_1  = V_{o} \left(1/R_1 + \tau_2 s /R_2 + 1/R_3 + (\tau_2 s +1)(C_1 s)\right)

\frac{V_{o}}{V_{in}} = -\tau_2 s / \left({1+ \tau_2 s R_1/R_2 + R_1/R_3 + (\tau_2 s +1)(R_1 C_1 s)}\right)

More correctly, that should be \frac{V_{o}-V_{ref}}{V_{in}-V_{ref}} = \ldots, but I’ll just talk about the gain G.

Defining \tau_1 = R_1 C_1 we get

G = -\tau_2 s / \left(1+ R_1/R_3+  (\tau_2 R_1/R_2 + \tau_1) s + \tau_1\tau_2 s ^2)\right)

G = -(\tau_2 s/R_1 )/ \left(1/R_1+1/R_3+  (\tau_2 /R_2 + \tau_1/R_1) s + \tau_1\tau_2 s ^2/R_1)\right)

If we have C_1=C_2=C, which is a common design constraint, we can simplify to

G = -(R_2 C s/R_1 )/ \left(1/R_1+1/R_3+  2 C s +C\tau_2 s ^2)\right)

G = -(R_2 /R_1 )/ \left((1/R_1+1/R_3)(Cs)^{-1}+  2  +\tau_2 s)\right)

We can define R_p = R_1 || R_3 = R_1R_3/(R_1+R_3) and \tau_p = R_p C_1.  Then we have

G = -(R_2 /R_1 )/ \left((\tau_p s)^{-1}+  2  +\tau_2 s)\right)

If we let s = j \omega, we get

G = -(R_2 /R_1 )/ \left(2  +j (\tau_2 \omega-(\tau_p \omega)^{-1})\right),

whose magnitude is maximized when \tau_2 \omega-(\tau_p \omega)^{-1}=0 or \omega = 1/\sqrt{\tau_2\tau_p}, and at that maximum the gain is - R_2/ (2 R_1).

The corner frequencies, where the gain drops by the square-root of 2 occur when the real and imaginary parts of the denominator have the same magnitude, or \tau_2 \omega-(\tau_p \omega)^{-1}= \pm 2. If we take only the positive values of \omega, we get \omega = \left(\pm 1 + \sqrt{(\tau_p+\tau_2)/\tau_p}\right) /\tau_2, for a bandwidth of 2/\tau_2, or 1/(\pi R_2 C_2) Hz.

For C=10nF and R2=220kΩ, we get a bandwidth of 144.7Hz.  With R1=6.8kΩ and R3=300Ω, Rp=287.32Ω, with time constant 2.8732 µs, giving a center frequency of 2001.8kHz.  I built this filter and tested it with the Analog Discovery 2:

The overall match of the filter to the theory is pretty good.

But when we look in detail at the peak, we can see that the center frequency is off a little and the gain isn’t nearly as big as it should be. I suspect that the problem is poor matching of the very cheap 10nF capacitors.

I also tried exciting the filter with wideband noise from the Analog Discovery 2, and doing an FFT of the input and output to get a different view of the response:

The filter response looks a little larger taking the difference of FFTs (after averaging many FFTs, since each individual one has a lot of noise). Note the 60 Hz spike.

2017 June 28

Changing the loudspeaker lab

Filed under: Circuits course,Data acquisition — gasstationwithoutpumps @ 21:38
Tags: ,

I’ve started looking at how the loudspeaker lab in the applied-electronics course will change, given that we will have the Analog Discovery 2 USB oscilloscope in the labs.  There are two main changes in capabilities:

  • hundreds of measurements can be made in a couple of minutes from 10Hz to 1MHz
  • phase information is available, not just amplitude information.

The availability of phase information means that we can try fitting complex impedance, and not just the magnitude of impedance.  Unfortunately, the gnuplot curve fitting that we’ve been using is only set up for fitting real-valued functions, not complex-valued ones, and I don’t really want to switch to a more full-featured model fitting program, as they get hard to use.

Today I gathered data from two loudspeakers: the tiny 1cm one that I have posted about before (Ultrasonic rangefinder tests with tiny loudspeaker and Redoing impedance test for tiny loudspeaker) and an 8Ω oval loudspeaker that we used to use in the class until Parts Express sold out of them (Loudspeaker analysisBetter model for loudspeaker). I only collected data out to 1MHz, since my previous experience indicated that the network analyzer was not very useful past that frequency (Loudspeaker impedance with Analog Discovery 2). I put a 22Ω resistor in series with the loudspeaker and measured the voltage across it on channel 1, with the voltage across the loudspeaker on channel 2.  I recorded the “gain”, which is channel2/channel1, and the phase for channel 2 relative to channel 1.  The gain times 22Ω is the magnitude of the impedance of the loudspeaker.

The formula that I’ve been using for impedance modeling, R + ja\omega^\beta + (R_m || Z_{L_m} || Z_{C_m}), turned out not to be a good fit for the phase.  I played around for a couple of hours with different, more complicated models, before finally settling on a model that is as simple as the one I’ve been using, but that fits pretty well:

R + a (j\omega)^\beta + (R_m || Z_{L_m} || Z_{C_m})

The difference is just that I’m including the j inside the exponentiation, so that the “inductor-like” component is now in direction j^\beta instead of j—that is, the asymptotic phase angle is \sin^{-1}(\beta) instead of 90°.

To do the fitting, I alternated between fitting some of the parameters to the magnitude of the impedance and some to the phase. After I had layed around a bit and gotten ball-park estimates of the parameters, I did a more careful fitting. I started by fitting the series R to just the low-frequency end of the data, then fitting the resonance peak without the “inductor-like” component in the model with data collected in just a decade of frequency around the resonance peak (I used a separate data file that had 600 data points for just that decade).  I fit the three parallel parameters using the phase data, then refit the parallel resistance using the amplitude data.  To start in the right region, I set initial values R_m = R_{peak}-R, C_m = 0.3/\omega, and L_m = 3.3/\omega, with R_{peak} being the magnitude of the impedance at the top of the peak, located at frequency  f, with \omega = 2 \pi f.

With those parameters set, I fit \beta with phase information for the high frequencies, and a with amplitude information.

I then tweaked the fit by refitting the resonance peak, refitting \beta and a, and repeating the refits.

The 6-parameter fit for the magnitude is pretty good.

The 6-parameter fit for the oval loudspeaker is not great for phase, but is not too bad.

The 6-parameter fit for the tiny loudspeaker is pretty good, though I did not attempt to model the two higher frequency resonances that are visible, despite knowing that the resonance around 9kHz rings seriously.

The resonances are somewhat more visible in the phase plot for the tiny loudspeaker than they are in the magnitude plot.

I’m going to have to write up a tutorial for the book on using a real-valued fitting method for fitting a complex-valued function.

Next Page »

Create a free website or blog at WordPress.com.

%d bloggers like this: