Grand Central Oscilloscope adc help

Please tell us which board you are using.
For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Grand Central Oscilloscope adc help

Post by BrokenCode »

I have a grand central with ili9341 using 8-bit parallel adapted from here https://learn.adafruit.com/adafruit-pyp ... hics-demos.

My goal is to use this setup as a 2 channel oscilloscope with 2 channel signal generator.
I have display working perfectly fine.
The 2 analog input channels should use one ADC each to allow full 1MSPS sampling of 2 channels. (output channels should follow the same paradigm)
ADC/DAC access should be double buffered through DMA.
My problem is I have now idea what is wrong, my math says I'm only getting ~60kSPS. (if my math is wrong please let me know how to calculate it properly)
I've spent the last 18 hours digging all over the internet and have read every post here on Adafruit to no avail.

Code: Select all

/*
 * developed from Adafruit 'Boing' ball demo for PyPortal
 */

#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#define SCREENWIDTH   ILI9341_TFTHEIGHT
#define SCREENHEIGHT  ILI9341_TFTWIDTH

// pins
#define TFT_D0        37  // data bit 0 (MUST be on PORT byte boundary)
#define TFT_WR        4   // write strobe
#define TFT_DC        3   // command/data
#define TFT_CS        2   // chip select
#define TFT_RST       6   // reset
#define TFT_RD        5   // read strobe
#define TFT_BACKLIGHT 7

// ILI9341 with 8-bit parallel interface:
Adafruit_ILI9341 tft(tft8bitbus, TFT_D0, TFT_WR, TFT_DC, TFT_CS, TFT_RST, TFT_RD);

// for FPS counting (sstimate)
uint32_t startTime, frame = 0;

void setup() {
  Serial.begin(115200);
  //while (!Serial);

  // TODO make pwm for dimmable backlight
  pinMode(TFT_BACKLIGHT, OUTPUT);
  digitalWrite(TFT_BACKLIGHT, HIGH);

  tft.begin();
  tft.setRotation(3);

//  tft.drawBitmap(0, 0, (uint8_t *)background, SCREENWIDTH, SCREENHEIGH, GRIDCOLOR, BGCOLOR);
  
  tft.fillScreen(ILI9341_BLACK);


  // setup and start adc/dma
  initADC(A2, ADC0);
  initDMA();
  ADC0->SWTRIG.bit.START = 1;

  
  
  // initial time
  startTime = millis();
}

// adc dma buffers and flags
uint16_t adc0Buff[2][2048];
bool adc0Active = true;   // false = buffer 0, true = buffer 1
bool adc0Valid = false;

volatile uint64_t lastTime = 0;

uint16_t col = 0;
void loop() {

  // other logic here

  // wait for last frame complete
  tft.dmaWait();
  tft.endWrite();

  tft.startWrite();

  if (adc0Valid)
  {
    uint16_t rendBuff[2][240];
    uint8_t rendBuffActive = 0;
    for (uint16_t x=0; x<320; x++)
    { // loop columns
      tft.setAddrWindow(x, 0, 1, 240);
      // calculate where to draw waveform pixel
      // 2^12=4096 (need to map to {0:239})(each pixel corresponds to 68.266... adc values)
      uint16_t value = map(adc0Buff[adc0Active][x], 0, 4096, 0, 239);
//      Serial.print(adc0Buff[adc0Active][x]);
//      Serial.print(" : ");
//      Serial.println(value);
      
      for (uint16_t y=0; y<240; y++)
      { // loop rows
        if (y == value) {
          // draw waveform pixel
          rendBuff[rendBuffActive][y] = ILI9341_YELLOW;
        } else {
          // draw background
          rendBuff[rendBuffActive][y] = ILI9341_BLACK;
        }
      }
      tft.dmaWait();
      tft.writePixels(&rendBuff[rendBuffActive][0], 240, false);
      rendBuffActive = 1 - rendBuffActive;
    }
    adc0Valid = false;
    
    uint64_t thisTime = micros();
    Serial.println((uint32_t)(thisTime-lastTime)/2048); // per-sample time (prints 24)
    lastTime=thisTime;
  }
//  else Serial.println("invalid buffer");
  
  // FPS estimate
//  if ( !(++frame & 255)) {  // Every 256 frames...
//    uint32_t elapsed = (millis() - startTime) / 1000; // Seconds
//    if (elapsed) {
//      Serial.print(frame / elapsed);
//      Serial.println(" fps");
//    }
//  }
}

#include <wiring_private.h>

void initADC(int pin, Adc *ADCx)
{
  
  ADCx->CTRLA.bit.ENABLE = 0x00;
  while (ADCx->SYNCBUSY.bit.ENABLE);
  
  pinPeripheral(pin, PIO_ANALOG);

  ADCx->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND;
  while (ADCx->SYNCBUSY.bit.INPUTCTRL);

  ADCx->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber;
  while (ADCx->SYNCBUSY.bit.INPUTCTRL);

  ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV4_Val;
//  ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV8_Val;
//  ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV2_Val;
  while (ADCx->SYNCBUSY.bit.CTRLB);

  ADCx->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
  while (ADCx->SYNCBUSY.bit.CTRLB);

  ADCx->SAMPCTRL.reg = 0x0;
  while (ADCx->SYNCBUSY.bit.SAMPCTRL);

  ADCx->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | ADC_AVGCTRL_ADJRES(0x0ul);
  while (ADCx->SYNCBUSY.bit.AVGCTRL);

  ADCx->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_AREFA_Val;
  while (ADCx->SYNCBUSY.bit.REFCTRL);

  ADCx->CTRLB.bit.FREERUN = 1;
  while (ADCx->SYNCBUSY.bit.CTRLB);

  ADCx->CTRLA.bit.ENABLE = 0x01;
  while (ADCx->SYNCBUSY.bit.ENABLE);
  
}

