What could be my problem, is it the ADC code, MATLAB code or the functional generator settings

New_bee

Active member
I tried to connect the function generator probe to the ADC, A0 of the teensy4.1 with setting the input frequency to 10kHz. because I am planning to connect an analog accelerometer ADXL1002 to the ADC A0 pin. Even when I twerk the input frequency , I couldn't get a pure sine wave.

I also got a funny output at the Arduino monitor, click on the attached images to see the fluatuations.

I have attached the codes and images

Code:
#include <Arduino.h>
#include <ADC.h> // Include the ADC library

ADC *adc = new ADC(); // adc object

const int adcPin = A0;

void setup() {
  // Initialize serial communication
  Serial.begin(2000000);

  // Set ADC resolution and averaging
  analogReadResolution(12);
  analogReadAveraging(4);

  // Configure ADC settings
  adc->adc0->setAveraging(4); // set number of averages for ADC0
  adc->adc0->setResolution(16); // set bits of resolution for ADC0
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // set conversion speed for ADC0
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // set sampling speed for ADC0
}

void loop() {
  // Get the current timestamp in microseconds
  unsigned long timestamp = micros();
 
  // Read the analog value from the specified pin
  int adcValue = adc->adc0->analogRead(adcPin); // Use adc0 instance to read

  // Print timestamp and ADC value separated by a comma
  Serial.print(timestamp);
  Serial.print(",");
  Serial.println(adcValue);
  delay(1);
}

Code:
% MATLAB Code to Read Data from USB Port and Plot in Real-Time

% Clear any existing serialport connections
availablePorts = serialportlist("available");
for i = 1:length(availablePorts)
    try
        % Try closing each port if open
        portObj = serialport(availablePorts(i), 9600); % Dummy baud rate
        configureCallback(portObj, "off");
        clear portObj;
    catch
        % If port is not accessible or already cleared, ignore
    end
end

% Define the serial port and baud rate
serialPort = '/dev/tty.usbmodem131678501'; % Replace with your actual serial port
baudRate = 2000000;

% Create a serialport object
try
    s = serialport(serialPort, baudRate);
    % Display connection info
    disp(['Connected to ', serialPort]);
catch ME
    disp('Failed to connect to the serial port.');
    disp(ME.message);
    return;
end

% Initialize variables for plotting
h = animatedline('MaximumNumPoints', 5000);
ax = gca;
ax.YGrid = 'on';
ax.XGrid = 'on';
xlabel('Time (s)');
ylabel('ADC Value');
title('Real-Time ADC Data Plot');

% Store the animated line object in appdata
setappdata(gcf, 'PlotLine', h);

% Configure the callback function to read data
configureCallback(s, "terminator", @readSerialData);

% Callback function to read serial data
function readSerialData(src, ~)
    try
        % Retrieve the animated line object
        h = getappdata(gcf, 'PlotLine');
      
        % Read the data from the serial port
        data = readline(src);
      
        % Display raw data for debugging
        disp(['Raw Data: ', data]);

        % Parse the data
        dataArray = split(data, ',');
        if length(dataArray) == 2
            timestamp = str2double(dataArray{1}) / 1e6; % Convert micros to seconds
            adcValue = str2double(dataArray{2});

            % Validate parsed data
            if ~isnan(timestamp) && ~isnan(adcValue)
                % Display parsed data for debugging
                disp(['Parsed Data - Time: ', num2str(timestamp), ', ADC: ', num2str(adcValue)]);
              
                % Add data to the plot
                addpoints(h, timestamp, adcValue);
                drawnow limitrate; % Update the plot with rate limiting
            else
                disp('Error: Parsed data contains NaN values.');
            end
        else
            disp('Error: Data does not have exactly two elements.');
        end
    catch ME
        % Display error message in case of issues
        disp('Error reading serial data:');
        disp(ME.message);
    end
end
 

Attachments

  • Figure output from MATLAB.png
    Figure output from MATLAB.png
    94.9 KB · Views: 21
  • Screenshot 2024-05-14 at 12.12.42.png
    Screenshot 2024-05-14 at 12.12.42.png
    77.4 KB · Views: 22
  • Screenshot 2024-05-14 at 13.15.45.png
    Screenshot 2024-05-14 at 13.15.45.png
    1.7 MB · Views: 17
