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

madias Sat May 16, 2015 6:23 am

. We would still need some method of calibration and there may be issues with input impedance affecting the simplistic 1 Meg Ohm resistor as attenuator approach I have adopted… but I’m sure we can fix that in software
Ahull: I believe you have taken a look on the DSO nano schematics? Maybe there a few things which can be interesting for this project…

http://www.seeedstudio.com/document/pdf … ematic.pdf

ahull Sat May 16, 2015 8:52 am

@madias I did look at the DSO Nano, it is designed from the ground up to be an oscilloscope, and uses a couple of op-amps and some other analogue circuitry to buffer the input. The interesting thing is that you can get a pretty usable result without any additional circuitry (except a high value resistor as an attenuator).

With the DSO Nano they face the same bandwidth limitation of the ADC, and as well as buffering the input, they have squared up the input out their probes with a couple of on board trimmer capacitors (these do the job of the trimmer on a standard scope probe).

The minimalist approach I have had to adopt actually works almost as well, but runs a slight risk of zapping the ADC as there is no real input protection except for the 1 Meg Ohm resistor. Having said that, if you do blow the thing up, well the STM32 board is cheap and easily replaceable.

I was surprised how good the results were to be honest, I was expecting it to be pretty useless.

mrburnette Sat May 16, 2015 2:02 pm

. Having said that, if you do blow the thing up, well the STM32 board is cheap and easily replaceable.

YES :D

Now, this is my personal approach to engineering!

Ray

ahull Sat May 16, 2015 4:19 pm

mrburnette wrote:. Having said that, if you do blow the thing up, well the STM32 board is cheap and easily replaceable.

YES :D

Now, this is my personal approach to engineering!

Ray

ahull Sat May 16, 2015 4:24 pm

On a more serious note, does anybody know the easiest way to persuade the STMF103 to toggle a pin at a predefined frequency using the timers. I fudged something together by messing with analogWrite() and ..


uint16 timer_set_period(HardwareTimer timer, uint32 microseconds) {
if (!microseconds) {
timer.setPrescaleFactor(1);
timer.setOverflow(1);
return timer.getOverflow();
}

uint32 cycles = microseconds * (72000000 / 1000000); // 72 cycles per microsecond

uint16 ps = (uint16)((cycles >> 16) + 1);
timer.setPrescaleFactor(ps);
timer.setOverflow((cycles / ps) - 1 );
return timer.getOverflow();
}

ahull Sat May 16, 2015 11:28 pm

I didn’t have much time to play today, as I was doing my day job, but here’s a 540,000 samples per second graph of an approx 50kHz signal. The x mark are the sample points.

Image

Looks like I need to trim the capacitance of my crude 1M Ohm attenuator, a few pF here, a 22pf trimmer there.. I’m sure I can square that waveform back up. :D

gnuplot code..
gnuplot> # Gnuplot script file for plotting data in file "serial3.csv"
gnuplot> # This file is called serial3.gplot
gnuplot> set autoscale # scale axes automatically
gnuplot> unset log # remove any log-scaling
gnuplot> unset label # remove any previous labels
gnuplot> set xtic auto # set xtics automatically
gnuplot> set ytic auto # set ytics automatically
gnuplot> set title "STM32 Oscilloscope plot"
gnuplot> set xlabel "Time (microseconds)"
gnuplot> set ylabel "Signal (mV)"
gnuplot> set linestyle 2 linetype rgb "green"
gnuplot> plot "serial3.dat" using 1:2 title 'Channel 1' with lines linetype 2
gnuplot> plot [1000:1100] "serial3.dat" using 1:2 title 'Channel 1' with linespoints linetype 2

victor_pv Tue May 19, 2015 8:30 pm

Andy I was reading about the ADC peripheral and I think I have DMA sorted out. Pretty much the same as with spi, we only need to write one function to enable and disable the DMA Enable bit. My guess is that leaflabs never got to do it, because that is not libmaple, but the equivalent spi ones were already in libmaple…

Anyway, I will give at shot after work, and see if I can write the modifications to your sketch. Given that the ADC is not too fast, I dont think there will be a big improvement by using DMA in a single channel, but with DMA we can use the interleaved mode with ADC2 and double the bandwidth, and from the datasheet I understand DMA is a requirement to be able to use the interleaved mode:

11.8 DMA request
Since converted regular channels value are stored in a unique data register, it is necessary
to use DMA for conversion of more than one regular channel. This avoids the loss of data
already stored in the ADC_DR register.
Only the end of conversion of a regular channel generates a DMA request, which allows the
transfer of its converted data from the ADC_DR register to the destination location selected
by the user.
Note: Only ADC1 and ADC3 have this DMA capability. ADC2-converted data can be transferred in
dual ADC mode using DMA thanks to master ADC1.