void adc0Complete(Adafruit_ZeroDMA *dma)
{
  // flag which buffer is valid
  adc0Active = !adc0Active;
  adc0Valid = true;
}

Adafruit_ZeroDMA adc0DMA;
void initDMA()
{
  adc0DMA.allocate();
  auto desc = adc0DMA.addDescriptor((void*)(&ADC0->RESULT.reg), adc0Buff[0], 2048, DMA_BEAT_SIZE_HWORD, false, true);
  desc->BANNED.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
  desc = adc0DMA.addDescriptor((void*)(&ADC0->RESULT.reg), adc0Buff[1], 2048, DMA_BEAT_SIZE_HWORD, false, true);
  desc->BANNED.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;

  adc0DMA.loop(true);
  adc0DMA.setTrigger(0x44);
  adc0DMA.setAction(DMA_TRIGGER_ACTON_BEAT);
  adc0DMA.setCallback(adc0Complete);

  adc0DMA.startJob();

  
}
I'm not sure how to decouple LCD code intuitively because serial wont accept as much data.
I know a lot of my buffer is not being rendered.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Grand Central Oscilloscope adc help

Post by User_UMjT7KxnxP8YN8 »

Hmmmm. You're capturing 4096 samples that you expect to occur at 1 MSPS, so they should complete in ~4ms (or ~2ms if they run in parallel, but I think I recall reading in the Datasheet that concurrent DMAs run serially based on priority. You may want to look at that).

Anyway, I recommend using micros() instead of millis() to measure elapsed time because the slop in a millis() measurement will substantially skew measurement of such a short time period.

That may not produce your expected result but it's going to be more accurate than using millis().

User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Re: Grand Central Oscilloscope adc help

Post by BrokenCode »

User_UMjT7KxnxP8YN8 wrote:Hmmmm. You're capturing 4096 samples that you expect to occur at 1 MSPS, so they should complete in ~4ms (or ~2ms if they run in parallel, but I think I recall reading in the Datasheet that concurrent DMAs run serially based on priority. You may want to look at that).
I'll look into this. Do you have or know of any resources that would help (I'm hoping to have 4 DMA operations playing nice and all four with 1MSPS beat speed).

User_UMjT7KxnxP8YN8 wrote:Anyway, I recommend using micros() instead of millis() to measure elapsed time because the slop in a millis() measurement will substantially skew measurement of such a short time period.

That may not produce your expected result but it's going to be more accurate than using millis().
I am using micros in the ADC time measurement as you can see in these lines.

Code: Select all

uint64_t thisTime = micros();
    Serial.println((uint32_t)(thisTime-lastTime)/2048); // per-sample time (prints 24)
    lastTime=thisTime;
The millis() is comes from the Adafruit code for the FPS measurement; I haven't changed the 1000 to 1000000 etc.

Code: Select all

  // FPS estimate
//  if ( !(++frame & 255)) {  // Every 256 frames...
//    uint32_t elapsed = (millis() - startTime) / 1000; // Seconds
//    if (elapsed) {
//      Serial.print(frame / elapsed);
//      Serial.println(" fps");
//    }
//  }
I wrote another sketch breaking out only the analog related code to experiment with; I ignore the actual values of the data and simply measure the time with a kind of tight loop (only kinda tight because I let loop return and between the end of loop and the next iteration of loop the Arduino core executes background USB task which give me a slight variation in timing).

Code: Select all

#include <Adafruit_ZeroDMA.h>

#include <wiring_private.h>

uint16_t adc0Buff[2][2048];
bool adc0Active = true;   // false = buffer 0, true = buffer 1
bool adc0Valid = false;

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println("starting");

  // setup and start adc/dma
  initADC(A2, ADC0);
  initDMA();
  ADC0->SWTRIG.bit.START = 1;
}

uint32_t loops = 0;
uint32_t startTime = 0;
void loop() {
  if (adc0Valid)
  {
    uint32_t currentTime = micros();
    Serial.print(currentTime - startTime);
    Serial.print(" : ");
    Serial.println((currentTime - startTime) / 2048);
    startTime = micros();
    adc0Valid = false;
  }

}

void initADC(int pin, Adc *ADCx)
{
  
  ADCx->CTRLA.bit.ENABLE = 0x00;
  while (ADCx->SYNCBUSY.bit.ENABLE);
  
  pinPeripheral(pin, PIO_ANALOG);

  ADCx->INPUTCTRL.reg = ADC_INPUTCTRL_MUXNEG_GND;
  while (ADCx->SYNCBUSY.bit.INPUTCTRL);

  ADCx->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber;
  while (ADCx->SYNCBUSY.bit.INPUTCTRL);

//  ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV4_Val;
//  ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV16_Val;
  ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV8_Val;
//  ADCx->CTRLA.bit.PRESCALER = ADC_CTRLA_PRESCALER_DIV2_Val;
  while (ADCx->SYNCBUSY.bit.CTRLB);

  ADCx->CTRLB.bit.RESSEL = ADC_CTRLB_RESSEL_12BIT_Val;
  while (ADCx->SYNCBUSY.bit.CTRLB);

  ADCx->SAMPCTRL.reg = 0x0;
  while (ADCx->SYNCBUSY.bit.SAMPCTRL);

  ADCx->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | ADC_AVGCTRL_ADJRES(0x0ul);
  while (ADCx->SYNCBUSY.bit.AVGCTRL);

  ADCx->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_AREFA_Val;
  while (ADCx->SYNCBUSY.bit.REFCTRL);

  ADCx->CTRLB.bit.FREERUN = 1;
  while (ADCx->SYNCBUSY.bit.CTRLB);

  ADCx->CTRLA.bit.ENABLE = 0x01;
  while (ADCx->SYNCBUSY.bit.ENABLE);
  
}

