ADC ENOB low value.

Generic boards that are not Maple or Maple mini clones, and don't contain the additional USB reset hardware
Post Reply
MasterT
Posts: 12
Joined: Wed Mar 08, 2017 4:17 am

ADC ENOB low value.

Post by MasterT » Thu Mar 09, 2017 6:45 pm

I get couple boards from aliexpress:
Image

Running ENOB test for ADC I see pretty low value, ~ 8.0 bits. Decoupling on power line with two caps, electrolytic 10uF and ceramic 0.1 may vary +-0.3 bits, but not really changing anything substantially.
Input is biased about Vcc/2, signal source impedance should be low, no more than 500 oHm (Hantec DSO built in generator output in series with 220 oHm for safety).

Input 57.8 kHz, start conversion CONT (free run) prescaler /6 = sampling rate 857.142. Hz
Power 3.3V + FTDI 3.3V
Peak number: 69 magnitude: 1026789.38 Total: 2978.2058105469 SINAD: 50.751 ENOB: 8.138
Peak number: 69 magnitude: 1027198.25 Total: 2760.3044433594 SINAD: 51.414 ENOB: 8.248
Peak number: 69 magnitude: 1026673.88 Total: 3061.3911132813 SINAD: 50.510 ENOB: 8.098
Peak number: 69 magnitude: 1025888.13 Total: 3155.5949707031 SINAD: 50.240 ENOB: 8.053
Peak number: 69 magnitude: 1025854.44 Total: 2885.7080078125 SINAD: 51.017 ENOB: 8.182


I'm sure that software is correct, same math (fft-1024) outputs 9.6 bits for arduino Mega2560 and 9.2 bits for Due board (it's also low for Due, not so drastically).
The question is : does anyone measure this value, and what may be the cause of failure?
Config

Code: Select all

void dma_irq(void) 
{  
      process = dma_get_irq_cause(DMA1, DMA_CH1);
  
  if (process == DMA_TRANSFER_ERROR) {
        dma_disable(DMA1, DMA_CH1);                 // Disable ourselves to prevent further errors              
        process = 0;
    return;                                         // Something went wrong, exit early.
    } 
    
  process += 1;                                     // Complete = 1, Half = 2.
  dma_clear_isr_bits( DMA1, DMA_CH1 );
	//  adc_set_exttrig(ADC1, 0);        
  ADC1->regs->CR2 &= ~ADC_CR2_CONT;
}

void init_dma(void) 
{  
  dma_init(DMA1);                                   //rcc_clk_enable
  dma_attach_interrupt (DMA1, DMA_CH1, dma_irq);
  dma_setup_transfer(DMA1, DMA_CH1,
                   &ADC1->regs->DR, DMA_SIZE_16BITS,
                   & adc_buf,       DMA_SIZE_16BITS,                  
                   (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_TRNS_CMPLT)
                   );                       
  dma_set_num_transfers(DMA1, DMA_CH1, INP_BUFF);    
  dma_set_priority(DMA1, DMA_CH1, DMA_PRIORITY_VERY_HIGH);
  dma_enable(DMA1, DMA_CH1);
}

void init_adc(void) 
{
  adc_set_prescaler(ADC_PRE_PCLK2_DIV_6);
  adc_calibrate(ADC1); 
  //  adc_set_sample_rate(ADC1, ADC_SMPR_71_5);
  adc_set_sample_rate(ADC1, ADC_SMPR_1_5);
  ADC1->regs->CR2 |=  ADC_CR2_DMA;  
  adc_set_reg_seqlen(ADC1, 1);
  adc_enable(ADC1);
  ADC1->regs->CR2 |= ADC_CR2_CONT;
  ADC1->regs->CR2 |= ADC_CR2_SWSTART;
}
I also tried to change ADC_SMPR_1_5 to maximum 239.5 and observe some improvement, but sampling rate becomes unacceptable low , so this is not an option.

User avatar
Pito
Posts: 1109
Joined: Sat Mar 26, 2016 3:26 pm
Location: Rapa Nui

Re: ADC ENOB low value.

Post by Pito » Thu Mar 09, 2017 7:16 pm

Could you post the complete sketch, plz?
Pukao Hats Cleaning Services Ltd.

User avatar
RogerClark
Posts: 5917
Joined: Mon Apr 27, 2015 10:36 am
Location: Melbourne, Australia
Contact:

Re: ADC ENOB low value.

Post by RogerClark » Thu Mar 09, 2017 8:43 pm

I am not sure what you are testing, but I think the problem is the analog supply input is connected directly to the main Vdd on these boards.

If you want better analog performance, you will have to cut the track and feed it separately.

Take a look at the original Maple-mini schematic on the leaflabs github repo and compare that with the schematic for the Blue Pill in the wiki.stm32duino.com

Unfortunately, I dont think any of the boards currently for sale, including the reworked Maple-mini boards, have a separate supply for the analog supply input

stevestrong
Posts: 1139
Joined: Mon Oct 19, 2015 12:06 am
Location: Munich, Germany

Re: ADC ENOB low value.

Post by stevestrong » Fri Mar 10, 2017 1:12 pm

RogerClark wrote:I am not sure what you are testing, but I think the problem is the analog supply input is connected directly to the main Vdd on these boards.
If you want better analog performance, you will have to cut the track and feed it separately.
Or supply the board from a battery.
In my application I sample the analog lines on one pill supplied with a battery, and send the data over SPI to the second pill which processes/stores the transmitted sampled data.

And it would be nice if you could share the whole sketch.

User avatar
mrburnette
Posts: 1783
Joined: Mon Apr 27, 2015 12:50 pm
Location: Greater Atlanta
Contact:

Re: ADC ENOB low value.

Post by mrburnette » Fri Mar 10, 2017 1:40 pm

