Spark Logic

$10 O-Scope revisited

ahull Sat May 09, 2015 12:56 pm

Over the last few days I’ve been playing with Ray’s STM32 Oscilloscope idea.

Image

Some preliminary results are available here..

https://github.com/pingumacpenguin/STM3 … ontruction

My take on the Scope Sketch is here..

https://github.com/pingumacpenguin/STM32-O-Scope

Its a fun little project and well worth building just for the entertainment value. Currently bandwidth is a little limited, but with a little extra effort it would be possible to make a fully functional 2MHz Scope for next to nothing. Ideal for probing i2c, audio and other low speed signals.

EDIT: We managed to squeeze the maximum speed out of the ADC, read further through the thread for details. The github code includes all of the enhancements made of the course of this thread.

Replies

ahull Thu May 21, 2015 10:19 am

Here are pictures of the screen, a real oscilloscope for comparison and a plot of the data.

Image

NOTE: uS per div is misleading, this should be the time per screen division, but currently I think it relates to the time per sample.

EDIT: What was odd was that that this figure is wrong. The total time taken for the samples is 2988.0 uS and the number of samples is 5120 so this gives a per sample time of 0.58359 uS per sample .. in other words we are seeing an issue here when we divide ints and the result is <1

I have corrected this and it gives a per sample time of 0.58uS which gives a figure of 1.713 Mega Samples per Second. This is pretty close to our minimum of 0.5us or 2 megasamples per second.

Quite why we end up with such a wildly incorrect answer when we divided our ints is another question.

Image

The same 1kHz reference signal viewed simultaneously on my Tektronix.

Image

An area of the data – plotted with gnuplot. You can see there is a fair amount of noise, but I suspect a lot of that comes from inadequate smoothing on the STM32F103 board. Additional caps across the ADC vref will probably eliminate a lot of this. Sample rate is 1.5 clock cycles per sample ( ADC_SMPR_1_5, /**< 1.5 ADC cycles */)

BTW if anybody is looking for a signal function generator, asearch ebay for AD9850 – These little boards are quite useful – in fact I can see another little project coming up. The STM32-Function Generator.

ahull Thu May 21, 2015 11:45 am

If I increase the ADC sample rate to 13.5 clock cycles from 1.5 clock cycles, I get a cleaner result. I now get 1.08 uS per sample or 925,925.925 samples per second.

Image

Much better, although there is a noticeable 50 kHz or so square wave ripple on the signal.

mrburnette Thu May 21, 2015 12:03 pm

Additional caps across the ADC vref will probably eliminate a lot of this.

If not on your board, a small inductor in the Vcc line to the Analog portion of the chip is also helpful. Original boards were 4 layers with an analog ground plane but my clone Maple Mini is a 2-sided rehash of the layout; somewhat inferior in design, but at $4 one has to be realistic about fabrication.

Good job. The beast is evolving into a very inexpensive, low-budget home-built tool for the weekend experimenter.

Ray

victor_pv Thu May 21, 2015 12:43 pm

We should probably use a pin further away from SPI MOSI, as the one used right now is next to it, and MOSI can generate quite some noise. Not sure if it is indeed, as the display access and capture happen at different times, but just in case…
Besides that, both ADC1 and ADC2 have a calibration function, which I believe is called upon initialization of the ports, but I am not certain, could be good to call it at some point in the sketch.
Then we have the single PSU issue. I am using a mini clone, and your pictures show another board that probably doesn’t have a separate PSU either.
As a test before going to sleep I trimmer the last 4 bits of the samples, leaving only 8 bits of resolution, plus I should have been trimming the 4 top bits as the capture is only for 12 bits, and I wasn’t so we should do that, that may reduce that noise too.
So in my list:
-Trim top 4 bits.
-Use different pin.
-Call calibration on both ports at start.
-Trigger ADC continuous capture a bit earlier.

Now, the ADC sample time can be adjusted on the fly. Would be a good idea if you could add a command to change that, so for slower signals we get a cleaner capture.

BTW, according to specs, when using 72Mhz on the MCU main clock, the fastest ADC capture time is 1.17 uS. As we use 2 ADCs we could get double the captures, so .58 uS, which is the times you say above that you get maximum. I think we have reached the speed limit of the chip, now all we can do is improve accuracy.