void adc0Complete(Adafruit_ZeroDMA *dma)
{
  // flag which buffer is valid
  adc0Active = !adc0Active;
  adc0Valid = true;
}

Adafruit_ZeroDMA adc0DMA;
void initDMA()
{
  adc0DMA.allocate();
  
  auto desc = adc0DMA.addDescriptor((void*)(&ADC0->RESULT.reg), adc0Buff[0], 2048, DMA_BEAT_SIZE_HWORD, false, true);
  desc->BANNED.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;
  
  desc = adc0DMA.addDescriptor((void*)(&ADC0->RESULT.reg), adc0Buff[1], 2048, DMA_BEAT_SIZE_HWORD, false, true);
  desc->BANNED.bit.BLOCKACT = DMA_BLOCK_ACTION_INT;

  adc0DMA.loop(true);
  adc0DMA.setTrigger(0x44);
  adc0DMA.setAction(DMA_TRIGGER_ACTON_BEAT);
  adc0DMA.setCallback(adc0Complete);

  adc0DMA.startJob();
}
This code give 0-1 which calculates out to ~1MSPS as I expect. I suspect my LCD rendering is taking longer than I have causing my timing problem (maybe need to do more than 2048 samples, in the back of my mind I'm worrying about using larger buffer because trigger detection have more samples to process).
I believe my next steps here would be expanding to use both ADC with DMA, then expand to include DACs with DMA (hopefully I can sequence where all 4 DMA operations work concurrently and fluently).

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Grand Central Oscilloscope adc help

Post by User_UMjT7KxnxP8YN8 »

I recommend the Microchip datasheet, available at https://www.microchip.com/content/dam/m ... 01507G.pdf as your primary reference for how the hardware works. See Chapter 45 for A/D convertor details.

The datasheet starts with an overview of ADC subsystem features. Each of the sections that follows provides more detail, with the final sections documenting register use.

Good news on the simultaneous A/D front:
The SAM D5x/E5x has two ADC instances, ADC0 and ADC1. The two inputs can be sampled simultaneously, as
each ADC includes sample and hold circuits.
UNLESS you're using the peripheral touch interface with your ILI9341, in which case this may apply:
Note:  When the Peripheral Touch Controller (PTC) is enabled, ADC0 is serving the PTC exclusively. In this case, ADC0 cannot be used by the user application.
Depending on how complex your triggers will be, the ADC peripheral may be able to offload some of the word.
The ADC has a compare function for accurate monitoring of user-defined thresholds, with minimum software intervention required.
If you have complex triggers, you may want to configure the ADC to generate an interrupt after every sample so you can see if your trigger condition has occurred so you can look at it as soon as possible. You could DMA your pre-capture data to a small circular buffer while waiting for your trigger, then kick off a second, higher-priority DMA of trace A/D data upon detecting the trigger.

Use of the dual DACs is covered in chapter 47. If your signals are periodic, you should be able to start repetitive DMAs that goes through your descriptor(s) to output a single period of each so that they are interleaved as needed in time before looping back to the beginning to output the next period of each without software intervention. It's been awhile since I read that section but think you can automate those cycles.

As I recall there are errata disclosed for DMA controller operation, so it won't work exactly as described in the datasheet. I've attached the latest errata document version I have for reference. You have 32 "independent" channels to work with but since they use some of the same resources I don't think you can run all of them at full speed; instead, channels with queued-up requests take turns controlling the bus. Since DMA is usually implemented using "cycle-stealing" from the processor this will affect your display update rate.

Are your signal generator outputs periodic, or must you calculate them on the fly? If periodic, do they both have the same frequency?
What kinds of triggers do you plan to support? Simple level transition on one or the other input? Or a more complex trigger composed of a series of changes on both inputs?

Sounds like an interesting problem, and I'll be happy to help if I can.

User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Re: Grand Central Oscilloscope adc help

Post by BrokenCode »

User_UMjT7KxnxP8YN8 wrote:I recommend the Microchip datasheet
I already have the datasheet and latest errata I could find on Microchip's website, and I have already been relying heavily upon them; a bit complicated to understand, but repetitive reading and googling helps alot.
User_UMjT7KxnxP8YN8 wrote: UNLESS you're using the peripheral touch interface with your ILI9341, ...
I won't be needing the PTC as I purchased the version with the FT6206 capacitive touch controller.
User_UMjT7KxnxP8YN8 wrote:Depending on how complex your triggers will be, the ADC peripheral may be able to offload some of the word.
This should work well for earlier revisions (maybe later on, time and project success will tell this).
User_UMjT7KxnxP8YN8 wrote:If you have complex triggers, you may want to configure the ADC to generate an interrupt after every sample so you can see if your trigger condition has occurred so you can look at it as soon as possible. You could DMA your pre-capture data to a small circular buffer while waiting for your trigger, then kick off a second, higher-priority DMA of trace A/D data upon detecting the trigger.
I like the sound of these methods for more complex triggering. My intent is to develop this project over time expanding to as full featured Oscilloscope as posable, hopefully I end up pushing the SAMD51 to the limits and optimize things to get as much more as I can.
User_UMjT7KxnxP8YN8 wrote:Use of the dual DACs is covered in chapter 47. If your signals are periodic, you should be able to start repetitive DMAs that goes through your descriptor(s) to output a single period of each so that they are interleaved as needed in time before looping back to the beginning to output the next period of each without software intervention. It's been awhile since I read that section but think you can automate those cycles.
My impressions from the data sheet are quite similar. My end goals are to also get as full featured waveform generation as posable as well. (Arbitrary Waveform Generator anyone :D)
User_UMjT7KxnxP8YN8 wrote:As I recall there are errata disclosed for DMA controller operation, so it won't work exactly as described in the datasheet.
I'm not sure if the issues described in the errata will affect this project or not, experimentation may me nessicary.
User_UMjT7KxnxP8YN8 wrote:Since DMA is usually implemented using "cycle-stealing" from the processor this will affect your display update rate.
Currently I'm seeing ~50kHz frame rate, but definitely something to keep in mind.
User_UMjT7KxnxP8YN8 wrote:Are your signal generator outputs periodic, or must you calculate them on the fly? If periodic, do they both have the same frequency?
Ideally these would be able to be presets, user defined and editable. I was planning on just sending samples from a buffer, either presets, user defined or captured with the ADC.
User_UMjT7KxnxP8YN8 wrote:What kinds of triggers do you plan to support? Simple level transition on one or the other input? Or a more complex trigger composed of a series of changes on both inputs?
Initially triggers would be simple single channel, however as the project matures more complex triggers would be awesome. I hadn't considered multi-channel triggering; I like the idea, and I'm adding that to the list for down the road development. :D
User_UMjT7KxnxP8YN8 wrote:Sounds like an interesting problem, and I'll be happy to help if I can.
I really do appreciate any help and will at the very least give credit where its due; more as appropriate. I have big dreams here, but if this project comes to full fruition it will be the most advanced MCU based Oscilloscope/Signal Generator posable.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Grand Central Oscilloscope adc help

Post by User_UMjT7KxnxP8YN8 »

Ideally these would be able to be presets, user defined and editable. I was planning on just sending samples from a buffer, either presets, user defined or captured with the ADC.
The ATSAMD51's Achilles' Heel is its RAM. You get a total of 192K bytes which is used for your variables, the stack and the heap. Let's assume your max stack size is 1 KB (perhaps achievable if you're careful), your heap is 1 KB (don't use malloc()!) and you keep your non-trace, non-waveform variables around 4 KB. That leaves you 186 KB of RAM.

If you divide that evenly among your two traces and two user-editable waveforms, that gives you 46.5 KB for each. At 1 MSPS and 10-bit samples, that gives you about 23 milliseconds of trace data. If you limit users to using canned periodic waveforms that you can force into program flash, you can use 93 KB for each trace, which gives you about 45 ms of data. You can still do interesting waveforms (sinusoids, square waves, sawtooths, pulse trains, etc) and change the frequency by diddling the DMA speed.

To get around the RAM limitations, you can look at the Grand Central's SD card datalogging capabilities. The latest version of Greiner's SDfat library supports pre-allocated files on the SD card, and dedicating your SPI bus to the SD card will increase throughput. I'll leave it to you to figure out whether it can be fast enough to keep up. If not, you can reduce the sample rate until it is.


You may want to stash this code away somewhere for future reference. It will tell you how your memory is being used as your program runs:

Code: Select all

#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__

#define DC_HeapListCount 20
void RamUsage(int &p_data_bss_size, int &p_heap_size, int &p_free_ram, int &p_stack_size, int &p_heap_free, int p_heap_list_sizes[]) {
   //Total memory is (int)&__stack - (int)&__data_start + 1;
  //From bottom (low memory) to top (high memory) RAM divides as follows:
  //DATA + BSS + H EA P>> (if any) + FREE + <<STACK
  //"H EA P" indicates a heap with embedded free space.
  //The HEAP moves up into FREE space and the STACK moves down into FREE space.
  //If the two collide you are out of FREE ram and your program goes crazy.

  extern int __stack, __bss_start__, __bss_end__, __end__, __malloc_sbrk_start, __StackTop, __StackLimit, __ram_end__, __HeapLimit;
  int l_heap_end;

  p_data_bss_size = (int)&__bss_end__ - (int)&__bss_start__;

  l_heap_end = (int)sbrk(0);

  p_heap_size  = l_heap_end - (int)&__bss_end__;
  p_free_ram   = (int)&l_heap_end - l_heap_end;
  p_stack_size = (int)&__stack - (int)&l_heap_end + 1;

}   // RamUsage

void PrintMemoryStats(void) {

  //Extract current memory use statistics and print
  //them to the Serial monitor on a single line.
  int l_data_bss_size;
  int l_heap_size;
  int l_free_ram;
  int l_stack_size;
  int l_heap_free;
  int l_heap_list_sizes[DC_HeapListCount];

  //The source code for this is in the following post
  RamUsage(l_data_bss_size, l_heap_size, l_free_ram, l_stack_size, l_heap_free, l_heap_list_sizes);

  Serial.print("Data/BSS: ");
  Serial.print(l_data_bss_size);
  Serial.print(", Heap size: ");
  Serial.print(l_heap_size);
  Serial.print(", Free RAM: ");
  Serial.print(l_free_ram);
  Serial.print(", Stack size: ");
  Serial.print(l_stack_size);
  Serial.print(", Total RAM: ");
  Serial.print(l_data_bss_size + l_heap_size + l_free_ram + l_stack_size);
  // Serial.print(", FREE HEAP ");
  // Serial.print(l_heap_free);
  /*
     Serial.print(", FREE LIST ");
    for (int l_index = 0; l_index < DC_HeapListCount; l_index++) {
    if (l_heap_list_sizes[l_index] == 0) {
      if (l_index == 0)
         Serial.print("<<NULL>>");
      //
      break;
    }
    if (l_index != 0)
       Serial.print(", ");
    //
     Serial.print(l_heap_list_sizes[l_index]);
    }*/
  Serial.println();

}   // PrintMemoryStats


User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Re: Grand Central Oscilloscope adc help

Post by BrokenCode »

User_UMjT7KxnxP8YN8 wrote:The ATSAMD51's Achilles' Heel is its RAM. You get a total of 192K bytes which is used for your variables, the stack and the heap. Let's assume your max stack size is 1 KB (perhaps achievable if you're careful), your heap is 1 KB (don't use malloc()!) and you keep your non-trace, non-waveform variables around 4 KB. That leaves you 186 KB of RAM.
I am using the Grand Central partly for that reason, and partly for the huge amount of GPIO. The version of the samd51 on the Grand Central has 1MB flash and 256kB ram (only 60kB I know) (that gives 32.5kB per trace). This may still be too much of a limit.
User_UMjT7KxnxP8YN8 wrote:To get around the RAM limitations, you can look at the Grand Central's SD card datalogging capabilities. The latest version of Greiner's SDfat library supports pre-allocated files on the SD card, and dedicating your SPI bus to the SD card will increase throughput. I'll leave it to you to figure out whether it can be fast enough to keep up. If not, you can reduce the sample rate until it is.
I was hoping to use the SD slot for more long term storage of waveforms or whatever I may find I need, however I might have just the solution to the RAM limitation.
I happen to have a couple 2 MB QSPI SRAM chips with the exact same pinout and package as the onboard flash chip of the Grand Central. Looking at the SAMD51 datasheet I am hopeful that I can solder the SRAM chip in place of the flash chip already there (I'm not afraid of SMT soldering at all). If my thoughts are correct I can configure the QSPI module in serial memory mode and just access it directory via memory map (hopefully using DMA???, I'm iffy on this). If that doesn't work I should be able to just write some sort of brute force approach using the QSPI module.
Please let me know what you think of the feasibility of this idea.
Datasheet -> (37.6.8 QSPI Serial Memory Mode in the data, page 971)
SPRAM I have -> (https://www.adafruit.com/product/4677)
User_UMjT7KxnxP8YN8 wrote:You may want to stash this code away somewhere for future reference. It will tell you how your memory is being used as your program runs;
Thanks a ton, this will most defiantly be extremely useful!!!!

An aside here: I just want to thank you for all the help you have provided me. Most of my experience with other programmers, more specifically asking for help, is a resounding negative. I can be a bit slow with things at times, and you have been extremely helpful.

Aside 2: A shout out to the community here at Adafruit; it is an extraordinarily nice one compared to most places on the web.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Grand Central Oscilloscope adc help

Post by User_UMjT7KxnxP8YN8 »

The version of the samd51 on the Grand Central has 1MB flash and 256kB ram
That's good to know. I've developed some version interesting code to conserve RAM and have managed to live with 192 KB so far, but I'll keep that in mind for future reference.
If my thoughts are correct I can configure the QSPI module in serial memory mode and just access it directory via memory map
You'll need to add the chip's characteristics to one of the files in the QSPI library. It's table-driven based on a chip ID. I don't see any reason it won't work.
access it directory via memory map
Definitely doable, provided you put the processor in the right mode; I'm using the QSPI Flash in memory mapped mode and it works great. You can even run code stored in it. Ping me when you get there and I'll share my code. I don't know if it will be fast enough to keep up with 2x 1 MSPS streams though. I'd test that early before investing lots of effort.
Most of my experience with other programmers, more specifically asking for help, is a resounding negative
Yeah, I've seen a bit of that too. I don't understand why that is; I've been programming for nearly 50 years and owe much of what I know to the help of others. I'm happy to help motivated people pursue their interests, and wish everyone would. In my experience, the Adafruit folks are helpful, some very much so.

Maybe you can give me tips on surface-mount soldering ;)

User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Re: Grand Central Oscilloscope adc help

Post by BrokenCode »

User_UMjT7KxnxP8YN8 wrote:You'll need to add the chip's characteristics to one of the files in the QSPI library. It's table-driven based on a chip ID. I don't see any reason it won't work.
Great info.
User_UMjT7KxnxP8YN8 wrote:Definitely doable, provided you put the processor in the right mode; I'm using the QSPI Flash in memory mapped mode and it works great. You can even run code stored in it. Ping me when you get there and I'll share my code. I don't know if it will be fast enough to keep up with 2x 1 MSPS streams though. I'd test that early before investing lots of effort.
Good to know that I read the datasheet right about how it maps to the memory map.

Correct me if these calculations are wrong here.
The SRAM is suppose to be 133MHZ so ~66.5MBPS meaning the chip should be able to keep up.
I'm a little iffy on my understanding of the baud rate registers, but I believe the baud rate is derived from MCLK; the same as the core clock. I'm thinking that means it the SAMD51 may be able to keep up as well (experimentation is the answer to all problems!!!). I hope I'm not missing some quirk or nuance here.
User_UMjT7KxnxP8YN8 wrote:Yeah, I've seen a bit of that too. I don't understand why that is; I've been programming for nearly 50 years and owe much of what I know to the help of others. I'm happy to help motivated people pursue their interests, and wish everyone would. In my experience, the Adafruit folks are helpful, some very much so.
I used to aggravate all the other kids at school because I was always looking for a reason the share what I know, lol. I'm about to turn 28 in a couple months and I'd like to think I've learned to tone it back at least a little, lol. Maybe I'm just around more mature people that overlook my annoying "qwirks", lol.
User_UMjT7KxnxP8YN8 wrote:Maybe you can give me tips on surface-mount soldering ;)
SMT soldering is more of an art than anything. I used to have all kinds of trouble with it. I have a jewelers iron that works off butane and have to be careful not to hold it still too long; it has an end that is a hot air blower. A proper hot air station would be the best bet as you can control the temp. way better. Lots of liquid flux and desoldering braid are your best friends. I avoid BGA as I don't have a soldering oven or even a soldering plate. It is way more important to have clean joints when working with SMT parts. Don't be afraid of using too much flux, >90% isopropyl cleans excess off really well (I dip a toothbrush in alcohol and scrub the area before work, and after, then after cleaning with toothbrush during after work cleanup a bit of pouring of the alcohol helps with the sticky residue left). Sorry about the run-on sentence here. wasn't sure how to break it up. I'd be happy to help anyway I can, one of my favorite past times is sharing every spec of information contained in my two brain cells.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Grand Central Oscilloscope adc help