Last edited:
I can only assume here:
Your function gen is generating a sine wave that goes from +3v to -3v

The teensy ADC cannot read negative voltages, the pin can go as low is -0.5v from what I recall.

You need some conditioning circuit on the input of A0 - an offset voltage of 1.65v and a DC blocking capacitor.
You will then offset your ADC output by half of your ADC resolution (I believe the T4.1 support a max 12bits but only 10bits are usable)
 
I can only assume here:
Your function gen is generating a sine wave that goes from +3v to -3v

The teensy ADC cannot read negative voltages, the pin can go as low is -0.5v from what I recall.

You need some conditioning circuit on the input of A0 - an offset voltage of 1.65v and a DC blocking capacitor.
You will then offset your ADC output by half of your ADC resolution (I believe the T4.1 support a max 12bits but only 10bits are usable)
Thanks for your response.

Using the same code and without the function gen and MATLAB , I got the below output from the Arduino. I can see that the ADC values are just two digits and never goes to zero unlike when the function is connected. Even at that ,I think the ADC values are wrong.
Code:
31455320,21
31456325,16
31457330,18
31458335,22
31459340,21
31460345,19
31461350,19
31462355,31
31463360,37
31464365,31
31465370,20
 
Last edited:
Your generator picture says it all. You have 3Vpp, which is good, but offset is 0V.
Try putting 1.5V-1.65V as an offset directly on your generator.

Angelo
 
Your generator picture says it all. You have 3Vpp, which is good, but offset is 0V.
Try putting 1.5V-1.65V as an offset directly on your generator.

Angelo
Thanks Angelo, following your suggestions I the values below

Code:
5766878,4095
5767893,2838
5768908,614
5769923,1247
5770938,3765
5771953,4095
5772968,4095
5773983,4039
5774998,1835
5776013,480
5777028,2107
5778043,4085
5779058,4095
5780073,4095
5781088,3602
5782103,1057
5783118,717
5784133,3102
5785148,4095
5786163,4095
5787178,4095
5788193,2760

without the function gen I got this below

Code:
13616242,4054
13617258,4054
13618274,4053
13619290,4054
13620306,4056
13621322,4052
13622338,4054
13623354,4047
13624370,4055
13625386,4052
13626402,4051
13627418,4052
13628434,4043
13629450,4052
13630466,4054
 
The timestamps show you are sampling roughly every 1000us which equals to 1ms.
The 10kHz generator signal has a period of 100us.
Nyquist tells you to sample at least twice as fast as the highest to-be-measured signal frequency, so the sample frequency should be at least 20kHz (which equals to 50 us).

Paul
 
The timestamps show you are sampling roughly every 1000us which equals to 1ms.
The 10kHz generator signal has a period of 100us.
Nyquist tells you to sample at least twice as fast as the highest to-be-measured signal frequency, so the sample frequency should be at least 20kHz (which equals to 50 us).

Paul
Many thanks Paul but it seems not working as expected. I just need to see a clean signal or waveform. After I adjusted the FG as suggested , I got the following on serial monitor and using MATLAB to plot the timestamp versus data.

Code:
21541086,4095
21542102,3235
21543118,1206
21544134,4095
21545150,3619
21546166,908
21547182,4069
21548198,3898
21549214,783
21550230,3923
21551246,4058
21552262,877
21553278,3659
21554294,4095
21555310,1152
21556326,3295
21557342,4095
 

Attachments

  • Screenshot 2024-05-14 at 17.53.22.png
    Screenshot 2024-05-14 at 17.53.22.png
    833.2 KB · Views: 8
Your sine wave is 10 kHz but you are sampling at only about 1 kHz. Try reducing the frequency of the sine wave to 100 Hz or even 10 Hz and I think you will see something that looks more like a sine wave.
 
Your sine wave is 10 kHz but you are sampling at only about 1 kHz. Try reducing the frequency of the sine wave to 100 Hz or even 10 Hz and I think you will see something that looks more like a sine wave.
Thanks
Please, what do I do to keep the sine wave at 10kHz and see more like a sine wave at the output.
 
