5

My original question was if there is a library that covers the internal comparator and differential and gain modes for the Arduino Uno, Mega 2560, Leonardo, Due, Zero, M0.
The answer is obvious: "No". To my surprise there is not even a start to make it and neither a collaboration to gather usefull code.

If you know pieces of code (Arduino compatible and documentation for which pins) for the analog comparator and gain, then please tell me about it, and I put them in this list.

9
  • Please pick a board, as the all have wildly differing processors. Or tell us what you want to achieve if you are actually asking which board to buy.
    – Gerben
    Jun 6, 2017 at 17:31
  • @Gerben, But I don't want to pick a board. I don't want to have a single solution for one specific project, I would like to know that there is something that I might use when needed. Next time I have a project with Arduino with one of the more common AVR or SAM boards then I would like to use a library to use the comparator or gain features. If I try really hard, then I could get some of the ATmega32U4 extra analog features working, but then I would have to do it all over again when using a Zero. For example the EnableInterrupt library is there and ready to be used when needed.
    – Jot
    Jun 6, 2017 at 18:24
  • 1
    For one, the UNOs processor doesn't have the option to set a gain. So that board is already incompatible. The different processors have different gain values that you can select. I'm not sure about any other incompatibilities, as I've never used the comparator. It's very hard to create a library that bridges these incompatibilities, without loosing functionality (lowest common denominator).
    – Gerben
    Jun 6, 2017 at 18:56
  • My intention is to find a way to use every possible extra analog feature. Not to find the common denominator and end up with nothing.
    – Jot
    Jun 6, 2017 at 22:29
  • Then you need to use the datasheets. You can have library to use simple abstractions, or you can have every possible features, but they are different for every processor and hypotetical library covering that all would be much more complicated, than those datasheets, as it would need to doo everything in each datasheet and also translation to some API and solve all cases, when user tries feature A from uP AA on uP BB, which does not have mentioned feature. And solving all those cases would cost a lot of cycles, so it would not be able use the uP to full extend anyway (for being too slow)
    – gilhad
    Jun 10, 2017 at 1:14

4 Answers 4

3
+200

I would recommend you to check Nick Gammon's example on using the analog comparator by means of configuring the relevant flag bits of registers in the ATmega328. Sample code from the link, by Nick:

volatile boolean triggered;

ISR (ANALOG_COMP_vect)
  {
  triggered = true;
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ("Started.");
  ADCSRB = 0;           // (Disable) ACME: Analog Comparator Multiplexer Enable
  ACSR =  bit (ACI)     // (Clear) Analog Comparator Interrupt Flag
        | bit (ACIE)    // Analog Comparator Interrupt Enable
        | bit (ACIS1);  // ACIS1, ACIS0: Analog Comparator Interrupt Mode Select (trigger on falling edge)
   }  // end of setup

void loop ()
  {
  if (triggered)
    {
    Serial.println ("Triggered!"); 
    triggered = false;
    }

  }  // end of loop
1
  • 1
    That was already in the analogComp library. But I didn't know that Nick Gammon wrote a page about it. Therefor you get the +200 bounty. At this moment I'm looking at the Atmel Studio 7 API for the ATSAMD21.
    – Jot
    Jun 23, 2017 at 16:29
2
+400

You seem to be interested mainly in ATSAMD21. There is an application note from Atmel, AT11480: Analog Comparator Application Examples, which may be of interest for you.

From the Getting started section (3.1, page 8):

The AC [Analog Comparator] example project has to be opened from New Example Project option in Atmel Studio. Using the option Atmel Studio → File → New → Example Project will open the New Example Project from ASF or Extensions window. The project named Analog Comparator Examples – SAM D21 Xplained Pro has to be opened.

There you'll find example code that might be useful for you, as it will show how to configure and manage the registers involved in using the analog comparator of the ATSAMD21. Namely, the example code is related to the following example applications:

  1. Level crossing detector.
  2. Window mode operation.
  3. Preventing false spike detection.
  4. Gray signal frequency measurement.
  5. SleepWalking with analog comparator.

If you look into the first case (level crossing detector) for guidelines for where to start from, you can see that the application example goes through the following steps:

  1. Enable alternate function H (AC/CMP[0]) for pin PA12 so that the comparator 0 output is directly routed to the pin.
  2. Select GCLK Generator 0 as GCLK source for AC module. The GCLK Generator 0 is clocked from internal OSC8M oscillator whose output is set to 8MHz.
  3. Select AIN[0] pin (PA04) as positive input and internal voltage scaler output as negative input for comparator 0. The voltage scaler register is set to a value of 9 corresponding to a voltage output of 0.5156V.
  4. Set comparator 0 in continuous comparison mode with hysteresis and filter mode disabled.
  5. Enable comparator 0 output to be routed to I/O pin and select interrupt mode as toggle so that comparator output toggles when positive input crosses the negative input (both during rising and falling).
  6. Enable comparator 0 and then enable the AC module.