Post by User_UMjT7KxnxP8YN8 »

The SRAM is suppose to be 133MHZ so ~66.5MBPS meaning the chip should be able to keep up.
It may be more complicated than that. Check your SRAM datasheet to see what the max transfer rate is. Also check the processor datasheet to see what else may be on the bus the data is transferred on.

Keep in mind there will be a bit of latency when you start capturing trace data after triggering. The ATSAMD51 has an "Event" subsystem that you should look at. You can set up the DMAs in advance and have them wait for an event, which can be generated by other peripherals as well as by software. Using an event may minimize latency, but your traces won't include your oscilloscope trigger unless you keep everything up until the DMAs start. Maybe you can capture a bit of post-trigger data using the same technique used to acquire pre-trigger samples then match them up with data from your external SRAM to create a gapless record? Maybe there's a way to capture post-trigger data until you see the DMA begin to transfer data. Check the DMA controller status registers.

I've read the ATSAMD51 datasheet chapter on DMAs but never got around to implementing it (I'm a big fan of DMAs - who doesn't love freeing up CPU clock cycles?). IIRC, you can use an event from the A/D convertor (e.g., sample ready) to trigger a DMA without processor intervention.

User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Re: Grand Central Oscilloscope adc help

Post by BrokenCode »

User_UMjT7KxnxP8YN8 wrote:
The SRAM is suppose to be 133MHZ so ~66.5MBPS meaning the chip should be able to keep up.
It may be more complicated than that. Check your SRAM datasheet to see what the max transfer rate is. Also check the processor datasheet to see what else may be on the bus the data is transferred on.
I will be doing some experimenting as well as reading on both data sheets as suggested. I'm at the point where I will need to figure this out before I run into issues needing more ram.
User_UMjT7KxnxP8YN8 wrote:Keep in mind there will be a bit of latency when you start capturing trace data after triggering. The ATSAMD51 has an "Event" subsystem that you should look at. You can set up the DMAs in advance and have them wait for an event, which can be generated by other peripherals as well as by software. Using an event may minimize latency, but your traces won't include your oscilloscope trigger unless you keep everything up until the DMAs start. Maybe you can capture a bit of post-trigger data using the same technique used to acquire pre-trigger samples then match them up with data from your external SRAM to create a gapless record? Maybe there's a way to capture post-trigger data until you see the DMA begin to transfer data. Check the DMA controller status registers.
I've been triggering DMA using the ADC conversion complete event, however upon implementing the DMA to the DACs (which seems to not have any issue with all 4 DMA interfering with each other) I started getting weird choppy waveforms being red back through the ADC. These waveforms are correct as confirmed with a real oscilloscope; well mostly correct, between the last sample and the first there seems to be a one sample latency causing my waveform to have a jump to zero volts between cycles(I think configuring DAC refresh and making sure the relevant errata is considered). After attempting to implement a basic level based software trigger I got some extremely pretty colorful effects. I eventually narrowed it down to the fact that I've been just rendering when the ADC buffer was full, which was causing the buffer I was trying to render from to change mid frame. I thought my double buffering would give me enough time to render, guess I was wrong Earlier when discussing triggering you mentioned:
User_UMjT7KxnxP8YN8 wrote:If you have complex triggers, you may want to configure the ADC to generate an interrupt after every sample so you can see if your trigger condition has occurred so you can look at it as soon as possible. You could DMA your pre-capture data to a small circular buffer while waiting for your trigger, then kick off a second, higher-priority DMA of trace A/D data upon detecting the trigger.
This along with a trigger holdoff until the screen has finished rendering and your latest post (with the suggestion of the event system) seems to be the simplest, most efficient answer this very strange problem.
User_UMjT7KxnxP8YN8 wrote:I've read the ATSAMD51 datasheet chapter on DMAs but never got around to implementing it (I'm a big fan of DMAs - who doesn't love freeing up CPU clock cycles?). IIRC, you can use an event from the A/D convertor (e.g., sample ready) to trigger a DMA without processor intervention.
I'm currently using the Adafruit_ZeroDMA library to handle my DMA right now, it's not obvious because I'm relying on the ILI9341 library inclusion (I know bad practice). I plan on changing to manipulating the registers myself, however this provided me an easy way to get some code running because the ILI9341 library is already using it and I want to make sure when I start messing with the registers myself I'm not interfering with that library.
User_UMjT7KxnxP8YN8 wrote:I'm a big fan of DMAs - who doesn't love freeing up CPU clock cycles?
Who wouldn't be, lol; this is my first time working with them, and I'm loving the extra cycles even if they have their nuances. :D

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Grand Central Oscilloscope adc help