Thanks
Please, what do I do to keep the sine wave at 10kHz and see more like a sine wave at the output.
You would have to sample much faster. In your loop() function, you have delay(1), which is 1 millisecond, so loop() will execute at a little less than 1 kHz. If you replace delay(1) with delayMicroseconds(10), you will get ~100 kHz sampling, but you will also have a lot more data to handle. I suggest you start slow. Reduce your function generator sine wave frequency to 10 Hz and confirm that you can get a good sine wave with 1 kHz sampling. When you have that working, you can increase the sine wave frequency and the sample frequency and see where you begin to find other issues.
 
Last edited:
keep your sampling frequency at least twice (Nyquist theorem) or better 3 to 4 times the frequency of the signal generator.
 
20kHz means 50us. You probably should implement timer triggered aquisition, to ensure software will not insert some jitter.
Reading all the values by the PC should be faster than the acquisition rate.

Angelo
 
20kHz means 50us. You probably should implement timer triggered aquisition, to ensure software will not insert some jitter.
Reading all the values by the PC should be faster than the acquisition rate.

Angelo
I used this code but unfortunately it became worst . I think I am doing something wrong.

Code:
#include <Arduino.h>
#include <ADC.h> // Include the ADC library
#include <IntervalTimer.h> // Include the IntervalTimer library

ADC *adc = new ADC(); // adc object
IntervalTimer timer; // Timer object

const int adcPin = A9;
volatile bool newData = false;
volatile unsigned long timestamp;
volatile int adcValue;

void setup() {
  // Initialize serial communication
  Serial.begin(2000000);

  // Set ADC resolution and averaging
  analogReadResolution(16);
  analogReadAveraging(16);

  // Configure ADC settings
  adc->adc0->setAveraging(16); // set number of averages for ADC0
  adc->adc0->setResolution(16); // set bits of resolution for ADC0
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // set conversion speed for ADC0
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // set sampling speed for ADC0

  // Start the timer to trigger every 1000 microseconds (1 ms)
  timer.begin(timerCallback, 1000); // Adjust the interval as needed
}

void loop() {
  if (newData) {
    // Disable interrupts while accessing shared variables
    noInterrupts();
    unsigned long currentTimestamp = timestamp;
    int currentAdcValue = adcValue;
    newData = false;
    // Re-enable interrupts
    interrupts();

    // Print timestamp and ADC value separated by a comma
    Serial.print(currentTimestamp);
    Serial.print(",");
    Serial.println(currentAdcValue);
  }
}

void timerCallback() {
  // Get the current timestamp in microseconds
  timestamp = micros();

  // Read the analog value from the specified pin
  adcValue = adc->adc0->analogRead(adcPin); // Use adc0 instance to read

  // Indicate that new data is available
  newData = true;
}

Code:
146207003,4049
146208003,4050
146209003,4051
146210003,4051
146211003,4050
146212003,4051
146213003,4051
146214003,4050
146215003,4052
146216003,4051
146217003,4051
146218003,4052
146219003,4052
 
Perhaps to keep the USB transfers as efficient as possible, create two 2D buffers
Each buffer will store timestamps and readings.