You could get from that example code some ideas about the registers to be configured and managed, and then try to use that knowledge for creating (or even reusing) some code within the Arduino IDE.

Hope it helps...

1
  • Thank you. That is the comparator. What about the differential mode with gain (and offset). I would like to keep using the Arduino, therefor I would like to be able to use the Arduino definitions instead of the Atmel Studio API calls.
    – Jot
    Jun 30, 2017 at 14:26
0

What you are trying to do can be easily done via simple programming of the channels bits in the ADV module - the datasheet is your best friend here.

So if you want to take this one, take a look at the datasheet and that's all there is to it.

Happy to provide a sample piece to get you going.

13
  • A "common" problem is with a simple pressure sensor that has a wheatstone output with a offset. I would need a differential input, preferably with offset compensation and a lot of gain. For example the M0+ (ATSAM21G18) in the Arduino M0 or Zero can probably not do all of that, but should be able to get a reasonable resolution, but I don't know where to start microchip.com/wwwproducts/en/ATSAMD21G18 and I don't understand this: electronics.stackexchange.com/questions/275940/…
    – Jot
    Jun 16, 2017 at 19:48
  • Yes. A differential acc would be the way to go. In ave, the channel selection buts control the inputs as well as gains. So the best place to start would be to look at the datasheet and write your own analog Read() or to customize the stock analog read.
    – dannyf
    Jun 16, 2017 at 20:00
  • > but I don't know where to start. the device datasheet is generally a great place to start. in the case of certain avrs, the adc can be configured to have gain and different inputs by selecting the channel bits. You can also use an external instrumentation amplifier to convert different inputs into single-ended input for your mcu that doesn't have different adc capabilities.
    – dannyf
    Jun 30, 2017 at 15:51
  • For that approach, you can check out an experiement I did using the differential input on ATtiny85 to build a simple milliohm meter here: wordpress.com/post/dannyelectronics.wordpress.com/262
    – dannyf
    Jun 30, 2017 at 15:54
  • another alternative is to use external adc with differential inputs. I also built a millieohm meter with the super cheap HX711. You may find it helpful as well: wordpress.com/post/dannyelectronics.wordpress.com/219
    – dannyf
    Jun 30, 2017 at 15:55
0