Post by User_UMjT7KxnxP8YN8 »

I'm guessing you've already run your signal generator by itself and verified that the waveforms look as you expect them to. If so, maybe your issue is the relative priorities of A/D and D/A interrupts and DMAs? If you're driving a system with a waveform then capturing its response I'd think the D/A should get top priority. If you take a while to read a sample, it's going to be there until the next conversion completes, but if you delay a D/A it may cause a malformed output waveform.

Most oscilloscopes I've used (admittedly some years ago) display nothing but "waiting for trigger" until they trigger. Upon triggering, they capture data, then display it. Given the relatively modest resources of the ATSAMD51, you may want to consider that approach.

You may have noticed that you can overclock the Grand Central's to 200 MHz. I run all of my Metro M4 Expresses at 200 MHz and for non-IO-bound programs it makes a huge difference. Never had one yet that didn't love 200 MHz. I've got one that's been running 24/7 for over 2 years. I've got a Feather M4 Express driving an ILI9341 display and overclocking significantly improved text page scroll time (see graphic). You can see where the improvement starts to drop off as IO speed begins to play a bigger role than processing time. Sorry about the cursor capture :)

Nothing wrong with using the ILI9241 library, and as long as you use different DMA channels than the one it's using you should be fine. But I'd use the highest priority channels (0 & 1 as I recall) for your D/As, 2 & 3 for your A/Ds and move the display to 4. Always a good idea to keep your priorities straight.