The "original" Maple boards were 4 layer boards, with a separate analog ground plane. As mentioned earlier, in the cheap-o boards, the analog reference is tied to Vcc which is 3.3'ish Volts. Looking at the running STM32F103 Vcc to ground with a scope, there is lots of noise present. Likely this is because of the cheap 2-sided PC board and poor bypassing on the board. We could discuss reworking the board with Tantalum capacitors and implementing a ferrite bead on the analog reference side but by the time you get all of that rework done, the board is going to look like it came out of Frankenstein's workshop.

When critical analog performance is required, it is always an appropriate design to use a separate A/D chip. This solves most analog problems by simply not using the A/D inside the uC. Once can also get better readings by averaging A/D readings. And while I have not tried this technique, it was mentioned in the original Leaflab forum: http://forums.leaflabs.com/forums.leafl ... l?id=74213

Another possibility is to power the board with a low-Z battery such as a LiFePO4 cell ... these particular cells can be "floated" on the 3.3 Volt bus and act as a low-cost super-capacitor in this use. I've been doing this for 18 months on a test project in my lab and cell still exhibits a superb low-Z with the added benefit of completely supplying the full 3.3 V load should the main supply be temporarily removed.

Ray

MasterT
Posts: 12
Joined: Wed Mar 08, 2017 4:17 am

Re: ADC ENOB low value.

Post by MasterT » Fri Mar 10, 2017 3:40 pm

Thanks all for your comments,
Certainly, ENOB low value is related to board design, and arduino Due is not much different. I was surprised that AtMega board with 10-bit ADC has better results than ARM with their "supposed to be" 12-bits, but 8-9 in reality,
Here is the sketch, I use IDE tabs - no external library, fft code is taken from this page and re-worked.
http://www.jjj.de/fft/fftpage.html

Code: Select all


//------------------------------------------------- MAIN page tab
#include    "dma.h"
#include    "string.h"

#define     FFT_SIZE             1024
#define     INP_BUFF         FFT_SIZE    
#define     MIRROR           FFT_SIZE / 2

//#define     TMR_BASE            36000               // Timer Clock in kHz
//#define     SMP_RATE               41               // Sampling Rate in kHz

            uint16_t  adc_buf[INP_BUFF] = { 0};     /* 2 x1024 = 2 k*/
            float     f_r[FFT_SIZE]     = { 0};     /* 4 x1024 = 4 k*/
            uint16_t  copy[INP_BUFF]    =  {0};     /* 2 x1024 = 2 k*/

volatile    int32     process           =    0;     // flag.

const       uint32    analogPin1        =  PA0;    // AN0
const       uint32    analogPin2        =  PA1;    // AN1
const       uint32    analogPin3        =  PA2;    // AN2
const       uint32    analogPin4        =  PA3;    // AN3 

const       uint32    digitalPin1       = PB11;    // process time
const       uint32    digitalPin2       = PB10;    // input time
const       uint32    digitalPin3       =  PB1;    // rev_bin +fft
const       uint32    digitalPin4       = PB12;    // gain-reset + magnit2

const       int32     dc_offset         = 2000; 
  
//            int32     print_inp         =    0;    // print switch
             
            uint32    adres_reg         =    0;         
            String    in_String         =   "";        
            boolean   end_input         =false;  
            uint8_t   debug_osm         =    0;

            int16_t   bin_numbr         =    0;
            float     max_magnt         =    0;
            float     tot_magnt         =    0;
            float     sinad             =  0.0;
            float     enobs             =  0.0;


void setup(void) 
{ 
  Serial.begin(115200); 
  in_String.reserve(200);  
    // Configure the ADC pin
  pinMode(analogPin1, INPUT_ANALOG);
  pinMode(analogPin2, INPUT_ANALOG);
  pinMode(analogPin3, INPUT_ANALOG);
  pinMode(analogPin4, INPUT_ANALOG);

  pinMode(PC13,     OUTPUT);

  pinMode(digitalPin1,       OUTPUT);
  pinMode(digitalPin2,       OUTPUT);
  pinMode(digitalPin3,       OUTPUT);
  pinMode(digitalPin4,       OUTPUT);
   
  init_dma();
//  init_tmr();
  init_adc();
  systick_disable();
}