So I will need to add an ISR, change a couple of bits in CR2, and set the DMA controller, pretty similar to what we did with SPI.
Once that works, I will look at enabling the interleaved mode.

ahull Tue May 19, 2015 9:30 pm

On other advantage of DMA obviously is that it should allow us to do multiple ADC conversions without any need for a polling loop. My understanding of how it works (and I could be wrong) is that you can specify how many conversions you want, and the period between them.

So if that is the case we can point the DMA to our ADC array, say give me ‘n’ conversions in that chunk of memory (our array), then examine them later once the hard work is done. This leaves the processor free to do other things, like draw the display, read the touch panel, talk to the serial port or whatever.

I’ll be pretty impressed if we can manage this little trick. BTW I am not wedded to any particular pin for the ADC input, if we need to shift to another pin, because of conflicts with timers or whatever, I’m fine with that.

RogerClark Tue May 19, 2015 10:26 pm

@ahull

The DMA has multiple modes of operation, you can give it a fixed length buffer but you can also operate it in circular buffer mode, though I don’t know if that applies to ADC (probably).

With a scope, it would probably mean you grab the data into a buffer a bit quicker and easier than looping and waiting.

At the moment Victors SPI DMA code is “blocking”, i.e while its transferring e.g to the display, the code sits in a while loop and waits for completion.

However, I’m pretty sure that its possible to get the DMA to trigger an interrupt when its finished, so it would be possible to build a output frame buffer, and then kick off the SPI DMA to write it to the display, and then go back to sampling the ADC on the main loop

ahull Tue May 19, 2015 11:47 pm

RogerClark wrote:@ahull

The DMA has multiple modes of operation, you can give it a fixed length buffer but you can also operate it in circular buffer mode, though I don’t know if that applies to ADC (probably).

With a scope, it would probably mean you grab the data into a buffer a bit quicker and easier than looping and waiting.

At the moment Victors SPI DMA code is “blocking”, i.e while its transferring e.g to the display, the code sits in a while loop and waits for completion.

However, I’m pretty sure that its possible to get the DMA to trigger an interrupt when its finished, so it would be possible to build a output frame buffer, and then kick off the SPI DMA to write it to the display, and then go back to sampling the ADC on the main loop

RogerClark Tue May 19, 2015 11:55 pm

I’m not sure how FreeRTOS integrates with DMA

Its really a job for DMA, as you can transfer from ADC to memory at the same time as transferring a frame buffer to the display, and still have the main loop running your gui control code.

Although you could write this using RTOS it won’t give you the true multitasking that DMA gives you. i.e DMA gives the effect of having a multi core processor – as long as some cores are just transferring data.
RTOS would have to constantly context switch to do the same thing, which would be incredibly inefficient.

victor_pv Wed May 20, 2015 4:04 am

Andy, I had a shot at converting the ADC capture to DMA. I compiled but didn’t have time to test it, so can’t warranty everything is like it should, but I believe it is. These are the changes you need:

First declare this at the beginning:
// End of DMA indication
volatile static bool dma1_ch1_Active;

ahull Wed May 20, 2015 11:41 am

Victor, thanks for that, I should get a chance to test, this evening. I’ll let you know how it goes.

Once we have it working, I suspect the next move is to create an ADC lib similar to the SPI lib in order to make the code modular, and easier to port to the other architectures (F4 etc).

ahull Wed May 20, 2015 1:06 pm

Fails to compile…


STM32-O-Scope.ino: In function 'void takeSamples()':
STM32-O-Scope.ino:385:3: error: 'regs' was not declared in this scope
'regs' was not declared in this scope

victor_pv Wed May 20, 2015 2:37 pm

Andy, perhaps I didn’t explain it clear in my post. Do not replace the whole content of void takeSamples (), only replace comment out the loop that takes the captures to the array, and add the new routine to do that. But you have to keep the first part of the takeSamples function.

regs is declared on the first part of that function, in this line:

adc_reg_map *regs = dev->regs;

victor_pv Wed May 20, 2015 2:48 pm