Another idea you may want to pursue: if you're storing your data in RAM, you set up your DMAs to capture to circular buffers. Don't recall if that can be automated, but you can certainly DMA into a buffer until the buffer is full then generate an interrupt and restart the DMA in the ISR. Then your code could look in the buffer for the trigger, remember where it occurred and when your buffer is full, set up a shorter DMA to capture from the beginning of the buffer until just before the trigger (basically just modify the DMA descriptor for a shorter capture). You can stitch them together during post-capture processing before display. This approach would guarantee no gap between trigger and waveform.
ScrollTimes.jpg
ScrollTimes.jpg (16.32 KiB) Viewed 358 times

User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Re: Grand Central Oscilloscope adc help

Post by BrokenCode »

User_UMjT7KxnxP8YN8 wrote:I'm guessing you've already run your signal generator by itself and verified that the waveforms look as you expect them to. If so, maybe your issue is the relative priorities of A/D and D/A interrupts and DMAs? If you're driving a system with a waveform then capturing its response I'd think the D/A should get top priority. If you take a while to read a sample, it's going to be there until the next conversion completes, but if you delay a D/A it may cause a malformed output waveform.
I'll look into this in more detail, admittedly I haven't paid much (any) attention to the DMA priorities. It would be better to have them organized as you suggest.
User_UMjT7KxnxP8YN8 wrote:Most oscilloscopes I've used (admittedly some years ago) display nothing but "waiting for trigger" until they trigger. Upon triggering, they capture data, then display it. Given the relatively modest resources of the ATSAMD51, you may want to consider that approach.
Analog oscilloscopes usually operate this way, not allowing you to see anything before the trigger. Some digital ones work this way to, usually the oldest ones. Most modern digital oscilloscopes continually capture data while searching for a trigger then place the trigger in the center of the screen, also allowing the trigger to moved horizontally on the screen. I would prefer to have pre-trigger data, but it shouldn't be absolutely necessary. Pre-trigger data seems more of a convenience than anything to me.
User_UMjT7KxnxP8YN8 wrote:You may have noticed that you can overclock the Grand Central's to 200 MHz. I run all of my Metro M4 Expresses at 200 MHz and for non-IO-bound programs it makes a huge difference. Never had one yet that didn't love 200 MHz. I've got one that's been running 24/7 for over 2 years. I've got a Feather M4 Express driving an ILI9341 display and overclocking significantly improved text page scroll time (see graphic). You can see where the improvement starts to drop off as IO speed begins to play a bigger role than processing time. Sorry about the cursor capture :)
When I discovered the overclocking ability of the SAMD51 I got really excited, like a little kid or something. I've always been fascinated with overclocking on the PC CPU/GPUs and was astounded to see it on a microcontroller. I also noticed Adafruit also included an optimization option in the SAMD Arduino core that causes GCC to unroll loops and apply every speed optimization possible, at the expense of code size. They call it "here be the dragons" stating that it may introduce bugs, however I haven't seen any indication that unrolling the loops has caused any issues. It does however speed things up a lot, especially when combined with 200 MHz.
User_UMjT7KxnxP8YN8 wrote:Nothing wrong with using the ILI9241 library, and as long as you use different DMA channels than the one it's using you should be fine. But I'd use the highest priority channels (0 & 1 as I recall) for your D/As, 2 & 3 for your A/Ds and move the display to 4. Always a good idea to keep your priorities straight.
I'll have to look into whether the Adafruit_ZeroDMA allows me to define which channels are used for what. I know it allows defining priorities though.
User_UMjT7KxnxP8YN8 wrote:Another idea you may want to pursue: if you're storing your data in RAM, you set up your DMAs to capture to circular buffers. Don't recall if that can be automated, but you can certainly DMA into a buffer until the buffer is full then generate an interrupt and restart the DMA in the ISR. Then your code could look in the buffer for the trigger, remember where it occurred and when your buffer is full, set up a shorter DMA to capture from the beginning of the buffer until just before the trigger (basically just modify the DMA descriptor for a shorter capture). You can stitch them together during post-capture processing before display. This approach would guarantee no gap between trigger and waveform.
I currently have the ADC DMA descriptors configured to fill buffer1 then immediately fill buffer2 then buffer1, switching back and forth automatically. This gives me the length of time to fill the second buffer to render the first, however transferring to the screen seems to be the bottleneck here. I have a couple ideas to change the rasterization stage of the rendering, such as using a full screen buffer as opposed to writing each 240pixel column. Though I don't think the rasterization is the issue as expanding the size of ADC buffers to 25000 samples remedied the tearing effects. This lead me to believe that I should search for a trigger then start data capture ignoring any pre-trigger data. I don't see the loss of pre-trigger data as problem, but I may look into it at a future time.

