The idea comes out while using my DSO138, a cheap oscilloscope you can find for $20, based on STM32F103. I was wondering what I can achieve with other Arduino compatible boards and some display I had available at home.
I didn't plan to replace my DSO138, but just have fun. Having a dedicated board can be a waste of resources, as I don't need an oscilloscope regularly, so why don't try to make just a shield, who can be attached to a board whenever I need to debug some signal?
RequirementsOscilloscope: should cover at least the audio frequencies, while reaching speeds over 1Msps can be really welcome. Of course, should have variable sampling speed and sensibility, to better adapt to the signal to probe. Should be able to show parameters about the signals, like frequency, voltage, and so on. Hold function and Zoom are welcome, and we can think about export functionality, like on SD card, as the SD slot is frequently available on TFT screens.
FFT (Fast Fourier Transform): showing the spectrum of the signal sometime can be more interesting than the signal itself. There are several libraries already available on Arduino to transform a signal from the time domain to the frequency domain. As the calculation requires to works with Floating numbers, an FPU is required for this intensive work. I expect from an FFT visualization to show the main frequency of the signals and eventually the minor components as well.
ADC: larger and faster is better, but how much?
Most of onboard ADCs support 10 to 12 bits. As the display's height is usually less than 256 pixels, 8 bits should be enough. One of my display is 160x128, perfect for a 7 bit ADC.
However, as I discovered during my tests with FFT visualization, a better precision is required while working with FFT transformations. The 12 bits of most of the boards are really welcome there.
About the speed, my boards with STM32F113 run the fastest ADC I had available, able to reach 2.4 Msps, not bad. As I was using a generic and portable setup without DMA operations, I was able to reach 1.7Msps. By using the same approach, on Arduino Due without DMA I reached 670Ksps, while using external ADC (see below), I reached 8Msps.
My SetupI did my tests using bread boards and prototype boards. As source signal, to have something calibrated, I made a small signal generator around an AD9833, adding a small display to show the frequency generated and the waveform, all controlled by a ESP32 board.
I tested Arduino Mega, Arduino Due, STM32 Black pill and STM32 Nucleo64 with F411 CPU. Didn't test the ESP32 as I knew in advance his ADC it is not excellent, not linear and not fast. Did not spend too much time designing an input stage, on my tests I sent directly the signal to the ADC input pin. Of course, the final design will have a proper stage there.
Arduino MegaThis was the initial test. I used a parallel display (MCUFriend 2.4") and sampled the data initially by using analogRead(). I implemented a simple oscilloscope with fixed sampling rate, and a spectrum analyser using the FFT library.
It was expected to not be fast, but the frame rate using FFT was really frustrating.
I stopped any further experiment on the Arduino Mega as the CPU is very slow and moved to my faster 32 bits boards.
Arduino DUEAs soon as the new board arrived, I switched the experiments on the Arduino Due board. I used a display with Serial SPI (ili9341). Didn't reuse the parallel display, as the DUE board can't accept 5V on any i/o pin like the display can provide. With 84Mhz, 32 bits and a 1Msps ADC, I managed to have more fun. First, to have good performance on the ADC, I stopped using analogRead() and I used directly the CPU registry. This skips a lot of repetitive setting done by analogRead while running in loop
for (scan = 0; scan < size_array; scan++) {
while ((ADC->ADC_ISR & 0x80) == 0) ; // wait for conversion
raw_sample[scan] = ADC->ADC_CDR[7];
delayMicroseconds(sample_time); // wait some time before next sample
}
This allows to get around 666Ksps. Didn't reach 1Msps as for this speed I must use DMA, maybe on next iteration, but this time getting the full speed was not my goal.
Running a single FFT calculation requires around 70ms, allowing very good frame rates. Sampling a pure sine wave, most of the time show a single vertical line, confirming the good quality of his ADC. To estimate the frequency, it checks the peak on the FFT array, and by knowing the sample rate can calculate the frequency. The error is usually below 2%.
Having 666Ksps, it can detect theoretically 333KHz (Shannon Sampling Theorem), and it worked.
The oscilloscope part show the input in a range 0-3.3V and calculate min and max value, maybe later I can add avg, and other calculation. Added a button for freezing the screen as hold feature, maybe I can use the hold time to calculate additional infos on the signals. On oscilloscope mode, it shows the samples per second speed, not the signal frequency. This is because the FFT it is not calculated to have a higher frame rate.
STM32 NUCLEO-F411REThis board comes with Arduino Uno compatible socket, handy to use the MCUFriend parallel display. His CPU runs at 100Mhz and has Hardware FPU, helpful when you need to perform FFT calculations, and his ADC can sample at 2.4Msps max. By using the same simple generic approach for sampling, I managed to get 1.7Msps, while his FPU allowed to calculate the FFT in 36ms, half of the time required by the Arduino DUE. Also, the speed accessing the screen is higher, allowing to draw a frame in 5ms vs 14ms of the Due board. In general, it's a better solution, however the ADC seem to be more noisy, having the signal not stable between the frames
As I wanted to get higher sample rates, I tried a different approach.
I used the CA3306 chip, a 6 bit fast ADC, able to reach up to 15Msps. I had the version CE, limited to 10Msps. As 6 bits was not enough, I managed to connect two chips to have a 7 bits ADC, as suggested on the datasheet, still not sufficient for my scope but better.
The clock is generated directly by STM32, while at the moment, I skipped any DMA setting and accessed directly all the pins via the registry. I also avoided digitalWrite command as it requires multiple cpu cycle to get executed, preferring direct access to the BSRR registry
for (scan = 0; scan < size_array; scan++) {
GPIOB->BSRR = 0b0000001000000000;
raw_sample[scan] = GPIOB->IDR & 0x007F;
GPIOB->BSRR = 0b0000001000000000 << 16;
}
One additional changes I postponed, it's about accessing the array via index in the loop, it needs a better implementation to optimize for speed.
Using this setup, I reached 8Msps, 20% slower than the CA3306 maximum speed, but still a good result.
Note: on this test, I had a lot of errors:On the ADC side, I didn't put all capacitor required by the Application notes, resulting in a not very precise sampling: You can see some random dots on the sine wave displayed above. On the FFT side, by using only 7 bits instead of 12, I got lower precision on the spectrum visualized in almost all frequencies.
Also, as I'm getting the timing for the sample speed using micros() to calculate the sample frequency, having a very fast sampling, the frequency calculated on the FFT was around 4% lower than the real value, indeed the 4 MHz signal is displayed as 3.84 MHz, as you can see on the photo abobe.
What I do like on this approach: I can reach higher speed, decoupling the Microcontroller from the specific ADC implementation. Eventually, I can have dual channel by using multiple ADC.
Limits:
Assuming I can get a cheap ADC with 12 bits and I can get rid of the noise, this test highlighted the next bottleneck: accessing the internal RAM of the microcontrollers.
If I get a 100Msps ADC, I can't use assume the microcontroller will be able to read the data at that speed.
Next stepsHow can I move forward? If I want higher speed, I need an external static ram.I have some chip EM51256 with 32 KB 8bit who can be used as temporary storage. I can add an external counter like CD74HCT4040 to cycle on all cells in the Ram and I can use an external clock generator, like Si5351A to get the different sample speed. Then the microcontroller then have the job to trigger the sampling, and get the data once sampled, easy (?). On the remaining time, as the microcontroller elaborate the data, the ADC can sample again for the next iteration.
My goal is still make something not costing more than $20, as the chip mentioned costs around $1 each one, I should be able to stay within the budget.
Sprites: on these tests, I didn't use sprites to visualize a waveform on top of a grid, instead I just plotted some dots and remove before next iteration. Although I got a very low flickering, by using sprites I can remove totally, offering a better visualization. Will start translating some of the code with sprites.
ConclusionI hope you find this report insightful. Let me know if you follow a different approach. Now a short video showing the FFT on Arduino DUE
Comments