void loop(void) {
  uint32_t  back  = 0;
  char *    pEnd;

  if(process ==1) {   
    digitalWrite( digitalPin1, HIGH);
    digitalWrite( digitalPin2, HIGH);
    
    for( uint16_t i = 0; i < INP_BUFF; i++) {
      int16_t temp = adc_buf[i];
        f_r[i] = (temp - dc_offset);
        copy[i] = (temp - dc_offset);
        }

// Test float
    if(debug_osm) {
      for(int i = 0; i < FFT_SIZE; i++){
        double wave = cos(( i *41.0 *( 2 *3.1415926535) /FFT_SIZE));
        f_r[i] = 2047.0 * wave;
        }        
      }    
// 2.Windowing BlackMan-Harris 4-term
     double base_angle = 2.0 *M_PI / (FFT_SIZE -1);
     for( uint16_t i = 0; i < FFT_SIZE; i++ ) {
        double angle = i *base_angle;
        float temp = 0.35875 -(0.48829*cos(angle)) +(0.14128*cos(2*angle)) -(0.01168*cos(3*angle));
       f_r[i] *= temp;
       }  
    digitalWrite( digitalPin2, LOW);
    // 228.4 msec
    digitalWrite( digitalPin3, HIGH);
    rev_bin( f_r, (int16_t) FFT_SIZE);
    rfft33( f_r, FFT_SIZE);
    digitalWrite( digitalPin3, LOW);
    digitalWrite( digitalPin4, HIGH);  
    // 55.2 msec
    get_MagnitF( f_r);
    // 12.4 msec
    digitalWrite( digitalPin1, LOW);                
    digitalWrite( digitalPin4, LOW);
               
    //xx All above = 296 (debug data cosine) millisec. 
    process = 0;
//    adc_set_exttrig(ADC1, 1);        
    ADC1->regs->CR2 |= ADC_CR2_CONT;
    ADC1->regs->CR2 |= ADC_CR2_SWSTART;
  }   

  serialEvent(); 

  if( end_input) {
    char cmd = in_String[0];
    in_String[0] = '+';
    
    if( cmd == 'a' ) {
      adres_reg = strtol( in_String.c_str(), &pEnd, 16);
      Serial.print(F("\n\tReg: "));
      Serial.print(adres_reg, HEX);
      Serial.print(F("\tvalue: "));
      back =   (*(uint32_t*)adres_reg);
//      Serial.print(back, BIN);
      prnt_binf(back);
      }
    if( cmd == 'r' ) {
      Serial.print(F("\n\tReg: "));
      Serial.print(adres_reg, HEX);
      Serial.print(F("\tvalue: "));
      back =   (*(uint32_t*)adres_reg);
//      Serial.print(back, BIN);
      prnt_binf(back);
      }
    if( cmd == 'w' ) {
      Serial.print(F("\n\tReg: "));
      Serial.print(adres_reg, HEX);
      Serial.print(F("\tvalue: "));
      back =   (*(uint32_t*)adres_reg);
//      Serial.print(back, BIN);
      prnt_binf(back);
      back = strtol( in_String.c_str(), &pEnd, 2);      
      (*(uint32_t*)adres_reg) = back;
      Serial.print(F("\tnew  value: "));
      back =   (*(uint32_t*)adres_reg);
//      Serial.print( back, BIN);      
      prnt_binf(back);
      }

    if( cmd == 'd' ) {
      debug_osm = 1 - debug_osm;
      if(debug_osm) Serial.print(F("\nDebug aktiv."));
      else          Serial.print(F("\nDebug de-aktiv."));
      }

    // "x" command - print a table of incomming buffer.
    if( cmd == 'x' ) {
      Serial.print("\n\t");  
        for( uint16_t i = 0; i < INP_BUFF; i++) {  
          int16_t tempr = copy[i];  
            Serial.print(tempr, DEC);       
            //Serial.print(",\n");
            Serial.print("\t");    
            if ((i+1)%16 == 0) Serial.print("\n\t");
            }
        Serial.println("\n\t");
      }
    // "f" command - print a table of fft results.
    if( cmd == 'f' ) {
      Serial.print("\n\t");  
        for( uint16_t i = 0; i < MIRROR; i++) {  
          Serial.print(f_r[i], 1);       
          Serial.print("\t");    
          if ((i+1)%16 == 0) Serial.print("\n\t");
          }
        Serial.println("\n\t");
      }
    // "e" command.
    if( cmd == 'e' ) {
      enob(f_r);
      Serial.print(F("\n\tPeak number:\t"));
      Serial.print(bin_numbr, DEC);
      Serial.print(F("\tmagnitude:\t"));
      Serial.print(max_magnt, 2);
      Serial.print(F("\tTotal:\t"));
      Serial.print(tot_magnt, 10);
      Serial.print(F("\tSINAD:\t"));
      Serial.print(sinad, 3);
      Serial.print(F("\tENOB:\t"));
      Serial.print(enobs, 3);
      }

    in_String = "";
    end_input= false;
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    in_String += inChar;
    if (inChar == '\n') {
      end_input= true;
    }
  }
}

//------------------------------------------------- ADC page tab
void dma_irq(void) 
{  
      process = dma_get_irq_cause(DMA1, DMA_CH1);
  
  if (process == DMA_TRANSFER_ERROR) {
        dma_disable(DMA1, DMA_CH1);                 // Disable ourselves to prevent further errors              
        process = 0;
    return;                                         // Something went wrong, exit early.
    } 
    
  process += 1;                                     // Complete = 1, Half = 2.
  dma_clear_isr_bits( DMA1, DMA_CH1 );
//  adc_set_exttrig(ADC1, 0);        
  ADC1->regs->CR2 &= ~ADC_CR2_CONT;
}

void init_dma(void) 
{  
  dma_init(DMA1);                                   //rcc_clk_enable
  dma_attach_interrupt (DMA1, DMA_CH1, dma_irq);
  dma_setup_transfer(DMA1, DMA_CH1,
//                   &ADC1->regs->DR, DMA_SIZE_32BITS,
  //                 & adc_buf,       DMA_SIZE_32BITS,                  
//                   (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_TRNS_CMPLT | DMA_HALF_TRNS)
                   &ADC1->regs->DR, DMA_SIZE_16BITS,
                   & adc_buf,       DMA_SIZE_16BITS,                  
                   (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_TRNS_CMPLT)
                   );                       
  dma_set_num_transfers(DMA1, DMA_CH1, INP_BUFF);    
  dma_set_priority(DMA1, DMA_CH1, DMA_PRIORITY_VERY_HIGH);
  dma_enable(DMA1, DMA_CH1);
}

void init_adc(void) 
{
  adc_set_prescaler(ADC_PRE_PCLK2_DIV_6);

  adc_calibrate(ADC1); 
  adc_set_sample_rate(ADC1, ADC_SMPR_1_5);

  // ADC1->regs->CR1 |=  ADC_CR1_SCAN;  
  // ADC1->regs->SQR3 = 0x00000000; //  00 000   0 0000  //ADC 0 (PA 0) (board 11 & 10 pin)- board stm32-mini.
  ADC1->regs->CR2 |=  ADC_CR2_DMA;  
  //  adc_set_exttrig(ADC1, 1);        
  //  adc_set_extsel (ADC1, ADC_ADC12_TIM3_TRGO); 
  adc_set_reg_seqlen(ADC1, 1);
  //  ADC1->regs->CR1 |=  0x00060000; // DUAL MODE
  adc_enable(ADC1);
  //  adc_calibrate(ADC1); 
//setContinuous();
//startConversion();

  ADC1->regs->CR2 |= ADC_CR2_CONT;
  ADC1->regs->CR2 |= ADC_CR2_SWSTART;
}   