Aside: I read more into the datasheet of the SRAM chip and any data access that crosses the 1k page boundaries is limited to 84MHz giving a max of 42MBPS. Looking more in depth in both datasheets appears that the external SRAM should be more than fast enough. I will break out the soldering gear (hopefully within the next of couple days) and install it and I will be able to do some experiments to test the actual timing ability.

User avatar
User_UMjT7KxnxP8YN8
 
Posts: 323
Joined: Tue Jul 17, 2018 1:28 pm

Re: Grand Central Oscilloscope adc help

Post by User_UMjT7KxnxP8YN8 »

SMT soldering is more of an art than anything
I have a hot air rework station and bought solder paste. First attempt was to replace a microUSB port that had broken loose from an otherwise functional Arduino board. I had a hard time melting the solder paste and getting port to stay in place while I heated the pads. Never did get the board to work. The solder paste https://static6.arrow.com/aropdfconvers ... -4860p.pdf contains flux, so I didn't apply any. Do you think that may have been the problem?

User avatar
BrokenCode
 
Posts: 9
Joined: Wed Nov 11, 2020 4:26 pm

Re: Grand Central Oscilloscope adc help

Post by BrokenCode »

User_UMjT7KxnxP8YN8 wrote:The solder paste https://static6.arrow.com/aropdfconvers ... -4860p.pdf contains flux, so I didn't apply any. Do you think that may have been the problem?
It is very likely so, I use liquid flux because it can flow into small areas and flux in general helps clean surfaces as it heats up. Admittedly I have never used solder paste as I usually just cut off a small piece of solder and lay across the pins followed by heating with hot air, and always cleaning up bridging with de-soldering braid. I tack a pin on the corner first thing with my iron which helps a lot with alignment. I actually find it easier to solder Integrated Circuits than some port connectors. I personally had a failed attempt when attempting to replace a broken tablet USB port. USB ports are hard to solder without a proper reflow oven. I personally don't like no clean flux as It makes the board look horrible. People may say I use too much flux but I use lots and lots, it helps heat the board and components up and spread the heat to everything.