Use this loop after the ADC DMA finishes to trim the top 4 bits that don’t belong to the actual sample, may make a difference. I don’t have a real oscilloscope to compare :(

...
dma_disable(DMA1, DMA_CH1); //End of trasfer, disable DMA and Continuous mode.
// regs->CR2 &= ~ADC_CR2_CONT;
for (int16_t j = 0; j < maxSamples/2 ; j++ )
{
dataPoints32[j] &=0x0FFF0FFF;
}

ahull Thu May 21, 2015 1:03 pm

This is the “trimmed” version…

Image

victor_pv Thu May 21, 2015 1:15 pm

Not much of a difference, I guess the ADC was writing 0 in the unused bits.

Try trimming the 2 or 4 bottom bits of the samples with 0FF60FF6 or 0FF00FF0.
That will reduce noise for sure at the cost of losing some resolution.

Now, I remember reading an STM application note on how to increase an ADC capture resolution by using the noise in it.
I’ll have to find it and see if we can apply it to this case.

I thought on Roger’s idea for a frame buffer, but that would not work for this LCD and MCU. The display has 240×320 pixels, 2 bytes each, that needs around 144KB of RAM to store a frame.
Now that would work in the smaller 128×128 displays, that need 32KB for a frame buffer, but only using a bigger MCU.

Any way we should get quite a big speed improvement by using the reworked line drawing functions, or even changing the way the signal is printed now, so it uses more of the fastHline and fastVline whenever possible.

ahull Thu May 21, 2015 3:44 pm

I moved the ADC input to PB1, switched off my test output pin (the source of the 50kHz I suspect) and tidied up my cabling a bit.

Image

The above is with ADC ADC_SMPR_13_5 (1.08 uS per sample)- it looks a whole lot cleaner. I do need to see if I can somehow clean up the ADC supply, but that is a noticeable improvement. I’ll perhaps see if I can put the test pin on port C or whatever. The idea of having a test pin with a known frequency of squarewave still appeals, but I guess if it is going to cause issues, I may end up dropping it. Grounded shielding on the test signal cable might be all that is required.

I also need to look at adding a small capacitor in parallel with my 1 meg resistor (or some other little tweak) to trim up my probe and flatten off that annoying curve on the leading edge of the 1kHz test signal.

There seems to be a jitter between the two ADCs which I suspect we can eliminate by calibrating them.

This is pretty obvious when you look at some of the sample data on a flat spot on the waveform.

#Time, ADC, Value
7722.84 1 1997
7723.92 2 2015
7725.01 1 1997
7726.09 2 2015
7727.17 1 2001
7728.26 2 2015
7729.34 1 1996
7730.42 2 2015
7731.51 1 1997

victor_pv Thu May 21, 2015 4:22 pm

The calibrate function is this:

adc_calibrate(const adc_dev *dev)

So try adding them at some point at the start of the sketch when the pulse output is not going on.
I think the libmaple code runs that on start up, but not totally sure. In any case if it doesn’t help we just need to take it out:
adc_calibrate(ADC1);
adc_calibrate(ADC2);

I guess running the calibration while the PWM output is going on may affect the calibration.

We can set a 2 channel oscilloscope rather easily the exact same way, only providing a different input to each ADC, and changing some bits in the dual mode settings, so both capture at the same time. That should provide for almost 1Mhz per channel.

One other option, for digital signals, is to do a port capture rather than ADC. That could provide 15 channels in a single IO. I believe the max may be around 50Mhz on digital inputs with DMA, but would need to check.

ahull Thu May 21, 2015 6:48 pm

I added the ADC calibration lines, but it doesn’t seem to make a lot of difference.

Image

I changed a couple of other things.

1) I re-enabled the test pin, but dropped its frequency, and this seems to cause far less interference.
2) I am now measuring the test pin on the STM32 rather than the test signal on my real scope (because it is in the office and I am at home :D )
so bear in mind that although the noise appears to have increased this is due to the fact that were are looking at a 3v3 signal rather than a 5v signal.

As before the issue becomes apparent if we look at the data.

#Time(uS), ADC Number, value
7734.36 1 2194
7735.44 2 2174
7736.52 1 2196
7737.61 2 2176
7738.69 1 2195
7739.77 2 2176
7740.85 1 2197
7741.93 2 2173
7743.02 1 2197
7744.10 2 2176
7745.18 1 2193

ahull Thu May 21, 2015 7:33 pm

victor_pv wrote:The calibrate function is this:

adc_calibrate(const adc_dev *dev)

So try adding them at some point at the start of the sketch when the pulse output is not going on.
I think the libmaple code runs that on start up, but not totally sure. In any case if it doesn’t help we just need to take it out:
adc_calibrate(ADC1);
adc_calibrate(ADC2);

I guess running the calibration while the PWM output is going on may affect the calibration.

ahull Thu May 21, 2015 8:10 pm

I did the obvious and averaged out the errors i.e. I plotted the average of two adjacent ADC values (i.e. one from each ADC) to see what effect this would have.. Result is below.

Image