//-------------------------------------------------DMA page tab
/******************************************************************************
 * The MIT License
 *
 * Copyright (c) 2010 Michael Hope.
 * Copyright (c) 2012 LeafLabs, LLC.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************/

/**
 * @file libmaple/include/libmaple/dma.h
 *
 * @author Marti Bolivar <mbolivar@leaflabs.com>;
 *         Original implementation by Michael Hope
 *
 * @brief Direct Memory Access peripheral support
 */

#ifndef _LIBMAPLE_DMA_H_
#define _LIBMAPLE_DMA_H_

#ifdef __cplusplus
extern "C"{
#endif

/* <series/dma.h> provides:
 *
 * - An opaque dma_tube type, and predefined rvalues for each tube
 *   supported by the series.
 *
 *   A "DMA tube" is a series-specific (hopefully integer) datatype
 *   that abstracts the conduit through which DMA-ed data flow.
 *
 *   Examples: On STM32F1, dma_tube is just an alias for dma_channel,
 *   and the tube values are just DMA_CH1 (=1), DMA_CH2 (=2), etc.
 *
 *   Note that a dma_tube doesn't have to be an enum, and its values
 *   don't have to be integral. They _do_ need to be cheap to pass as
 *   arguments, though.
 *
 * - struct dma_tube_reg_map (and typedef to dma_tube_reg_map). DMA
 *   register maps tend to be split into global registers and per-tube
 *   registers. It's convenient to pass around pointers to a tube's
 *   registers, since that makes it possible to configure or otherwise
 *   mess with a tube without knowing which one you're dealing with.
 *
 * - Base pointers to the various dma_tube_reg_maps.
 *
 *   Examples: On STM32F1, these are DMAxCHy_BASE. You can access
 *   registers like DMAxCHy_BASE->CPAR, etc.
 *
 * - enum dma_request_src (and typedef to dma_request_src). This
 *   specifies the peripheral DMA request sources (e.g. USART TX DMA
 *   requests, etc.).
 *
 * - enum dma_mode_flags (and typedef to dma_mode_flags). Used in
 *   dma_tube_config. If two series both support the same mode flags,
 *   they must use the same enumerator names for those flags (the
 *   values of those enumerators are of course allowed to differ).
 *
 * - Normal stuff: dma_reg_map and base pointers, register bit
 *   definitions, dma_dev pointer declarations, and any other
 *   convenience functions useful for the series. */

 /* Roger clark. Replaced with line below #include <series/dma.h>*/
#include "stm32f1/include/series/dma.h"

/* <libmaple/dma_common.h> buys us dma_dev and other necessities. */
#include <libmaple/dma_common.h>
#include <libmaple/libmaple_types.h>

/*
 * Declarations/documentation for some of the series-provided types.
 */

/**
 * @brief (Series-dependent) DMA request sources.
 *
 * These specify the various pieces of peripheral functionality which
 * may make DMA requests.  Use them to set up a DMA transfer (see
 * struct dma_tube_config, dma_tube_cfg()).
 */
enum dma_request_src;

/**
 * @brief (Series-dependent) DMA tube configuration flags.
 * These specify miscellaneous bits of configuration for a DMA tube.
 * @see struct dma_mode_config
 */
enum dma_cfg_flags;

/**
 * @brief (Series-dependent) DMA tube register map type.
 * This allows you to access a tube's registers as a group.
 * @see dma_tube_regs()
 */
struct dma_tube_reg_map;

/*
 * Convenience functions
 */

/* Initialization */

void dma_init(dma_dev *dev);

/* dma_tube configuration
 *
 * Use these types and functions to set up DMA transfers, handle
 * interrupts, etc. The main function of interest is dma_tube_cfg(),
 * which the various series implement separately. */

/**
 * @brief Specifies a DMA tube configuration.
 *
 * Use one of these to set up a DMA transfer by passing it to
 * dma_tube_cfg().
 *
 * @see dma_tube_cfg()
 * @see dma_xfer_size
 */
typedef struct dma_tube_config {
    /** Source of data */
    __io void     *tube_src;
    /** Source transfer size */
    dma_xfer_size  tube_src_size;

    /** Destination of data */
    __io void     *tube_dst;
    /** Destination transfer size */
    dma_xfer_size  tube_dst_size;

    /**
     * Number of data to transfer (0 to 65,535).
     *
     * Note that this is NOT measured in bytes; it's measured in
     * number of data, which occur in multiples of tube_src_size. For
     * example, if tube_src_size is DMA_SIZE_32BITS and tube_nr_xfers
     * is 2, then 8 total bytes will be transferred.
     */
    unsigned tube_nr_xfers;

    /**
     * Target-specific configuration flags.
     *
     * These are an OR of series-specific enum dma_mode_flags values.
     * Consult the documentation for your target for what flags you
     * can use here.
     *
     * Typical flag examples: DMA_CFG_SRC_INC, DMA_CFG_DST_INC,
     * DMA_CFG_CIRC, DMA_CFG_CMPLT_IE, etc.
     */
    unsigned tube_flags;

    /**
     * Currently unused. You must set this to 0 or something valid for
     * your target. */
    void *target_data;

    /**
     * Hardware DMA request source.
     *
     * This is ignored for memory-to-memory transfers.
     */
    enum dma_request_src tube_req_src;
} dma_tube_config;

#define DMA_TUBE_CFG_SUCCESS 0
#define DMA_TUBE_CFG_EREQ    1
#define DMA_TUBE_CFG_ENDATA  2
#define DMA_TUBE_CFG_EDEV    3
#define DMA_TUBE_CFG_ESRC    4
#define DMA_TUBE_CFG_EDST    5
#define DMA_TUBE_CFG_EDIR    6
#define DMA_TUBE_CFG_ESIZE   7
#define DMA_TUBE_CFG_ECFG    0xFF
/**
 * @brief Configure a DMA tube.
 *
 * Use this function to set up a DMA transfer. The tube will be
 * disabled before being reconfigured. The transfer will have low
 * priority by default. You can choose another priority before the
 * transfer begins using dma_set_priority(). You can manage your
 * interrupt handlers for the tube using dma_attach_interrupt() and
 * dma_detach_interrupt().
 *
 * After calling dma_tube_cfg() and performing any other desired
 * configuration, start the transfer using dma_enable().
 *
 * @param dev  DMA device.
 * @param tube DMA tube to configure.
 * @param cfg  Configuration to apply to tube.
 *
 * @return DMA_TUBE_CFG_SUCCESS (0) on success, <0 on failure. On
 *         failure, returned value will be the opposite (-) of one of:
 *
 *         - DMA_TUBE_CFG_EREQ: tube doesn't work with cfg->tube_req_src
 *         - DMA_TUBE_CFG_ENDATA: cfg->tube_[src,dst]_size are
 *           incompatible with cfg->tube_nr_xfers, or cfg->tube_nr_xfers
 *           is out of bounds.
 *         - DMA_TUBE_CFG_EDEV: dev does not support cfg
 *         - DMA_TUBE_CFG_ESRC: bad cfg->tube_src
 *         - DMA_TUBE_CFG_EDST: bad cfg->tube_dst
 *         - DMA_TUBE_CFG_EDIR: dev can't transfer from cfg->tube_src to
 *           cfg->tube_dst
 *         - DMA_TUBE_CFG_ESIZE: something ended up wrong due to MSIZE/PSIZE
 *         - DMA_TUBE_CFG_ECFG: generic "something's wrong"
 *
 * @sideeffect Disables tube. May alter tube's registers even when an
 *             error occurs.
 * @see struct dma_tube_config
 * @see dma_attach_interrupt()
 * @see dma_detach_interrupt()
 * @see dma_enable()
 */
extern int dma_tube_cfg(dma_dev *dev, dma_tube tube, dma_tube_config *cfg);

/* Other tube configuration functions. You can use these if
 * dma_tube_cfg() isn't enough, or to adjust parts of an existing tube
 * configuration. */

/** DMA transfer priority. */
typedef enum dma_priority {
    DMA_PRIORITY_LOW       = 0, /**< Low priority */
    DMA_PRIORITY_MEDIUM    = 1, /**< Medium priority */
    DMA_PRIORITY_HIGH      = 2, /**< High priority */
    DMA_PRIORITY_VERY_HIGH = 3, /**< Very high priority */
} dma_priority;

/**
 * @brief Set the priority of a DMA transfer.
 *
 * You may not call this function while the tube is enabled.
 *
 * @param dev DMA device
 * @param tube DMA tube
 * @param priority priority to set.
 */
extern void dma_set_priority(dma_dev *dev, dma_tube tube,
                             dma_priority priority);

/**
 * @brief Set the number of data transfers on a DMA tube.
 *
 * You may not call this function while the tube is enabled.
 *
 * @param dev DMA device
 * @param tube Tube through which the transfer will occur.
 * @param num_transfers Number of DMA transactions to set.
 */
extern void dma_set_num_transfers(dma_dev *dev, dma_tube tube,
                                  uint16 num_transfers);

/**
 * @brief Set the base memory address where data will be read from or
 *        written to.
 *
 * You must not call this function while the tube is enabled.
 *
 * If the DMA memory size is 16 bits, the address is automatically
 * aligned to a half-word.  If the DMA memory size is 32 bits, the
 * address is aligned to a word.
 *
 * @param dev DMA Device
 * @param tube Tube whose base memory address to set.
 * @param address Memory base address to use.
 */
extern void dma_set_mem_addr(dma_dev *dev, dma_tube tube, __io void *address);

/**
 * @brief Set the base peripheral address where data will be read from
 *        or written to.
 *
 * You must not call this function while the channel is enabled.
 *
 * If the DMA peripheral size is 16 bits, the address is automatically
 * aligned to a half-word. If the DMA peripheral size is 32 bits, the
 * address is aligned to a word.
 *
 * @param dev DMA Device
 * @param tube Tube whose peripheral data register base address to set.
 * @param address Peripheral memory base address to use.
 */
extern void dma_set_per_addr(dma_dev *dev, dma_tube tube, __io void *address);

/* Interrupt handling */

/**
 * @brief Attach an interrupt to a DMA transfer.
 *
 * Interrupts are enabled using series-specific mode flags in
 * dma_tube_cfg().
 *
 * @param dev DMA device
 * @param tube Tube to attach handler to
 * @param handler Interrupt handler to call when tube interrupt fires.
 * @see dma_tube_cfg()
 * @see dma_get_irq_cause()
 * @see dma_detach_interrupt()
 */
extern void dma_attach_interrupt(dma_dev *dev, dma_tube tube,
                                 void (*handler)(void));


/**
 * @brief Detach a DMA transfer interrupt handler.
 *
 * After calling this function, the given tube's interrupts will be
 * disabled.
 *
 * @param dev DMA device
 * @param tube Tube whose handler to detach
 * @sideeffect Clears the tube's interrupt enable bits.
 * @see dma_attach_interrupt()
 */
extern void dma_detach_interrupt(dma_dev *dev, dma_tube tube);

/* Tube enable/disable */

/**
 * @brief Enable a DMA tube.
 *
 * If the tube has been properly configured, calling this function
 * allows it to start serving DMA requests.
 *
 * @param dev DMA device
 * @param tube Tube to enable
 * @see dma_tube_cfg()
 */
extern void dma_enable(dma_dev *dev, dma_tube tube);

/**
 * @brief Disable a DMA channel.
 *
 * Calling this function makes the tube stop serving DMA requests.
 *
 * @param dev DMA device
 * @param tube Tube to disable
 */
extern void dma_disable(dma_dev *dev, dma_tube tube);

/**
 * @brief Check if a DMA tube is enabled.
 * @param dev DMA device.
 * @param tube Tube to check.
 * @return 0 if the tube is disabled, >0 if it is enabled.
 */
static inline uint8 dma_is_enabled(dma_dev *dev, dma_tube tube);

/* Other conveniences */

/**
 * @brief Obtain a pointer to an individual DMA tube's registers.
 *
 * Examples:
 *
 * - On STM32F1, dma_channel_regs(DMA1, DMA_CH1)->CCR is DMA1_BASE->CCR1.
 *
 * @param dev DMA device.
 * @param tube DMA tube whose register map to obtain.
 * @return (Series-specific) tube register map.
 */
static inline dma_tube_reg_map* dma_tube_regs(dma_dev *dev, dma_tube tube);

/**
 * Encodes the reason why a DMA interrupt was called.
 * @see dma_get_irq_cause()
 */
typedef enum dma_irq_cause {
    DMA_TRANSFER_COMPLETE,      /**< Transfer is complete. */
    DMA_TRANSFER_HALF_COMPLETE, /**< Transfer is half complete. */
    DMA_TRANSFER_ERROR,         /**< Error occurred during transfer. */
    DMA_TRANSFER_DME_ERROR,     /**<
                                 * @brief Direct mode error occurred during
                                 *        transfer. */
    DMA_TRANSFER_FIFO_ERROR,    /**< FIFO error occurred during transfer. */
} dma_irq_cause;

/**
 * @brief Discover the reason why a DMA interrupt was called.
 *
 * You may only call this function within an attached interrupt
 * handler for the given channel.
 *
 * This function resets the internal DMA register state which encodes
 * the cause of the interrupt; consequently, it can only be called
 * once per interrupt handler invocation.
 *
 * @param dev DMA device
 * @param tube Tube whose interrupt is being handled.
 * @return Reason why the interrupt fired.
 * @sideeffect Clears flags in dev's interrupt status registers.
 * @see dma_attach_interrupt()
 * @see dma_irq_cause
 */
extern dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_tube tube);