I recommend getting some small breakout boards and some chips that you don't mind loosing if you mess things up and practicing. Plus if you get it right you have the SMD chip ready for prototyping with a breadboard, so minimal risk if you want to think that way.

I have attached an example of a couple chips I soldered to breakout boards, as you can see the chip on the right still has the flux literally filling the all the gaps around the chip. This was done with only an iron.

PS: I am team lead 100%. I bought a roll of lead free solder a year ago and quickly learned that the melting point is so much higher, they generally don't like to stick as well, and a hundred other problems that are all fixed by 60/40 lead based and ventilation. I could go on for days about how lead free solder is inferior to lead based and how the other chemicals in lead free solder are no better than lead based. Assuming solely from the datasheet you linked you may share the same opinion.

PS: To anybody inexperienced with soldering and the like. If your soldering with any solder ventilation is the most important thing to have. I have experienced light headedness and stuff from soldering in inappropriate locations, its not fun and can take less than 10 minutes to feel the affects of poor ventilation.
Attachments
Example of QSPI ram and QSPI flash chips soldered to breakout board
Example of QSPI ram and QSPI flash chips soldered to breakout board
IMG_20211111_105310745_HDR.jpg (972.61 KiB) Viewed 302 times

Locked
Please be positive and constructive with your questions and comments.

Return to “Metro, Metro Express, and Grand Central Boards”