The Arduino AdvancedAnalog library is designed to offer high performance DAC/ADC applications on boards based on the STM32H7 microcontroller.
📖 For more information about this library please read the documentation here.
Advanced Analog Library
License: GNU Lesser General Public License v2.1
The Arduino AdvancedAnalog library is designed to offer high performance DAC/ADC applications on boards based on the STM32H7 microcontroller.
📖 For more information about this library please read the documentation here.
Before releasing the new version (1.4.0):
api.md
with information on the new begin()
and AdvancedADCDual
.docs/readme.md
with an explanation and example for AdvancedADCDual
.These lines of code:
Arduino_AdvancedAnalog/src/HALConfig.cpp
Line 166 in d989bcf
and
(also referenced, here:
Arduino_AdvancedAnalog/src/AdvancedADC.h
Line 39 in d989bcf
limit multi-channel sampling to a sequence of max length 5. The STM32H7 actually supports 16 channels (accordingly #defines up to ADC_REGULAR_RANK_16
exist), and arguably the feature is most useful when more than a handful of pins are involved.
Now, I am not quite sure what is the long term plan for cross-hardware support in this library. Depending on that, the proper fix will either be a plain adjustment of the values, or a small mess of #ifdefs.
While trying to port the Mozzi (Sound Synthesis) library to the Arduino Giga, I notice that DMA driven DAC seems to be quite fragile to set up, in some cases the DAC will simply fail to start.
Testcase: I can trip up the DAC_Sine_wave example by as little as uncommenting the Serial.begin()
line. Now the very same sketch will work some but not all of the time (try resetting a couple of times).
After lots of trial and error, I think I have pinned this down to the double buffered dma setup around here:
Arduino_AdvancedAnalog/src/AdvancedDAC.cpp
Line 129 in 80eeb61
Quite obviously, first starting the DAC, then pausing it to enable double buffering, then restarting it seems like a hack (if justified) in the first place.
I managed to arrive at a robust sketch, by essentially copying the HAL_DAC_Start_DMA implementation from the HAL, but modifying it to set up double buffering right away (settings the correct flags, addresses, and starting DMA with HAL_DMAEx_MultiBufferStart_IT). Of course that makes for a very verbose solution. License-wise, I think it should be ok, as the HAL library appears to be BSD'ed.
Would the above make for an acceptable PR, do you have another idea on attacking the problem, or am I the only one to experience it?
Hello,
Using the Arduino Giga R1 WiFi and the pin DAC0, I tried to generate an analog signal but the maximum Voltage of this signal is ~330 mV. I used the function AdvancedDAC.write() and a sample code provided in this link and set the value as the maximum of the resolution(for example 255 for 8-bit resolution):
link from the arduino tutorial
Is it possible to have an analog output of 3.3V from the Arduino Giga R1 WiFi?
I'm new to Giga R1, so I've been trying out a few simple things. I'm trying to sample live audio via ADC.
I'm using the Arduino_AdvancedAnalog library. I'm seeing that the sample rate is about 4 times slower than I would expect.
I have an external strobe connected to a digital pin so I can measure the rate on a logic analyzer (roughly).
I programmed a single ADC:
adc1.begin(AN_RESOLUTION_16, 32000, 1, 32)
Which I THINK means 16-bit resolution, 32KHz sample rate, 1 sample per channel, 32-deep FIFO.
Then I loop on reading this as fast as I can. Obviously I'm outstripping the FIFO and exposing the underlying sample rate. The adc1.read()
is blocking, waiting for the DMA interrupt most of the time. What I see is that the adc sample is actually only being sampled twice every 250us. Two quick samples, and then 250us waiting, and two more samples...etc. So, kind of like
8KHz sampling. Not sure I understand what's going on here.
I tried different resolutions, different FIFO depths and the number of samples per channel. Nothing seems to budge this off this behavior (except moving the samples per channel to 16...which increases the period to 500us).
If I increase the sample rate to 48000, this seems to increase the sample rate as expected by 33%, but it is still slow by a factor or 4x.
Is this expected? I've pasted my code below. If I'm doing something stupid (always a possibility) I would appreciate being told what
it is. Thanks in advance.
#include <Arduino_AdvancedAnalog.h>
#define OUTPUT_PIN 3
AdvancedADC adc1(A0);
uint64_t last_millis = 0;
uint16_t data_Buffer[1024];
uint16_t data_ptr = 0;
void setup() {
Serial.begin(9600);
// Resolution, sample rate, number of samples per channel, queue depth.
if (!adc1.begin(AN_RESOLUTION_16, 32000, 1, 32)) {
Serial.println("Failed to start analog acquisition!");
while (1)
;
}
pinMode(OUTPUT_PIN, OUTPUT);
delay(2000);
}
void loop() {
data_ptr = 0;
while (data_ptr < 1024) {
digitalWrite(OUTPUT_PIN, 1);
SampleBuffer buf = adc1.read();
digitalWrite(OUTPUT_PIN, 0);
data_Buffer[data_ptr] = buf[0];
buf.release();
data_ptr++;
}
for (uint16_t i = 0; i < 1024; i++) {
Serial.println(data_Buffer[i]);
}
while (true) {
digitalWrite(OUTPUT_PIN, 1);
}
}
I am trying to take an analog signal in, convert to digital using ADC, and then send it out through the DAC. This is on a Arduino Giga R1.
I have the ADC setup with 16b resolution, 96KSamp/sec, and 1024-samples per buffer, and a 8-deep queue of buffers.
On the DAC size I have 12b resolution, 96KSamp/sec, 1024-samples per buffer and a 8-deep queue of buffers.
I know its hanging on the DAC output because I already validated that the ADC is working correctly with no hangs. Also, I added an output pin (D3) which toggles during each ADC/DAC pass. It goes high on the ADC, and low after the DAC. After example N+1 loops, where N is the queue-depth, it will hang on the N+2 loop with the D3 pin high, meaning it is not able to get an available DAC buffer.
I've written a minimal code example that shows the hang. I'm trying to figure out what I'm doing wrong here. This is a pretty basic operation. I think, somehow, the AdvancedDAC.write() operation is not freeing the dma buffer, so it runs out of buffers. I am using A2 for ADC, and A13 (DAC1) for DAC. It could be something obvious I've missed. Any help would be greatly appreciated.
#include <Arduino_AdvancedAnalog.h>
#include <Arduino.h>
AdvancedADC adc_input(A2);
AdvancedDAC dac_output(A13);
#define DBG_OUTPUT_PIN 3 //Digital Debug Output Pin
#define LED_OFF 1
#define LED_ON 0
#define DEBUG 0
const uint32_t SAMPLE_RATE=96000;
const uint32_t BUFFER_SIZE=1024;
const uint32_t SAMPLE_PERIOD=(uint32_t)((1.0/96000.0)*1000000.0);
int16_t resolution16b=4; //4=16,3=14,2=12,1=10,8=0
int16_t resolution12b=2;
int32_t io_buffer[1024];
void setup()
{
Serial.begin(9600);
delay(1000);
pinMode(DBG_OUTPUT_PIN,OUTPUT);
digitalWrite(DBG_OUTPUT_PIN,0);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN,LED_ON);
int32_t result=adc_input.begin(resolution16b, SAMPLE_RATE, BUFFER_SIZE, 8); //16b res, 96KS/s,1024 samples,4-deep queue
result=dac_output.begin(resolution12b, SAMPLE_RATE, BUFFER_SIZE, 8);//12b res, 96KS/s,1024 samples,4-deep queue
}
void loop()
{
if(adc_input.available())
{
digitalWrite(DBG_OUTPUT_PIN,1);
SampleBuffer buf = adc_input.read();
for(uint16_t i=0;i<BUFFER_SIZE;i++)
{
io_buffer[i]=(int32_t)buf[i]>>4;//Reducing precision to 12-bits
}
buf.release();
if (dac_output.available())
{
SampleBuffer bufout = dac_output.dequeue();
for(uint16_t i=0;i<BUFFER_SIZE;i++)
{
bufout[i]=(uint16_t)io_buffer[i];
}
dac_output.write(bufout);
digitalWrite(3,0);
}
}
}
After reading the 3500-page reference manual (nicely written but so much detail...so much...) for the STM32H7xx
I found how to enable the dual-mode.
I modified the begin() and start() routines and added a few new members to the AdvancedADC class to allow using dual-mode.
This gives the optimal synchronization between two sampled inputs. I'm seeing between 0 and 1us skew, which is pretty good for a 1M samp/sec configuration.
It does have some restrictions. You need to always use ADC1 and 2 (dual mode is only supported in HW for ADC 1 and 2). Also, you need to configure the AdvancedADC instance to explicitly selected dual mode. I tried to keep it as general purpose as possible.
I would be happy to share my code if you're interested. I can do a pull_request.
In HALConfig.cpp
, the hal_adc_config()
routine currently has the Sample Time (sConfig.SamplingTime
) hard coded to a value of
ADC_SAMPLETIME_8CYCLES_5
(8.5 clocks).
According to the datasheet, a value of ADC_SAMPLETIME_2CYCLES_5
(2.5 clocks) is legal. This increases the maximum sample rate from 1.8 MS/s to 2.9 MS/s.
Due to concerns about operating conditions (temperature, voltage etc), it would NOT be a good idea to simply change the hard-coded value from 8.5 clocks to 2.5 clocks. Instead, a new defaulted argument can be added to hal_adc_config()
which can be set to SAMPLE_SLOW
or SAMPLE_FAST
to enable switching between 8.5 clocks and 2.5 clocks as the user prefers based on their operating conditions and needs. The default would be SAMPLE_SLOW
to retain backward compatibility.
Looking through the Reference Manual for the STM32H7xx I see there is another Dual mode called Interleaved. It seems likely that by putting an input signal into two Analog Input pins (e.g. A0 and A1) and enabling Dual interleaved mode it will be possible to sample at twice the current maximum rate. So, instead of say 2.9MS/sec we can reach 5.8MS/sec at 16b resolution.
Also, one can also enable a single DMA to handle this dual interleaved data so that all the captured data needs to re-arranging once retrieved from the DMA buffers.
I'm going to try some experiments and see if it works.
First, great job with this library. I wouldn't necessarily call my post an issue, but since you say the ADC is running the "slowest clock", I have to ask.
I'm using your library with an Arduino Portenta H7 lite and reading two ADC channels simultaneously at 100,000 sample pairs/sec, 16-bit resolution, with what appears to be high accuracy. Here's the setup:
adc.begin(AN_RESOLUTION_16, 400000, 4, 128)
That's as fast as I need for sure, but I'm intrigued with the idea of faster based on your "slowest clock" comment. So, how much faster could you make it?
Also, could you shed some light on how the parameter queue depth affects the speed/accuracy? (I found this which answers an earlier question I had about the sample per channel parameter: https://www.st.com/resource/en/application_note/an5354-getting-started-with-the-stm32h7-series-mcu-16bit-adc-stmicroelectronics.pdf).
First of all, thank you for this great library.
Sampling with very high frequencies did not work as expected.
According to the documentation for the STM32H7, sampling rates of > 2 MHz should be possible. I have read that you are working with a slow timer.
In my case I need at least 4 ADC channels with a sampling rate of at least 8bit / 600'000Hz each. The problem is that I should work with ultrasonic receivers that receive at a frequency of 200KHz. In order to analyze the received data correctly, the triple sampling rate is the lower limit.
So far I have managed to sample at 70KHz per channel, distributed over two ADCs with 2 channels each. As soon as I go higher (80KHz) the program no longer starts.
Is this due to this "slow timer" or are my requirements too high?
It's been about 30 years since I wrote anything in C++ or assembler, but with a few hints I might be able to adapt the code myself.
AdvancedADC
gives you two options for capturing two analog signals simultaneously. The first is to bind both analog input pins to the same AdvancedADC
instance. This works great, but sadly, means your sample rate is cut in half. The sample_rate
provided applies to the sampling of both inputs. So a 1M Samp./sec rate really means each is sampled at 500K Samp./sec. If you trying to push the limits of sample rate, this is not a great solution.
The second way is to bind each analog input to a different AdvancedADC
. The library will automatically assign the two pins to two different ADC blocks. The sample rate of each will be maximum. Very nice. However, this creates another problem. The begin()
routine takes approximately 100 us on GIGA R1 to initialize and start the ADC capture. So if I say something like:
adc_input[0].load(res, sample_rate, num_samples, QUEUE_DEPTH,1,&(probes[0]));
adc_input[1].load(res, sample_rate, num_samples, QUEUE_DEPTH,1,&(probes[1])));
What happens is that the second ADC input capture is delayed by about 100us. So the two signals are not synchronized. If you're trying to capture two signals with reasonable synchronization, that is not going to work. I analyzed the timing of each line of code in the begin()
routine and found that two lines are causing almost all the delay:
Arduino_AdvancedAnalog/src/AdvancedADC.cpp
Line 187 in f7d081c
and
Arduino_AdvancedAnalog/src/AdvancedADC.cpp
Lines 200 to 202 in f7d081c
I have a solution. I created two new routines in AdvancedADC.cpp
I call load()
and fire()
.
load()
is identical to the begin()
routine up to a point, including the long-delay lines above. It then returns. Essentially load()
allocates the DMA and ADC etc, but does not start the capture. The fire()
routine (which only takes about 4 us) does the rest of
what was in begin()
and starts the capture. This is working and providing a synchronization error of about 4-6 us. Not bad.
This is probably NOT the solution that someone else would prefer, but it works for me. Now I have full sample-rate and okay synchronization between two or three analog inputs.
I guess I would say the issue I have is that begin()
takes too long (106 us!). Either this needs to be shortened significantly or the idea of the load()
and fire()
would also work.
When I run Arduino_AdvancedAnalog/examples/Advanced/ADC_Multi_Channel/ADC_Multi_Channel.ino in the M4 co-processor of my Portenta H7, it only works for channels A4 to A7. According to the documentation ADC1 should also work with A0 and A1. Why are A0 and A1 not working? They do work in the main M7 core.
I have used these codes:
M7:
void setup() {
bootM4();
Serial.begin(9600);
}
void loop() {
Serial.println("M7");
digitalWrite(LEDG, LOW);
delay(100);
digitalWrite(LEDG, HIGH);
delay(2000);
}
M4:
#include <Arduino_AdvancedAnalog.h>
AdvancedADC adc(A0); // not working with A0 or A1. It does work with A4 to A7
uint64_t last_millis = 0;
void setup() {
Serial1.begin(9600);
// Resolution, sample rate, number of samples per channel, queue depth.
if (!adc.begin(AN_RESOLUTION_16, 16000, 32, 128)) {
Serial1.println("Failed to start analog acquisition!");
while (1);
}
}
void loop() {
if (adc.available()) {
SampleBuffer buf = adc.read();
// Process the buffer.
if ((millis() - last_millis) > 20) {
Serial1.println(buf[0]); // Sample from first channel
Serial1.println(buf[1]); // Sample from second channel
last_millis = millis();
}
// Release the buffer to return it to the pool.
buf.release();
}
}
Spotted an incorrect GitHub repository link in README.md under section ## 🐛 Bugs & Issues (tag: issue tracker)
https://github.com/arduino-libraries/Arduino_Braccio_plusplus/issues
I've been able to reproduce this a couple of times. If I take the example program for generating a sinewave and change the frequency to 48000*lut_size (about 3M samples per second), the program compiles and uploads with no error. About two seconds later the board disconnects from the USB serial port and is no longer recognizable by my PC. I have to double-tap the reset and reload a program in order to get out of this. I see no "SOS" pattern on the LED. Just silence.
What is the maximum samples per second for the DAC outputs? Obviously for audio 2M samples per second is plenty. I was just wondering. If there is a hard limit, it might be good to either put a check in the code for values > 2M or at least something in the documentations saying "Don't do that".
I'm including the failing code below, but its just the sinewave example with the frequency increased to 48000*lut_size.
// This example outputs a 32KHz sine wave on A12/DAC1.
#include <Arduino_AdvancedAnalog.h>
AdvancedDAC dac1(A13);
uint16_t lut[] = {
0x0800,0x08c8,0x098f,0x0a52,0x0b0f,0x0bc5,0x0c71,0x0d12,0x0da7,0x0e2e,0x0ea6,0x0f0d,0x0f63,0x0fa7,0x0fd8,0x0ff5,
0x0fff,0x0ff5,0x0fd8,0x0fa7,0x0f63,0x0f0d,0x0ea6,0x0e2e,0x0da7,0x0d12,0x0c71,0x0bc5,0x0b0f,0x0a52,0x098f,0x08c8,
0x0800,0x0737,0x0670,0x05ad,0x04f0,0x043a,0x038e,0x02ed,0x0258,0x01d1,0x0159,0x00f2,0x009c,0x0058,0x0027,0x000a,
0x0000,0x000a,0x0027,0x0058,0x009c,0x00f2,0x0159,0x01d1,0x0258,0x02ed,0x038e,0x043a,0x04f0,0x05ad,0x0670,0x0737
};
static size_t lut_size = sizeof(lut) / sizeof(lut[0]);
void setup() {
Serial.begin(9600);
if (!dac1.begin(AN_RESOLUTION_12, 48000 * lut_size, 64, 128)) {
Serial.println("Failed to start DAC1 !");
while (1);
}
}
void loop() {
static size_t lut_offs = 0;
if (dac1.available()) {
// Get a free buffer for writing.
SampleBuffer buf = dac1.dequeue();
// Write data to buffer.
for (size_t i=0; i<buf.size(); i++, lut_offs++) {
buf[i] = lut[lut_offs % lut_size];
}
// Write the buffer to DAC.
dac1.write(buf);
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.