/**
 * @brief Get the ISR status bits for a DMA channel.
 *
 * The bits are returned right-aligned, in the order they appear in
 * the corresponding ISR register.
 *
 * If you're trying to figure out why a DMA interrupt fired, you may
 * find dma_get_irq_cause() more convenient.
 *
 * @param dev DMA device
 * @param tube Tube whose ISR bits to return.
 * @see dma_get_irq_cause().
 */
static inline uint8 dma_get_isr_bits(dma_dev *dev, dma_tube tube);

/**
 * @brief Clear the ISR status bits for a given DMA tube.
 *
 * If you're trying to clean up after yourself in a DMA interrupt, you
 * may find dma_get_irq_cause() more convenient.
 *
 * @param dev DMA device
 * @param tube Tube whose ISR bits to clear.
 * @see dma_get_irq_cause()
 */
static inline void dma_clear_isr_bits(dma_dev *dev, dma_tube tube);

#ifdef __cplusplus
} // extern "C"
#endif

#endif

/*
dma_irq_cause dma_get_irq_cause(dma_dev *dev, dma_channel channel) {
    uint8 status_bits = dma_get_isr_bits(dev, channel);
    dma_clear_isr_bits(dev, channel);

    ASSERT(status_bits & 0x1);
    ASSERT(status_bits != 0x1);

    if (status_bits & 0x8) {
        return DMA_TRANSFER_ERROR;
    } else if (status_bits & 0x2) {
        return DMA_TRANSFER_COMPLETE;
    } else if (status_bits & 0x4) {
        return DMA_TRANSFER_HALF_COMPLETE;
    }

    dma_disable(dev, channel);
    return DMA_TRANSFER_ERROR;
}
*/