I tried to find further information that would help you about the ATSAMD21, but I only managed to find the source code for the definitions of the Analog Comparator Controller (ACC), the Power Management Controller (PMC), and some example code as well. The example code follows, to avoid this being a link-only answer:

 #include "asf.h"
 #include "stdio_serial.h"
 #include "conf_board.h"
 #include "conf_clock.h"

 #define VOLT_REF   (3300)

 #define MAX_DIGITAL (4095)

 #define DACC_CHANNEL_0 0

 #define DACC_ANALOG_CONTROL (DACC_ACR_IBCTLCH0(0x02) \
                           | DACC_ACR_IBCTLCH1(0x02) \
                           | DACC_ACR_IBCTLDACCORE(0x01))

 #define ADC_CLOCK 6400000

 #define ADC_STARTUP_TIME_SETTING 0x8u


 #define ADC_TRACK_SETTING 0x0u

 #define ADC_TRANSFER_SETTING 0x1u

 #define ACC_SELPLUS_AD5  0x5u
 #define ACC_SELMINUS_DAC0  0x2u
 #define ACC_EDGETYP_ANY  0x2u
 #define ACC_INVERT_NO 0x0u

 #define STRING_EOL    "\r"
 #define STRING_HEADER "-- ACC IRQ Example  --\r\n" \
         "-- "BOARD_NAME" --\r\n" \
         "-- Compiled: "__DATE__" "__TIME__" --"STRING_EOL

 void ACC_Handler(void)
 {
     uint32_t ul_status;

     ul_status = acc_get_interrupt_status(ACC);

     /* Compare Output Interrupt */
     if ((ul_status & ACC_ISR_CE) == ACC_ISR_CE) {

         if (acc_get_comparison_result(ACC)) {
             puts("-ISR- Voltage Comparison Result: AD5 > DAC0\r");
         } else {
             puts("-ISR- Voltage Comparison Result: AD5 < DAC0\r");
         }
     }
 }

 static void configure_console(void)
 {
     const usart_serial_options_t uart_serial_options = {
         .baudrate = CONF_UART_BAUDRATE,
         .paritytype = CONF_UART_PARITY
     };

     /* Configure console UART. */
     sysclk_enable_peripheral_clock(CONSOLE_UART_ID);
     stdio_serial_init(CONF_UART, &uart_serial_options);
 }

 static void dsplay_menu(void)
 {
     puts("-- Menu Choices for this example--\n\r"
             "  s: Set new DAC0 output voltage.\n\r"
             "  v: Get voltage on potentiometer.\n\r"
             "  m: Display this menu again.\r");
 }

 static int16_t get_input_voltage(void)
 {
     uint8_t i = 0, uc_key;
     int16_t us_value = 0;
     int8_t c_length = 0;
     int8_t ac_str_temp[5] = { 0 };

     while (1) {
         while (uart_read(CONSOLE_UART, &uc_key)) {
         }
         if (uc_key == '\n' || uc_key == '\r') {
             puts("\r");
             break;
         }

         if ('0' <= uc_key && '9' >= uc_key) {
             printf("%c", uc_key);
             ac_str_temp[i++] = uc_key;

             if (i >= 4)
                 break;
         }
     }

     ac_str_temp[i] = '\0';
     /* Input string length */
     c_length = i;
     us_value = 0;

     /* Convert string to integer */
     for (i = 0; i < 4; i++) {
         if (ac_str_temp[i] != '0') {
             switch (c_length - i - 1) {
             case 0:
                 us_value += (ac_str_temp[i] - '0');
                 break;
             case 1:
                 us_value += (ac_str_temp[i] - '0') * 10;
                 break;
             case 2:
                 us_value += (ac_str_temp[i] - '0') * 100;
                 break;
             case 3:
                 us_value += (ac_str_temp[i] - '0') * 1000;
                 break;
             }
         }
     }

     if (us_value > (5 * VOLT_REF / 6) || us_value < (1 * VOLT_REF / 6)) {
         return -1;
     }

     return us_value;
 }

 int main(void)
 {
     uint8_t uc_key;
     int16_t s_volt = 0;
     uint32_t ul_value = 0;
     volatile uint32_t ul_status = 0x0;
     int32_t l_volt_dac0 = 0;

     /* Initialize the system */
     sysclk_init();
     board_init();

     /* Initialize debug console */
     configure_console();

     /* Output example information */
     puts(STRING_HEADER);

     /* Initialize DACC */
     /* Enable clock for DACC */
     pmc_enable_periph_clk(ID_DACC);
     /* Reset DACC registers */
     dacc_reset(DACC);
     /* External trigger mode disabled. DACC in free running mode. */
     dacc_disable_trigger(DACC);
     /* Half word transfer mode */
     dacc_set_transfer_mode(DACC, 0);
     /* Power save:
      * sleep mode  - 0 (disabled)
      * fast wakeup - 0 (disabled)
      */
     dacc_set_power_save(DACC, 0, 0);
     /* Timing:
      * refresh        - 0x08 (1024*8 dacc clocks)
      * max speed mode -    0 (disabled)
      * startup time   - 0xf (960 dacc clocks)
      */
     dacc_set_timing(DACC, 0x08, 0, 0xf);
     /* Disable TAG and select output channel DACC_CHANNEL */
     dacc_set_channel_selection(DACC, DACC_CHANNEL_0);
     /* Enable output channel DACC_CHANNEL */
     dacc_enable_channel(DACC, DACC_CHANNEL_0);
     /* Setup analog current */
     dacc_set_analog_control(DACC, DACC_ANALOG_CONTROL);

     /* Set DAC0 output at ADVREF/2. The DAC formula is:
      *
      * (5/6 * VOLT_REF) - (1/6 * VOLT_REF)     volt - (1/6 * VOLT_REF)
      * ----------------------------------- = --------------------------
      *              MAX_DIGITAL                       digit
      *
      * Here, digit = MAX_DIGITAL/2
      */
     dacc_write_conversion_data(DACC, MAX_DIGITAL / 2);
     l_volt_dac0 = (MAX_DIGITAL / 2) * (2 * VOLT_REF / 3) / MAX_DIGITAL +
             VOLT_REF / 6;

     /* Initialize ADC */
     /* Enable clock for ADC */
     pmc_enable_periph_clk(ID_ADC);
     /*
      * Formula: ADCClock = MCK / ( (PRESCAL+1) * 2 )
      * For example, MCK = 64MHZ, PRESCAL = 4, then:
      *     ADCClock = 64 / ((4+1) * 2) = 6.4MHz;
      */
     adc_init(ADC, sysclk_get_cpu_hz(), ADC_CLOCK, ADC_STARTUP_TIME_SETTING);

     /* Formula:
      *     Startup  Time = startup value / ADCClock
      *     Transfer Time = (TRANSFER * 2 + 3) / ADCClock
      *     Tracking Time = (TRACKTIM + 1) / ADCClock
      *     Settling Time = settling value / ADCClock
      * For example, ADC clock = 6MHz (166.7 ns)
      *     Startup time = 512 / 6MHz = 85.3 us
      *     Transfer Time = (1 * 2 + 3) / 6MHz = 833.3 ns
      *     Tracking Time = (0 + 1) / 6MHz = 166.7 ns
      *     Settling Time = 3 / 6MHz = 500 ns
      */
     /* Set ADC timing */
     adc_configure_timing(ADC, ADC_TRACK_SETTING, ADC_SETTLING_TIME_3,
             ADC_TRANSFER_SETTING);

     /* Channel 5 has to be compared */
     adc_enable_channel(ADC, ADC_CHANNEL_5);

     /* Enable clock for ACC */
     pmc_enable_periph_clk(ID_ACC);
     /* Initialize ACC */
     acc_init(ACC, ACC_SELPLUS_AD5, ACC_SELMINUS_DAC0,
             ACC_EDGETYP_ANY, ACC_INVERT_NO);

     /* Enable ACC interrupt */
     NVIC_EnableIRQ(ACC_IRQn);

     /* Enable */
     acc_enable_interrupt(ACC);

     dsplay_menu();

     while (1) {
         while (uart_read(CONSOLE_UART, &uc_key)) {
         }

         printf("input: %c\r\n", uc_key);

         switch (uc_key) {
         case 's':
         case 'S':
             printf("Input DAC0 output voltage (%d~%d mv): ",
                     (VOLT_REF / 6), (VOLT_REF * 5 / 6));
             s_volt = get_input_voltage();
             puts("\r");

             if (s_volt > 0) {
                 l_volt_dac0 = s_volt;
                 /* The DAC formula is:
                  *
                  * (5/6 * VOLT_REF) - (1/6 * VOLT_REF)     volt - (1/6 * VOLT_REF)
                  * ----------------------------------- = --------------------------
                  *              MAX_DIGITAL                       digit
                  *
                  */
                 ul_value = ((s_volt - (VOLT_REF / 6))
                     * (MAX_DIGITAL * 6) / 4) / VOLT_REF;
                 dacc_write_conversion_data(DACC, ul_value);
                 puts("-I- Set ok\r");
             } else {
                 puts("-I- Input voltage is invalid\r");
             }
             break;
         case 'v':
         case 'V':
             /* Start conversion */
             adc_start(ADC);
             ul_status = adc_get_status(ADC);
             while ((ul_status & ADC_ISR_EOC5) != ADC_ISR_EOC5) {
                 ul_status = adc_get_status(ADC);
             }
             /* Conversion is done */
             ul_value = adc_get_channel_value(ADC, ADC_CHANNEL_5);

             /*
              * Convert ADC sample data to voltage value:
              * voltage value = (sample data / max. resolution) * reference voltage
              */
             s_volt = (ul_value * VOLT_REF) / MAX_DIGITAL;
             printf("-I- Voltage on potentiometer(AD5) is %d mv\n\r", s_volt);
             printf("-I- Voltage on DAC0 is %ld mv \n\r", (long)l_volt_dac0);
             break;

         case 'm':
         case 'M':
             dsplay_menu();
             break;
         }
     }
 }

I'm afraid you'll have to study how to configure the registers, taking the datasheet and, as a guideline, the ACC and PMC definitions linked above.

2
  • Thanks, but that is the ASF framework, and not how most Arduino code is (but I'm not sure). The Arduino has already all the bits defined for the differential and gain modes, for example "ADC_INPUTCTRL_GAIN_16X", but that might be the ASF framework as well (or not). I'm still like a rabbit staring into headlights.
    – Jot
    Jul 7, 2017 at 16:02
  • I can feel and understand your frustration. I've searched but haven't been able to find exactly what you need. At this point I can only recommended that you take Nick Gammon's example code and modify it to configure instead the specific registers of the ATSAMD21 as per the linked ASF examples and definitions. Sorry... Jul 7, 2017 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.