You can read as fast as needed using the ADC libraries built in quad timer, then just switch buffers when one is full and send the data off to the PC.
If you build it right you might buy some headroom too (meaning you have time to spare between the transfer and the buffer swap
 
Perhaps to keep the USB transfers as efficient as possible, create two 2D buffers
Each buffer will store timestamps and readings.

You can read as fast as needed using the ADC libraries built in quad timer, then just switch buffers when one is full and send the data off to the PC.
If you build it right you might buy some headroom too (meaning you have time to spare between the transfer and the buffer swap
WITH THIS , CAN THE DATA BE COLLECTED AND PLOT IN REAL-TIME?
 
Perhaps to keep the USB transfers as efficient as possible, create two 2D buffers
Each buffer will store timestamps and readings.

You can read as fast as needed using the ADC libraries built in quad timer, then just switch buffers when one is full and send the data off to the PC.
If you build it right you might buy some headroom too (meaning you have time to spare between the transfer and the buffer swap
I don't know if this is what you suggested

Code:
#include <ADC.h>
#include <AnalogBufferDMA.h>

#ifdef ADC_USE_DMA

// Define the pin for A9
const int readPin_adc_0 = A9;

ADC *adc = new ADC(); // adc object
const uint32_t initial_average_value = 2048;

// Going to try two buffers here using 2 dmaSettings and a DMAChannel
const uint32_t buffer_size = 1600;

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);

void setup() {
    while (!Serial && millis() < 5000) ;

    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(readPin_adc_0, INPUT); // pin for A9

    Serial.begin(9600);
    Serial.println("Setup ADC_0");

    adc->adc0->setAveraging(8); // set number of averages
    adc->adc0->setResolution(12); // set bits of resolution

    // enable DMA and interrupts
    Serial.println("before enableDMA"); Serial.flush();

    // setup a DMA Channel.
    abdma1.init(adc, ADC_0);
    abdma1.userData(initial_average_value); // save away initial starting average

    // Start the dma operation..
    adc->adc0->startContinuous(readPin_adc_0);

    Serial.println("End Setup");
}

void loop() {
    if (abdma1.interrupted()) {
        ProcessAnalogData(&abdma1, 0);
        Serial.println();
    }
}

void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
    uint32_t sum_values = 0;
    uint32_t average_value = pabdma->userData();

    volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
    volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

    if ((uint32_t)pbuffer >= 0x20200000u)  arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
    while (pbuffer < end_pbuffer) {
        sum_values += *pbuffer;
        pbuffer++;
    }

    average_value = sum_values / buffer_size;
    uint32_t currentTimestamp = micros();

    Serial.printf("%u,%u", currentTimestamp, average_value);
    pabdma->clearInterrupt();

    pabdma->userData(average_value);
}

#else // make sure the example can run for any boards (automated testing)
void setup() {}
void loop() {}
#endif // ADC_USE_DMA

I got this when the function generator was set to 1kHz
Code:
271676818,3702
271708221,3692
271739623,3700
271771026,3698
271802429,3693
271833831,3702
271865234,3691
271896637,3700
271928039,3698
271959442,3694
271990845,3702

and this when it was set to 10kHz
Code:
9444162,4039
9475565,4039
9506967,4039
9538370,4039
9569773,4039
9601175,4039
9632578,4039
9663981,4039
 
You have 31402 to 31403 microseconds between each timestamps. It is 31.4 milliseconds.
It means your loop executes at about 31.8Hz. Something wrong somewhere.

Angelo
 
It seems your main loop runs when the buffer of 1600 is full. But the ADC is set to make 8 acquisitions and averages them. And makes a continous acquisition, not through a timer.
So 1600x8= 12800 conversions.
Then you compute the average of the 1600 values and send it.

But I could be wrong, I am not familiar with the ADC and DMA of the Teensy.

Angelo
 
You have 31402 to 31403 microseconds between each timestamps. It is 31.4 milliseconds.
It means your loop executes at about 31.8Hz. Something wrong somewhere.

Angelo
I tried this code and got the output below even when the FG was set to 10kHz, offset 1.65V and Amplitude 3Vpp

Code:
#include <ADC.h>
#include <AnalogBufferDMA.h>

#ifdef ADC_USE_DMA

// Define the pin for A9
const int readPin_adc_0 = A9;

ADC *adc = new ADC(); // ADC object
const uint32_t initial_average_value = 2048;

// Define buffer size and 2D buffers
const uint32_t buffer_size = 1600;
const uint32_t num_buffers = 2;
volatile uint32_t timestamps[num_buffers][buffer_size];
volatile uint16_t readings[num_buffers][buffer_size];
volatile bool buffer_ready[num_buffers] = {false, false};

DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size];
DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size];
AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size);

volatile uint8_t current_buffer = 0;