//-------------------------------------------------DSP page tab
#define Leakage 10

void enob(float data[])
{
  float temp = 0.0;

  bin_numbr = 0;
  max_magnt = 0;
  tot_magnt = 0;

  for ( int i = Leakage; i < MIRROR; i++) {
    temp = f_r[i];
    if (temp > max_magnt) {
      max_magnt = temp;
      bin_numbr = i;
      }
    }
  max_magnt = 0;
  if ((bin_numbr > Leakage) && (bin_numbr < (MIRROR - Leakage))) {
    int16_t lefts = bin_numbr - Leakage;
    int16_t right = bin_numbr + Leakage;
    for ( int i = Leakage; i < MIRROR; i++) {
      temp = f_r[i];
      if ((i > lefts) && (i < right)) {
        max_magnt += temp;
        }
      else {
        tot_magnt += (temp * temp);
        }
      }
    tot_magnt = sqrt(tot_magnt); 
    // RSS vs RMS 
    
    if (tot_magnt == 0) tot_magnt = 0.000001;
    
    sinad = (20.0 * log10(max_magnt / (tot_magnt))); 
    // -10 *log(FFT_SIZE /2); IF RMS calculated, than process GAIN must be subtructed.
    
    enobs = (sinad - 1.76) / 6.02;
    }
  else {
    sinad = 0.0;
    enobs = 0.0;
  }
}