should end up like this:
void takeSamples ()
{
/*
for (uint16_t j = 0; j <= 10 ; j++ )
{
analogRead(analogInPin);
}
*/
// In effect I have unwrapped analogRead() into its component parts here to speed things up.
// this avoids the need to check the pinmap every time we go round the loop.
const adc_dev *dev = PIN_MAP[analogInPin].adc_device;
int pinMapPB0 = PIN_MAP[analogInPin].adc_channel;
adc_set_sample_rate(dev, ADC_SMPR_1_5);
adc_reg_map *regs = dev->regs;
adc_set_reg_seqlen(dev, 1);
regs->SQR3 = pinMapPB0;
/*
// Discard the first 10 samples to allow the ADC to stabalise.
for (int16_t j = -10; j <-0 ; j++)
{
regs->CR2 |= ADC_CR2_SWSTART;
while (!(regs->SR & ADC_SR_EOC))
;
if (j > 0)
{
regs->DR & ADC_DR_DATA;
}
}
*/
/*
for (int16_t j = 0; j <= maxSamples ; j++ )
{
regs->CR2 |= ADC_CR2_SWSTART;
while (!(regs->SR & ADC_SR_EOC))
;
if (j > 0)
{
dataPoints[j] = (regs->DR & ADC_DR_DATA);
}
// TODO: Tighten up this loop or better still use DMA and/or dual conversion to get up to 2MS/s i.e. 0.5uS per sample and sub-microsecond accuracy.

// sweepDelay adds delay factor with a sub microsecond resolution we would of course be better using an ISR and DMA for the ADC
//
//sweepDelay(sweepDelayFactor);
}
*/
// Loop above replaced with this DMA transfer setup:
regs->CR2 |= ADC_CR2_CONT; // Set continuous mode
dma_init(DMA1);
dma_attach_interrupt(DMA1, DMA_CH1, DMA1_CH1_Event);
adc_dma_enable(ADC1);
dma_setup_transfer(DMA1, DMA_CH1, &ADC1->regs->DR, DMA_SIZE_32BITS,
&dataPoints, DMA_SIZE_16BITS, (DMA_MINC_MODE | DMA_TRNS_CMPLT));// Receive buffer DMA
dma_set_num_transfers(DMA1, DMA_CH3, maxSamples);
dma1_ch1_Active = 1;
dma_enable(DMA1, DMA_CH1); // Enable the channel and start the transfer.
regs->CR2 |= ADC_CR2_SWSTART;
while (dma1_ch1_Active);
dma_disable(DMA1, DMA_CH1); //End of trasfer, disable DMA
regs->CR2 &= ~ADC_CR2_CONT; //and Continuous mode.
}

ahull Wed May 20, 2015 10:07 pm

Victor, serves me right for not reading the instructions.. I was in too much of a hurry to go out and enjoy the sunshine.

I’ll have a play with this tomorrow. Thanks.

victor_pv Thu May 21, 2015 2:00 am

I had a couple of errors in my code. This is working now with this code and the functions and variables I posted before.
This is the capture section to include in your take function:

regs->CR2 |= ADC_CR2_CONT; // | ADC_CR2_DMA; // Set continuous mode and DMA
dma_init(DMA1);
dma_attach_interrupt(DMA1, DMA_CH1, DMA1_CH1_Event);

adc_dma_enable(dev);
dma_setup_transfer(DMA1, DMA_CH1, &regs->DR, DMA_SIZE_32BITS,
&dataPoints, DMA_SIZE_16BITS, (DMA_MINC_MODE | DMA_TRNS_CMPLT));// Receive buffer DMA
dma_set_num_transfers(DMA1, DMA_CH1, maxSamples);
dma1_ch1_Active = 1;
dma_enable(DMA1, DMA_CH1); // Enable the channel and start the transfer.
samplingTime = micros();
regs->CR2 |= ADC_CR2_SWSTART;
while (dma1_ch1_Active);
samplingTime = (micros() - samplingTime);

/* uint32_t t0 = millis();
while (dma1_ch1_Active) {
if ((millis() - t0) > 100) {
dma1_ch3_Active = 0;
break;
}
}
*/
// regs->CR2 &= ~ADC_CR2_DMA;
dma_disable(DMA1, DMA_CH1); //End of trasfer, disable DMA and Continuous mode.
regs->CR2 &= ~ADC_CR2_CONT;

victor_pv Thu May 21, 2015 3:46 am

OK, now it is running in interleaved mode. We could also set a 2 channel 1Mhz oscilloscope in dual ADC mode, with both capturing at the same time but different inputs.

This is what’s needed for interleaved mode:

First change the array declaration so we can access it as a single 32 bit word during capture:
uint32_t dataPoints32[maxSamples/2];
uint16_t *dataPoints = (uint16_t *)&dataPoints32;

ahull Thu May 21, 2015 9:39 am

That is working well.

We have a couple of issues, and some pre-existing bugs, but over all that is a vast improvement.

The triggering certainly needs re-visited, my current method is crude to say the least. It was born out of necessity rather than carefully crafted.
Also the scaling and drawing of the waveform is currently pretty poor. I need to make that slicker. Anything that improves drawing speed to the display would also be a benefit.

I’ll push what we have done so far to git and you can compare with your working copy to make sure I picked up all of your changes. If you want commit access to my git repo, PM me your public key and I will add you.

Sign in to reply