My current university project is based around non destructive evaluation using ultrasonics. I’ve been delegated the task of trying to design a suitably cheap (cheap being my speciality 😉 ) low end ultrasonic evaluating system. This involves designing the high voltage supply, the output driver, a high speed amplifier system AND the whole digital domain stuff, sampling and transferring data into MATLAB or another equally useful mathematical evaluation package.
I’d be lying if I said I didn’t know atleast a small nugget of signal processing (OK, I am lying, I do only know a very small nugget haha). I do however feel relatively well versed in the STM32F0 architecture as I base a large variety of my projects around this microcontroller since I have 4 different discovery boards.
This post was generated in response to the requirement for a fast sampling system. For my ultrasonic task, I need to grab a ton of data essentially as fast as possible after the initial impulse. The ultrasonic transducer will be driven at 100kHz. If this was a perfect sinc waveform, I would need a sampling frequency of at least 200kHz (to satisfy Nyquists theorem) instead, since the transducer will be driven by a sharp edged pulse, the harmonics of the 100kHz fundamental will extend high into the upper 100’s of kHzs, along with into the lower MHz region. I mentioned a factor of 10x oversampling to my tutor and this seemed a relatively low SFR (sampling frequency ratio – thank you control!). The reason I mentioned 1MHz for a sampling rate is because secretly I knew that the STM32F0 ADCs can sample at times as low as 1us, 1us = sample rates of up to 1MHz! As ever, I decided to give it a shot and come up with some results.
The ADC is currently set up to use DMA (with a buffer size of 7500 bytes), triggered by TIM15’s update. I’ve deemed a suitable resolution for the ADC to be at least 8bit (256 values) though the STM32F0 ADCs support resolutions of 6bits, 8bits, 10bits and 12bits.
The advantage of using a timer as a trigger allows for predefinable sampling rates, along with the ability to change the sampling rate on the fly if required! As the STM32F051R8 is one of the slower and more simple cores, all the peripherals are clocked at the system clock rate (48MHz), apart from the ADCs which have their own seperate 14MHz clock. The ADCs however can also be clocked by PCLK/2 which in this case is 24MHz.
Now its all well and good having a timer trigger the ADC but if one was none the wiser, they might assume they can trigger the ADC at 24MHz and think they’re getting suitable samples out! This just isn’t a feasible thought as while the timer might be providing the trigger to the ADC, the ADC might still be converting a previous value and will likely queue the trigger until the end of the conversion. I have instead opted for a method of measuring the actual conversion rate of the ADC. To do this, some knowledge of how ADCs work is required. The STM32F0 contains SAR ADCs (successive approximation ADCs). To ensure the result is truly taken at a certain snapshot in time (to negate effects of fast changing signals messing up the conversion), a sample and hold circuit is used to sample the input signal and store that value, ready for conversion. For this, the input is used to charge an internal capacitor.
Obviously, charging an internal capacitor requires current from the input and how else can a changing current be measured? By measuring the voltage across a resistor of which that current is flowing through! To measure the exact sample time, I connect a medium value resistor (1k) to VCC (3V) and measure the voltage drop across the resistor (AC coupled) with my oscilloscope.
Example waveform for measuring the actual ADC conversion rate
As can be seen in the waveform above, the sharp drops indicate the charging of the sampling capacitor.
All of the below shots are with the internal 14MHz ADC clock.
The first test I conducted was trying to achieve the 1us sample time indicated in the datasheet with a 6bit conversion, which is actually the image above!
Trying to run an 8bit conversion at the same rate as the 6bit conversion (1MHz) results in loads of jitter, giving a completely screwy result!
The best I can consistently achieve for an 8bit conversion is ~800kHz. A touch lower than my requirements but still relatively impressive!
800kHz sampling rate with 8bit conversions, the frequency counter on the right is inaccurate compared to the one at the bottom.
One anomaly I have found however is if I let the ADC convert in continuous mode without a trigger (conversion time dependent on the ADC clock, I presume), I can achieve sampling rates of ~1.27MHz at 8bits and ~1.56MHz at 6bits!
8bit conversion with no external trigger
6bit conversion with no external trigger
Changing the ADC clock to PCLK/2 however gives a drastic change in performance!
Running in triggerless mode, for 6bit conversions, I was able to achieve sample rates of ~5.3MHz and 4.3MHz for 8bit conversion! I’m assuming these drastically increased triggerless rates are due to the ADC now being in the same clock domain as the DMA controller meaning less requirements for clock synchronization.
6bit conversions running off PCLK/2
8bit conversions running off PCLK/2
For some reason however, triggered conversions now occur at 2x the trigger frequency (a timer overflowing at 1MHz will generate 2Mhz conversion rates) which I don’t seem to understand as of yet… I can however now achieve 2MHz conversion rates at 8bit! This is actually the ideal limit of a successive approximation ADC running at 24MHz. Each bit requires 1 clock cycle. Since I’m running the ADC channel at the fastest sample rate (1.5 cycles), the best theoretical conversion speed should be: 24M/(8*1.5) = 2MHz.
This is brilliant! Having a 20x oversampling ratio is much better than my predicted 10x and if more resolution is required, I should still be able to achieve a 1MHz sample rate at 12bits according to the datasheet.
8bit samples at a rate of 2MHz!
For my test code, I’ve configured the DMA to transfer the 7500 samples at very high priority meaning during conversion, I doubt the CPU can do much other activity across the internal buses as this leaves 24 CPU cycles between conversions!
My main literally just consists of waiting and clearing the DMA complete flag and incrementing a counter for every 7500 transfers.
Commented code for this ADC analysis can be found on my github!