//-------------------------------------------------FFT page tab
void rfft33(float X[], int N)
{
    /****************************************************************************
    *   rfft(float X[],int N)                                                   *
    *     A real-valued, in-place, split-radix FFT program                      *
    *     Decimation-in-time, cos/sin in second loop                            *
    *     Length is N=2**M (i.e. N must be power of 2--no error checking)       *
    *                                                                           *
    * Original Fortran code by Sorensen; published in H.V. Sorensen, D.L. Jones,*
    * M.T. Heideman, C.S. Burrus (1987) Real-valued fast fourier transform      *
    * algorithms.  IEEE Trans on Acoustics, Speech, & Signal Processing, 35,    *
    * 849-863.  Adapted to C by Bill Simpson, 1995  wsimpson@uwinnipeg.ca       *
    * --------------------------------------------------------------------------*
    * C/C++ language bugs fixing & correction:                                  *
    * 1. C-style array start address [0];                                        *
    * 2. SIN() and COS tweed factors change place;                              *
    * 3. Rev Bin - replaced by new fastest version.                             *
    * done by Anatoly Kuzmenko, 2017,  anatolyk69@gmail.com                     *
    ****************************************************************************/

  int I, I0, I1, I2, I3, I4, I5, I6, I7, I8, IS, ID;
  int J, K, M, N2, N4, N8;
  float A, A3, CC1, SS1, CC3, SS3, E, R1;
  float T1, T2, T3, T4, T5, T6;

  M = (int)(log(N) / log(2.0));             /* N=2^M */

  /* ----Length two butterflies--------------------------------------------- */
  IS = 0;
  ID = 4;
  do {
    for (I0 = IS; I0 < N; I0 += ID) {
      I1    = I0 + 1;
      R1    = X[I0];
      X[I0] = R1 + X[I1];
      X[I1] = R1 - X[I1];
    }
    IS = 2 * ID - 2;
    ID = 4 * ID;
  } while (IS < N);

  /* ----L shaped butterflies----------------------------------------------- */
  N2 = 2;
  for (K = 1; K < M; K++) {
    N2    = N2 * 2;
    N4    = N2 / 4;
    N8    = N2 / 8;
    E     = (float) 6.2831853071719586f / N2;
    IS    = 0;
    ID    = N2 * 2;
    do {
      for ( I = IS; I < N; I += ID) {
        I1 = I; // + 1;
        I2 = I1 + N4;
        I3 = I2 + N4;
        I4 = I3 + N4;
        T1 = X[I4] + X[I3];
        X[I4] = X[I4] - X[I3];
        X[I3] = X[I1] - T1;
        X[I1] = X[I1] + T1;
        if (N4 != 1) {
          I1 += N8;
          I2 += N8;
          I3 += N8;
          I4 += N8;
          T1 = (X[I3] + X[I4]) * .7071067811865475244f;
          T2 = (X[I3] - X[I4]) * .7071067811865475244f;
          X[I4] = X[I2] - T1;
          X[I3] = -X[I2] - T1;
          X[I2] = X[I1] - T2;
          X[I1] = X[I1] + T2;
        }
      }
      IS = 2 * ID - N2;
      ID = 4 * ID;
    } while ( IS < N );
    A = E;
    for ( J = 1; J < N8; J++) {
      // ydaleno
      A = (float)J * E;
      A3 = 3.0 * A;
      CC1   = cos( A);
      SS1   = sin( A);
      CC3   = cos(A3);
      SS3   = sin(A3);
      IS = 0;
      ID = 2 * N2;
      do {
        for ( I = IS; I < N; I += ID) {
          I1 = I + J;
          I2 = I1 + N4;
          I3 = I2 + N4;
          I4 = I3 + N4;
          I5 = I + N4 - J;// + 2;
          I6 = I5 + N4;
          I7 = I6 + N4;
          I8 = I7 + N4;
          T1 = X[I3] * CC1 + X[I7] * SS1;
          T2 = X[I7] * CC1 - X[I3] * SS1;
          T3 = X[I4] * CC3 + X[I8] * SS3;
          T4 = X[I8] * CC3 - X[I4] * SS3;
          T5 = T1 + T3;
          T6 = T2 + T4;
          T3 = T1 - T3;
          T4 = T2 - T4;
          T2 = X[I6] + T6;
          X[I3] = T6 - X[I6];
          X[I8] = T2;
          T2    = X[I2] - T3;
          X[I7] = -X[I2] - T3;
          X[I4] = T2;
          T1    = X[I1] + T5;
          X[I6] = X[I1] - T5;
          X[I1] = T1;
          T1    = X[I5] + T4;
          X[I5] = X[I5] - T4;
          X[I2] = T1;
        }
        IS = 2 * ID - N2;
        ID = 4 * ID;
      } while ( IS < N );
    }
  }
}

void rev_bin( float *fr, int16_t fft_n)
{
  int m, mr, nn, l;
  float tr;

  mr = 0;
  nn = fft_n - 1;

  for (m = 1; m <= nn; ++m) {
    l = fft_n;
    do {
      l >>= 1;
    } while (mr + l > nn);

    mr = (mr & (l - 1)) + l;

    if (mr <= m)
      continue;
    tr = fr[m];
    fr[m] = fr[mr];
    fr[mr] = tr;
  }
}

void get_MagnitF(float *fr)
{
  for (int  i = 1; i < MIRROR; i++) {
    float real = fr[i];
    float imag = fr[FFT_SIZE - i];
    float Magnit = (float) (sqrt((real * real) + (imag * imag)));
    fr[i] = Magnit;
  }
}

//-------------------------------------------------Print page tab
void prnt_binf( uint32 reg_data ) 
{
  Serial.print("\t");      
  for( uint32 i = 0; i < 32; i++) {  
    //    uint32_t temp = (0xFF000000 & reg_data);
    if(0x80000000 & reg_data)
      Serial.print("1");
    else
      Serial.print("0");
      if((i+1)%4 == 0) Serial.print(" ");
      if((i+1)%8 == 0) Serial.print("   ");  
      //      Serial.print((0xF0000000 & temp), BIN);       
        //    Serial.print(" ");    
          //  Serial.print((0x0F000000 & temp), BIN);       
      reg_data <<= 1;
     }
  Serial.println("\n\t");
}