void setup() {
    while (!Serial && millis() < 5000);

    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(readPin_adc_0, INPUT); // pin for A9

    Serial.begin(9600);
    Serial.println("Setup ADC_0");

    adc->adc0->setAveraging(8); // set number of averages
    adc->adc0->setResolution(12); // set bits of resolution

    // enable DMA and interrupts
    Serial.println("before enableDMA"); Serial.flush();

    // setup a DMA Channel
    abdma1.init(adc, ADC_0);
    abdma1.userData(initial_average_value); // save away initial starting average

    // Start the dma operation
    adc->adc0->startContinuous(readPin_adc_0);

    Serial.println("End Setup");
}

void loop() {
    // Check if the current buffer is ready to be sent
    if (buffer_ready[current_buffer]) {
        sendBufferToPC(current_buffer);
        buffer_ready[current_buffer] = false;
        current_buffer = (current_buffer + 1) % num_buffers; // Switch to the other buffer
    }

    // Check for DMA interruptions and process the data
    if (abdma1.interrupted()) {
        ProcessAnalogData(&abdma1, 0);
    }
}

void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) {
    static uint32_t sample_index = 0;
    volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled();
    volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled();

    if ((uint32_t)pbuffer >= 0x20200000u) arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1));
    while (pbuffer < end_pbuffer) {
        timestamps[current_buffer][sample_index] = micros();
        readings[current_buffer][sample_index] = *pbuffer;

        sample_index++;
        pbuffer++;

        // Check if the current buffer is full
        if (sample_index >= buffer_size) {
            buffer_ready[current_buffer] = true;
            sample_index = 0;
            break;
        }
    }

    pabdma->clearInterrupt();
}

void sendBufferToPC(uint8_t buffer_index) {
    for (uint32_t i = 0; i < buffer_size; i++) {
        Serial.printf("%u,%u\n", timestamps[buffer_index][i], readings[buffer_index][i]);
    }
}

#else // make sure the example can run for any boards (automated testing)
void setup() {}
void loop() {}
#endif // ADC_USE_DMA

Code:
24323183,3968
24323183,3961
24323183,4082
24323183,4095
24323184,4091
24323184,3976
24323184,3953
24323184,4076
24323184,4095
24323184,4093
24323184,3991
24323184,3947
24323184,4067
24323184,4095
24323184,4095
24323184,4004
24323184,3942
24323185,4057
24323185,4095
24323185,4095
24323185,4020
24323185,3938
24323185,4044
24323185,4095
24323185,4095
24323185,4033
24323185,3937
24323185,4033
24323185,4095
24323185,4095
24323185,4047
24323186,3937
24323186,4019
24323186,4095
24323186,4095
 
WITH THIS , CAN THE DATA BE COLLECTED AND PLOT IN REAL-TIME?
Let's be clear: You CAN NOT collect and plot data faster than about 300 samples per second and plot it in real time. Neither the IDE plotting routines nor the human visual system can respond to more than about 30 data frames per second. You can receive and store a few hundred samples and plot those samples thirty times per second. The display will be useless to the observer.

The teensy can collect and store to SD card up to about 1 million samples per second. The human visual system (our highest-bandwidth input source) cannot process signals at that rate. Real-time sine wave plots of signals above a few tens of Hz. must be captured and stored for later analysis. Real-time display is not possible. With some real-time processing (and the T4.X can do a LOT of analysis of high-bandwidth signals in 1/30-th of a second), you can display the average characteristics of an input signal fast enough to bewilder an observer.

Very sophisticated analysis of signals collected by the Teensy ADC is possible. Just be aware, that with 1MSample collection you are going to collect about 2 to 6MB per second (depending on your time stamp interval and resolution) A thorough analysis of 1 hour of data will require you to write a program that can analyze a file of at least 7.2GB of data. Even on a fast PC, Matlab might take a few minutes to sort out that much data!

Rather than concentrate on the speed of data collection and display, you should be deciding what you want to learn from the data and how quickly you need to see the results. If the data is part of a real-time control system, you need to define how quickly the system must respond to anomalies and what should be done to control the system. For this you definitely do not want a human in the loop looking at a plot generated by the Arduino IDE unless your system has a time constant in the tens of seconds! A more appropriate response might be: "ERRROR ERROR, REACTOR OVER TEMPERATURE LIMIT. SYSTEM SCRAMMED TO AVOID MELTDOWN!" accompanied a large, red flashing light.
 
Back
Top