As you can see it had the effect I expected and removed the jitter. Obviously this is not the best way to eliminate the error, having the two ADCs agree would be better as it would preserve our bandwidth (averaging will have the effect of lower the bandwidth), but it shows what residual error remains once we eliminate this problem. By my metric calibrated eye, I guestimate the remaining error lies somewhere between 1% and 5% (so long as we don’t consider the slew contributed by the attenuator) which is pretty respectable. But we can do better!

EDIT: I should really fire up my analogue oscilloscope and compare, but its getting late here, so that looks like a task for tomorrow.

victor_pv Fri May 22, 2015 1:39 am

Do you see the error being consistently 15 to 20 no matter if it is at the top of the wave or at the bottom?
I thought the error was proportional, which would be more difficult to adjust, but if it is consistently that, we could try just adding 18 from the lower one, or subtracting 18 from the highest one.

About the start of the capture, we can set both ports to be in continuous mode and start capturing during the setup routine, and just start and stop the DMA transfer to memory.

And on the second bnc connector… well, up to you, but you can put a warning before posting the picture so anyone offended can just avoid it ;) otherwise I don’t think the silicone pig will care much where you drill an extra hole, he may squeal a little though… :lol:

ahull Fri May 22, 2015 1:55 pm

If I plot the points + half the difference between this point and the last, (i.e. half the difference between this ADCX value and the other ADC’s value) it looks pretty good, this is not optimal I suspect, there must be a better way to do this.

Image
Red line is a plot of the raw ADC points, Green is the same data + half the difference between this point and the last.

NOTE: The difference swaps between positive and negative at each point, in case you were trying to figure out why it is always added.
i.e. I am taking the difference between 45 and 50 to be +5 but the difference between 50 and 45 to be -5

I think the difference factor should be weighted over more than 2 conversions, otherwise it will have a disproportional effect on fast transients.
Can anybody think of a better way to do this?

Bear in mind that the “difference” changes, it is not a fixed figure, so it can vary over the length of our sample buffer, presumably due to the separate sources of errors in both ADCs

Here is a closer view of the error
Image

mrburnette Fri May 22, 2015 2:50 pm

I think the difference factor should be weighted over more than 2 conversions, otherwise it will have a disproportional effect on fast transients.
Can anybody think of a better way to do this?

Just my opinion, the “smoothing factor” should be user programmable… that is, a pushbutton held during power-on would run a small routine to capture the user input over Serialx. and this would be written to flash. Then during normal bootup, if the set-up button is not low at startup, the flash is read for configuration.

This way, as you get into soft-settings, the menu can be expanded easily and the procedure can be menu driven numerically.

Ray

ahull Fri May 22, 2015 3:39 pm

@Ray, I like the idea of keeping a seed for the correction factor and other setup parameters in flash. I would need to see if this parameter is consistent over reboots. It may be necessary to do a calibration run at startup instead.

One other thing to note. That irritating glitch in the previous graphs is crosstalk from my test pin. If I switch it on, I see the glitch. Switch it off… gone…. I need to find a better pin for this.

victor_pv Fri May 22, 2015 7:31 pm

We could also use a calibration routine that ask the user to plug the port to 0v, calculate the offset between both, then connect to 3.3, calculate the offset between the two, then use those offsets to readjust the samples.

ahull Fri May 22, 2015 8:17 pm

victor_pv wrote:We could also use a calibration routine that ask the user to plug the port to 0v, calculate the offset between both, then connect to 3.3, calculate the offset between the two, then use those offsets to readjust the samples.

victor_pv Fri May 22, 2015 11:38 pm

I understood from some document we can emulate a peripheral input by setting a pin as output and writing to it. I don’t remember if I read that in the datasheet or where. That’s were I got the idea that we could force one of the USB pins as output low to force USB reenumeration.
The USB peripheral is slightly different than the rest from what I saw in the datasheet, but I think it is worth testing something like that in the ADC.

I’ll give it a shot if I have a moment later, I want to test Ray’s RTC first.

victor_pv Sat May 23, 2015 3:52 am

Andy,

I worked some optimizations in the lcd library. It is still not the best it can, but much faster than before specially when it comes to drawing the captured data.
Download the zip attached and use that. It requires the latest SPI library from Roger’s repo.

(6.41 KiB) Downloaded 125 times

ahull Sun May 24, 2015 9:47 pm

Victor, that does appear to my metric calibrated eye to speed things up quite a bit. Thanks.

BTW I added Real Time Clock functionality to the Scope. See the RTC library page for more details.

Image

If you want to set the clock from a Linux host, the following one-liner should to the trick (assuming your serial port is on /dev/ttyACM3 , it is writeable by your user, and the serial monitor is not already running on it).

NOWTIME="timestamp $(date --date "+1hour1seconds" +"%s")";echo -e "$NOWTIME\r" >/dev/ttyACM3

Sign in to reply