void prnt_out1( uint32 *array ) 
{
  Serial.print("\n\t");      
     for ( uint32 i = 0; i < FFT_SIZE; i++)
     {  
       Serial.print(array[i]);       
       Serial.print("\t");    
       if ((i+1)%16 == 0) Serial.print("\n\t");
     }
  Serial.println("\n\t");
}

void prnt_out2( int32 *array ) 
{
  Serial.print("\n\t");      
     for ( uint32 i = 0; i < FFT_SIZE; i++)
     {
       Serial.print(array[i]);// >> 8);       
       Serial.print("\t");    
       if ((i+1)%16 == 0) Serial.print("\n\t");
     }
  Serial.println("\n\t");
}

void cmd_print_help(void) 
{
    Serial.println("\n  Listing of all available CLI Commands\n");
    Serial.println("\t\"?\" or \"h\": print this menu");
    Serial.println("\t\"x\": print out adc array");
    Serial.println("\t\"f\": print out fft array");
}

Or you can download a zip from https://drive.google.com/file/d/0Bw4tXX ... sp=sharing

I'd also be interested if someone repeat measurements with another hardware setup, and make their results publicly accessible.
Last edited by MasterT on Fri Mar 10, 2017 10:28 pm, edited 2 times in total.

User avatar
Pito
Posts: 1109
Joined: Sat Mar 26, 2016 3:26 pm
Location: Rapa Nui

Re: ADC ENOB low value.

Post by Pito » Fri Mar 10, 2017 6:12 pm

I've compiled it for Maple Mini clone.
Added

Code: Select all

  Serial.begin(115200); 
  // wait on USB
  while(!Serial.available()){};
  Serial.println("ENOB Measurement");
  ..

Code: Select all

Sketch uses 36,304 bytes (29%) of program storage space. Maximum is 122,880 bytes.
Global variables use 12,208 bytes of dynamic memory.
When I enter something it prints ENOB Measurement an the Led13 blinks 2-3Hz.
Any guide how to use it, plz?? :)
P.
Pukao Hats Cleaning Services Ltd.

MasterT
Posts: 12
Joined: Wed Mar 08, 2017 4:17 am

Re: ADC ENOB low value.

Post by MasterT » Fri Mar 10, 2017 6:26 pm

There is simple "arduino style" user interface, I thought it 'd easy to understand from the main listing.
Commands "x" "f" "e" - commented, just print into IDE serial monitor text field "send line" one letter and press Enter
"d" - control data set, to verify fft/ math., after you switch to d enter x, f or e, and don't forget to switch back sending d again.
There is also register modification interface, a r w letters standing for address, read, write. Format like a400C0041 - is to set a base address,
r - to read from, a w11110001110001110001111 - to write to that address register on "the fly". Leading "0" can be ommited for w, so w1 is setting only last bit and clear everything else

User avatar
Pito
Posts: 1109
Joined: Sat Mar 26, 2016 3:26 pm
Location: Rapa Nui

Re: ADC ENOB low value.

Post by Pito » Fri Mar 10, 2017 6:40 pm

The code is quite complex - I had a look.
May I suggest to you to create a "version 2 standard" where:

1. you connect a clean <500ohm 1kHz (or 100kHz) sinus signal (centered by say 2x 10k and coupled via a 100uF capacitor) at the pin XYZ
2. you run the sketch with almost no intervention
3. after a while you get printed the results.

In this way all interested could easily provide a comparison.

For example, I got the 103ZET6 board, which has got LC filters at analog ref, analog gnd.. I plan to wire an 2.5ref to its refinput.
So people may easily compare.
Pukao Hats Cleaning Services Ltd.

MasterT
Posts: 12
Joined: Wed Mar 08, 2017 4:17 am

Re: ADC ENOB low value.

Post by MasterT » Tue Mar 14, 2017 4:12 pm

Pito wrote:The code is quite complex - I had a look.
May I suggest to you to create a "version 2 standard" where:

1. you connect a clean <500ohm 1kHz (or 100kHz) sinus signal (centered by say 2x 10k and coupled via a 100uF capacitor) at the pin XYZ
2. you run the sketch with almost no intervention
3. after a while you get printed the results.

In this way all interested could easily provide a comparison.

For example, I got the 103ZET6 board, which has got LC filters at analog ref, analog gnd.. I plan to wire an 2.5ref to its refinput.
So people may easily compare.
O'K, I have to re-evaluate my signal source, because data sheet for DSO says DDS part is only 12-bits, with THD -50 dBc , and it looks not really well to measure 12-bits ADC ENOB.
So, I run arduino Due with WM8731 in 24-bits sampling system configuration, loading the same code to measure ENOB, arduino DUE confirms that my sine source (DDS of the Hantek DSO) not worse than 12.5 bits. Probably, oversampling is doing great work to bring up 12-bits DAC to about 14, but it's different story. I don't biased input, as my DDS can bias output directly, applying with sine wave over.
I did not follow your advise to use couple resistor to bias input, because board has no place to connect this resistors, and using external breadboard significantly change measurement results, actually any single 5 cm wire acts as antenna, it's length, proximity to ground wire, etc, affecting the results

So circuitry is absent, piece of wire from DDS output to A0, and ground of course. Voltage is adjasted to +-2000 peak-to-peak as close as possible, leaving +-47 as head room for noise.
Now, results obtain with Generic stm32f103C board for comparison:
Peak number: 69 magnitude: 1023759.31 Total: 2478.7614746094 SINAD: 52.319 ENOB: 8.399
Peak number: 69 magnitude: 1023118.25 Total: 2447.4824218750 SINAD: 52.424 ENOB: 8.416
Peak number: 69 magnitude: 1023132.50 Total: 2800.3054199219 SINAD: 51.255 ENOB: 8.222
Peak number: 69 magnitude: 1022922.31 Total: 2527.8261718750 SINAD: 52.142 ENOB: 8.369
Peak number: 69 magnitude: 1024296.75 Total: 2535.1118164063 SINAD: 52.129 ENOB: 8.367

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest