Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 17 of 17

Thread: Teensy 3.6 ADC/DMA Question

  1. #1
    Senior Member
    Join Date
    Nov 2012
    Posts
    241

    Teensy 3.6 ADC/DMA Question

    I would like to use DMA with Teeny 3 ADCs in a mode similar to STM32 "circular" mode. Is there a way to do this?

    In STM32 "circular" mode I use a 1024 byte buffer and set up a DMA channel to continuously read the ADC.

    The DMA controller produces an interrupt at mid buffer and end of buffer. At end of buffer it restarts at beginning of buffer.

    This simple interrupt routine writes a continuous stream of 512 byte blocks to a FIFO.

    Code:
    // uint32_t to insure 4-byte alignment.
    static uint32_t dmaBuf[2][128];
    
    static uint32_t dmaCount;
    
    static void DMA1_CH1_Event() {                                                                                                        static void DMA1_CH1_Event() {
      if (fifoCount < FIFO_DIM) {
        // memcpy takes 7 microseconds on STM32F103 at 72 MHz.
        memcpy(fifoBuf[fifoHead], dmaBuf[dmaCount & 1], 512);
        fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
        fifoCount++;
        dmaCount++;
      } else {
        // Overrun error.
      }
    }
    I have been testing my new exFAT library.

    It only requires 6 KB of FIFO on an ebay STM32F103C8 board, which has 20KB of RAM, to log a sequence of ADC channels to an SD at nearly 1MHz.

    I would like to go faster on Teensy 3.6 and try SDIO.

  2. #2
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    4,569
    Hi Bill,

    I am hoping others who have more direct experience doing what you want will give a more complete answer as I have not done exactly what you have mentioned. However I have done some playing around with the DMA with the SPI code as well as with my version of ili9341_t3n library which added a dma update... But I have not tried anything with DMA with the ADC...

    Again I have not done the circular code directly, but I do know that it is supported. If you look at the DMAChannel.h(cpp) files you will find members for setting up circular (SourceCircular, or more likely for your case destinationCircular). Queue.

    You can also setup for interrupts at completion and/or midpoint, with the interruptAtCompletion and the interruptAtHalf.

    Again I have not used the circular buffer directly, but have used the linking of buffers, which worked in a circular way, like what FrankB did with the DMA version of the ILI9341 library. I did not go circular as the maximum count for a buffer is either 32767 or 511, depending on some different options, and the display is 320*240*2 bytes...

    Also if you do chain buffers, you can setup additional interrupts if needed.

    Again sorry not much help here.

    Kurt

  3. #3
    Senior Member+ manitou's Avatar
    Join Date
    Jan 2013
    Posts
    1,929
    me thinks the teensy audio lib uses a circular DMA buffer for ADC
    https://forum.pjrc.com/threads/24492...ll=1#post37448

    and pedvide's ADC library might have circular DMA examples
    Last edited by manitou; 08-24-2017 at 03:31 PM.

  4. #4
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    19,929
    Quote Originally Posted by Bill Greiman View Post
    Is there a way to do this?
    Yes. Multiple ways, actually.

    In STM32 "circular" mode I use a 1024 byte buffer and set up a DMA channel to continuously read the ADC.

    The DMA controller produces an interrupt at mid buffer and end of buffer. At end of buffer it restarts at beginning of buffer.
    You're going to have to look at the DMA TCD registers in the reference manual. Or maybe you can make use of DMAChannel.h helper functions. But the way most libs use DMAChannel is merely to allocate the channel (so they play nicely with other libs using DMA) and then access its TCD registers.

    There is an option for a circular buffer.

    But they way you've described is probably done in most cases by setting the destination last address offset register to adjust the destination back to the beginning of the buffer, and the flags register to leave the channel enabled. Then the next hardware request gets serviced, because the DMA channel automatically sets itself back to its original settings with the final destination adjust. Of course, there's also a flag for the interrupts at half and full. The ISR function can tell which half is being written by just looking at the destination register.

    This is how most of the audio library DMA works. If you're looking for an example to start, those might help.

  5. #5
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    Bill take a look at this post, I think you can hack it down to do what you want. There are multiple examples of different setups. We mostly used both ADC0 and ADC1 but you can configure it, just takes practice.

  6. #6
    Senior Member
    Join Date
    Nov 2012
    Posts
    241
    Thanks, looks like I have lots of examples for Teensy 3.

    Now I need an architecture for all the boards I want to support. I think I will use a HAL, hardware adaptation layer, model from an RTOS.

    I plan to support STM32F1, STM32F4, Teensy 3, and probably SAM3/Due initially. People are starting to use lots of ebay Due clones, that cost about $13. I may do an interrupt version for AVR.

    The HAL I use a lot has a abstract level with these calls for ADCs with interrupt or DMA data readout.
    Code:
    void 	adcInit (void)
     	ADC Driver initialization. More...
     
    void 	adcObjectInit (ADCDriver *adcp)
     	Initializes the standard part of a ADCDriver structure. More...
     
    void 	adcStart (ADCDriver *adcp, const ADCConfig *config)
     	Configures and activates the ADC peripheral. More...
     
    void 	adcStop (ADCDriver *adcp)
     	Deactivates the ADC peripheral. More...
     
    void 	adcStartConversion (ADCDriver *adcp, const ADCConversionGroup *grpp, adcsample_t *samples, size_t depth)
     	Starts an ADC conversion. More...
     
    void 	adcStartConversionI (ADCDriver *adcp, const ADCConversionGroup *grpp, adcsample_t *samples, size_t depth)
     	Starts an ADC conversion in an interrupt handler. More...
     
    void 	adcStopConversion (ADCDriver *adcp)
     	Stops an ongoing conversion. More...
     
    void 	adcStopConversionI (ADCDriver *adcp)
     	Stops an ongoing conversion in an interrupt handler.. More...
     
    msg_t 	adcConvert (ADCDriver *adcp, const ADCConversionGroup *grpp, adcsample_t *samples, size_t depth)
     	Performs an ADC conversion. More...
    There is an abstract level that specifies the mandatory fields for each of the types. Here are the mandatory fields for ADCConversionGroup.

    Code:
    typedef struct {
      /**
       * @brief   Enables the circular buffer mode for the group.
       */
      bool                      circular;
      /**
       * @brief   Number of the analog channels belonging to the conversion group.
       */
      adc_channels_num_t        num_channels;
      /**
       * @brief   Callback function associated to the group or @p NULL.
       */
      adccallback_t             end_cb;
      /**
       * @brief   Error callback or @p NULL.
       */
      adcerrorcallback_t        error_cb;
      /* End of the mandatory fields.*/
    } ADCConversionGroup;
    The low level drivers for each architecture defines the full type. Here is STM32F1 ADCConversionGroup.
    Code:
    typedef struct {
      /**
       * @brief   Enables the circular buffer mode for the group.
       */
      bool                      circular;
      /**
       * @brief   Number of the analog channels belonging to the conversion group.
       */
      adc_channels_num_t        num_channels;
      /**
       * @brief   Callback function associated to the group or @p NULL.
       */
      adccallback_t             end_cb;
      /**
       * @brief   Error callback or @p NULL.
       */
      adcerrorcallback_t        error_cb;
      /* End of the mandatory fields.*/
      /**
       * @brief   ADC CR1 register initialization data.
       * @note    All the required bits must be defined into this field except
       *          @p ADC_CR1_SCAN that is enforced inside the driver.
       */
      uint32_t                  cr1;
      /**
       * @brief   ADC CR2 register initialization data.
       * @note    All the required bits must be defined into this field except
       *          @p ADC_CR2_DMA, @p ADC_CR2_CONT and @p ADC_CR2_ADON that are
       *          enforced inside the driver.
       */
      uint32_t                  cr2;
      /**
       * @brief   ADC SMPR1 register initialization data.
       * @details In this field must be specified the sample times for channels
       *          10...17.
       */
      uint32_t                  smpr1;
      /**
       * @brief   ADC SMPR2 register initialization data.
       * @details In this field must be specified the sample times for channels
       *          0...9.
       */
      uint32_t                  smpr2;
      /**
       * @brief   ADC SQR1 register initialization data.
       * @details Conversion group sequence 13...16 + sequence length.
       */
      uint32_t                  sqr1;
      /**
       * @brief   ADC SQR2 register initialization data.
       * @details Conversion group sequence 7...12.
       */
      uint32_t                  sqr2;
      /**
       * @brief   ADC SQR3 register initialization data.
       * @details Conversion group sequence 1...6.
       */
      uint32_t                  sqr3;
    } ADCConversionGroup;

  7. #7
    Senior Member
    Join Date
    Nov 2012
    Posts
    241
    I decided to try logging with a dummy DMA device to make sure exFAT with FIFO_SDIO is fast enough. Looks like ADCs won't be a problem.

    Logging at 10 MB/sec seems to work fine with a quality Samsung SD.

    I attached the test program. It allocates a FIFO with 400 sectors which uses 204,800 bytes of SRAM.

    I used a timer to simulate a DMA device that reads 512 bytes and generates an interrupt every 50 microseconds

    Here is the simulated ISR with a circular two sector DMA buffer :
    Code:
    // Dummy ISR.
    void callback() {
      if (fifoCount < FIFO_DIM) {
        // Takes about 3 microseconds on teensy 3.6.
        memcpy(fifoBuf[fifoHead], dmaBuf[dmaCount & 1], 512);
        dmaCount++;
        fifoHead = fifoHead < (FIFO_DIM - 1) ? fifoHead + 1 : 0;
        fifoCount++;
        if (fifoCount > maxFifoCount) {
          maxFifoCount = fifoCount;
        }
      } else {
        // overrun
        overrun = true;
      }
    }
    Here are results for a few 64GB exFAT microSD cards. Samsung Pro cards are best, SanDisk Extreme Pro cost more but are not as good and Samsung Evo doesn't cut it.

    Samsung cards use about half the FIFO. The max used is maxFifoCount.
    HTML Code:
    Type any character to begin
    
    FIFO_DIM = 400
    FreeStack: 49595
    Type any character to stop
    
    Samsung Pro Select 64 GB
    197 maxFifoCount
    3435397120 bytes
    335.49 seconds
    10.24 MB/sec
    
    SanDisk Extreme Pro 64GB
    241 maxFifoCount
    1588078592 bytes
    155.09 seconds
    10.24 MB/sec
    
    Second Samsung Pro Select 64 GB
    146 maxFifoCount
    1842427904 bytes
    179.92 seconds
    10.24 MB/sec
    
    Old Samsung Pro+
    164 maxFifoCount
    1266618880 bytes
    123.69 seconds
    10.24 MB/sec
    
    Samsung EVO 64GB fails.
    Overrun ERROR!!
    400 maxFifoCount
    41157120 bytes
    4.07 seconds
    10.12 MB/sec
    Attached Files Attached Files

  8. #8
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    Quote Originally Posted by Bill Greiman View Post
    Logging at 10 MB/sec seems to work fine with a quality Samsung SD.
    Nice, if i'm looking at my notes correctly you could do +/-1.4Msps between ADC0 and ADC1 combined at 12Bit.

  9. #9
    Senior Member
    Join Date
    Nov 2012
    Posts
    241
    Quote Originally Posted by Donziboy2 View Post
    Nice, if i'm looking at my notes correctly you could do +/-1.4Msps between ADC0 and ADC1 combined at 12Bit.
    It should be possible to easily do 1.4Msps.

    I attached a first cut prototype for ADC0. It's pretty rough, just a test.

    I hacked Paul's Audio ADC0 program. I am overclocking the ADC by a factor of 2.5 over the 24 MHz max and cutting sample and hold cycles to the minimum.

    This runs the ADC with a clock of F_BUS, 60MHz. The result is 3.0 Msps with 12-bit samples. I scaled the points in the plot by 3.3/4095

    I just wanted the speed, not accuracy. The results are better than I expected. Here is a quick Excel plot of a 50kHz sine with 60 points per cycle.

    Click image for larger version. 

Name:	TeensyTest.jpg 
Views:	98 
Size:	140.5 KB 
ID:	11390

    I have a very good scope and it measures Vpp and Voff very close to the plot. The signal generator isn't too accurate for high impedance loads.

    The program uses only 70 of the 400 fifo sectors that I allocate. I am pleased with exFAT performance.

    Code:
    FIFO_DIM = 400
    FreeStack: 49599
    Type any character to stop
    
    70 maxFifoCount
    1983390720 bytes
    1046.39 seconds
    6.00 MB/sec
    Attached Files Attached Files
    Last edited by Bill Greiman; 08-27-2017 at 04:02 PM.

  10. #10
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    The time constant on the Teensy 3 ADC is something like 25nS, so if you drive it with a decent source it wont take long to get charged. And I would not expect to much noise if the source is decent.

    The fastest possible sample speed is 4 ADC clock cycles(dont recall if it is constant or goes up after 1 sample). And if we go for say 5 time constants(the standard in textbooks) we would need 125nS minimum, so say 32Mhz on the ADC clock with 4 ADC clocks per sample. Probably safer at 24Mhz to reduce noise and possible hardware issues.
    This also assumes you dont mind loosing some of your ADC voltage at 5 time constants you will loose about 28 off your 12bit value since the cap is not fully charged..
    See the below image for time constants vs percentage of charge.

    Click image for larger version. 

Name:	ADC Time Constant Losses.JPG 
Views:	102 
Size:	68.7 KB 
ID:	11395

  11. #11
    Senior Member
    Join Date
    Nov 2012
    Posts
    241
    Quote Originally Posted by Donziboy2 View Post
    The time constant on the Teensy 3 ADC is something like 25nS, so if you drive it with a decent source it wont take long to get charged. And I would not expect to much noise if the source is decent.
    My test is kind of a cheat. I used a signal generator designed to drive a 50 ohm load. The 50 kHz sine at a sampling rate of 3 Msps means you don't really see S/H performance. You don't need to change the cap voltage much since there are 60 sample and hold times for a cycle.

    My main reason for the test was to show that I didn't have a bug that scrambled the values in the circular dma buffer or FIFO. I was glad to see the sine pattern when I read the file.

    I did find a bug in truncate(). I allocate a 8GiB file then truncate it at the end. I didn't sync properly in truncate so file system could be corrupt if the file was not accessed and closed properly.

    I try to do tests during development but nothing beats real apps for revealing bugs.

  12. #12
    Senior Member
    Join Date
    Nov 2012
    Posts
    241
    I ran a test with a square wave. Interesting noise. It is max about every 256 points, probably when I write a sector. No surprise, no signal averaging short S/H. No 50 ohm load, dangling wires, USB power, ...

    Click image for larger version. 

Name:	TeensySquare.jpg 
Views:	97 
Size:	174.9 KB 
ID:	11399

    Data is attached. No ideal 12-bit 74 dB SNR here.

    A closer view.

    Click image for larger version. 

Name:	TeensySquareMag.jpg 
Views:	76 
Size:	65.1 KB 
ID:	11400
    Attached Files Attached Files
    Last edited by Bill Greiman; 08-28-2017 at 03:17 PM.

  13. #13
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    I wonder if there should be resistors between the SDcard pins and the Teensy 3.5/3.6 to reduce noise.

  14. #14
    Senior Member
    Join Date
    Oct 2012
    Location
    Portland OR
    Posts
    593
    ...or a separate regulator and a separate ground return for the SD card power supply.

  15. #15
    Senior Member
    Join Date
    Mar 2013
    Posts
    651
    That may work unless the noise is induced on the ADC via the Data lines, which is something I have seen with SPI coms to ILI9341 and reading ADC's with/without external ref at the same time.

  16. #16
    Hi Bill, I`m searching a code like yours, I want read analog values and write it fastest as possible in SD. I don't have so much experience like you to do the code, May you share your code?
    When you say that you can Read analog and Write it on SD with 3Msps, its each ADC channel or combined two? If I use like 4 ch analogRead, 2 per channel I may expect 750Ksps or 1.5Msps?
    Thnak you

  17. #17
    Hello, how can I make your code to create a .txt or .csv file instead of a binary one?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •