text: //*****************************************************************************
  //
  //! @file adc_vbatt.c
  //!
  //! @brief ADC VBATT voltage divider, BATT load, and temperature reading example.
  //!
  //! This example initializes the ADC, and a timer. Two times per second it
  //! reads the VBATT voltage divider and temperature sensor and prints them.
  //! It monitors button 0 and if pressed, it turns on the BATT LOAD resistor.
  //! One should monitor MCU current to see when the load is on or off.
  //!
  //! Printing takes place over the ITM at 1M Baud.
  //
  //*****************************************************************************
  
  //*****************************************************************************
  //
  // Copyright (c) 2019, Ambiq Micro
  // All rights reserved.
  // 
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are met:
  // 
  // 1. Redistributions of source code must retain the above copyright notice,
  // this list of conditions and the following disclaimer.
  // 
  // 2. Redistributions in binary form must reproduce the above copyright
  // notice, this list of conditions and the following disclaimer in the
  // documentation and/or other materials provided with the distribution.
  // 
  // 3. Neither the name of the copyright holder nor the names of its
  // contributors may be used to endorse or promote products derived from this
  // software without specific prior written permission.
  // 
  // Third party software included in this distribution is subject to the
  // additional license terms as defined in the /docs/licenses directory.
  // 
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  // POSSIBILITY OF SUCH DAMAGE.
  //
  // This is part of revision v2.2.0-7-g63f7c2ba1 of the AmbiqSuite Development Package.
  //
  //*****************************************************************************
  
  #include "am_mcu_apollo.h"
  #include "am_bsp.h"
  #include "am_util.h"
  
  //*****************************************************************************
  //
  // Global Variables
  //
  //*****************************************************************************
  // Sample Count semaphore from ADC ISR to base level.
  uint32_t g_ui32SampleCount;
  
  // ADC code for voltage divider from ADC ISR to base level.
  uint16_t g_ui16ADCVDD_code;
  
  // ADC code for temperature sensor from ADC ISR to base level.
  uint16_t g_ui16ADCTEMP_code;
  
  //*****************************************************************************
  //
  // ADC Configuration
  //
  //*****************************************************************************
  am_hal_adc_config_t g_sADC_CfgA =
  {
  #if AM_PART_APOLLO2
      //
      // Select the ADC Clock source.
      //
      .ui32Clock = AM_HAL_ADC_CLOCK_DIV2,
  
      //
      // Select the ADC trigger source using a trigger source macro.
      //
      .ui32TriggerConfig = AM_HAL_ADC_TRIGGER_SOFT,
  
      //
      // Select the ADC reference voltage.
      //
      .ui32Reference = AM_HAL_ADC_REF_INT_1P5,
      .ui32ClockMode = AM_HAL_ADC_CK_LOW_POWER,
  
      //
      // Choose the power mode for the ADC's idle state.
      //
      .ui32PowerMode = AM_HAL_ADC_LPMODE_1,
  
      //
      // Enable repeating samples using Timer3A.
      //
      .ui32Repeat = AM_HAL_ADC_REPEAT
  #else
      // Select the ADC Clock source using one of the clock source macros.
      AM_HAL_ADC_CLOCK_1_5MHZ,
  
      // Select the ADC trigger source using a trigger source macro.
      AM_HAL_ADC_TRIGGER_SOFT,
  
      // Use a macro to select the ADC reference voltage.
      AM_HAL_ADC_REF_INT,
  
      // Use a macro to choose a maximum sample rate setting.
      AM_HAL_ADC_MODE_1MSPS,
  
      // Use a macro to choose the power mode for the ADC's idle state.
      AM_HAL_ADC_LPMODE_2,
  
      // Use the Repeat macro to enable repeating samples using Timer3A.
      AM_HAL_ADC_REPEAT,
  
      // Power Off the temp sensor.
      AM_HAL_ADC_PON_TEMP,
  
      // Set the ADC window limits using the window limit macro.
      AM_HAL_ADC_WINDOW(768, 256) // arbitrary window setting, not used here.
  #endif
  };
  
  //*****************************************************************************
  //
  // Timer configurations.
  //
  //*****************************************************************************
  am_hal_ctimer_config_t g_sTimer3 =
  {
      // do not link A and B together to make a long 32-bit counter.
      0,
  
      // Set up timer 3A to drive the ADC
      (AM_HAL_CTIMER_FN_PWM_REPEAT |
       AM_HAL_CTIMER_LFRC_32HZ),
  
      // Timer 3B is not used in this example.
      0,
  };
  
  //*****************************************************************************
  //
  // ADC Interrupt Service Routine (ISR)
  //
  //*****************************************************************************
  void
  am_adc_isr(void)
  {
  #if AM_PART_APOLLO2
      uint32_t ui32fifodata;
      volatile uint32_t ui32Status;
  
      //
      // Clear timer 3 interrupt.
      //
      ui32Status = am_hal_adc_int_status_get(true);
      am_hal_adc_int_clear(ui32Status);
  
      //
      // Toggle LED 3.
      //
      am_devices_led_toggle(am_bsp_psLEDs, 3);
  
      //
      // Keep grabbing value from the ADC FIFO until it goes empty.
      //
      while ( AM_HAL_ADC_FIFO_COUNT(am_hal_adc_fifo_peek()) )
      {
          ui32fifodata = am_hal_adc_fifo_pop();
  
          //
          // Select which one of the two enabled slots is here right now.
          //
          if ( AM_HAL_ADC_FIFO_SLOT(ui32fifodata) == 5)
          {
              //
              // Just grab the ADC code for the battery voltage divider.
              //
              g_ui16ADCVDD_code = AM_HAL_ADC_FIFO_FULL_SAMPLE(ui32fifodata);
          }
          else
          {
              //
              // Just grab the ADC code for the temperature sensor.
              // We need the integer part in the low 16-bits.
              //
              g_ui16ADCTEMP_code = AM_HAL_ADC_FIFO_FULL_SAMPLE(ui32fifodata) & 0xFFC0;
          }
      }
  #else
      am_hal_adc_fifo_read_t fifo_info;
  
      //
      // Clear ADC Interrupt (write to clear).
      //
      AM_REGn(CTIMER, 0, INTCLR) = AM_REG_CTIMER_INTCLR_CTMRA0INT_M;
  
      //
      // Toggle LED 3.
      //
      am_devices_led_toggle(am_bsp_psLEDs, 3);
  
      //
      // Keep grabbing value from the ADC FIFO until it goes empty.
      //
      while (am_hal_adc_fifo_read(&fifo_info))
      {
          //
          // Select which one of the two enabled slots is here right now.
          //
          if (fifo_info.ui8Slot == 5)
          {
              //
              // Just grab the ADC code for the battery voltage divider.
              //
              g_ui16ADCVDD_code = fifo_info.ui16Data;
          }
          else
          {
              //
              // Just grab the ADC code for the temperature sensor.
              //
              g_ui16ADCTEMP_code = fifo_info.ui16Data;
          }
      }
  #endif // AM_PART_APOLLO2
  
      //
      // Signal interrupt arrival to base level.
      //
      g_ui32SampleCount++;
  }
  
  //*****************************************************************************
  //
  // ADC INIT Function
  //
  //*****************************************************************************
  void
  adc_init(void)
  {
  #if AM_PART_APOLLO
      //
      // We MUST turn on band gap to use the Temp Sensor.
      // The ADC hardware in mode 2 will cycle the power to the bandgap
      // automatically.
      //
      am_hal_mcuctrl_bandgap_enable();
  #endif
  
  #if AM_PART_APOLLO2
      //
      // Enable the ADC power domain.
      //
      am_hal_pwrctrl_periph_enable(AM_HAL_PWRCTRL_ADC);
  #endif
  
      //
      // Configure the ADC.
      //
      am_hal_adc_config(&g_sADC_CfgA);
  
      //
      // Initialize the slot control registers.
      //
      am_hal_adc_slot_config(0, 0); // unused slot
      am_hal_adc_slot_config(1, 0); // unused slot
      am_hal_adc_slot_config(2, 0); // unused slot
      am_hal_adc_slot_config(3, 0); // unused slot
      am_hal_adc_slot_config(4, 0); // unused slot
      am_hal_adc_slot_config(5, (AM_HAL_ADC_SLOT_AVG_1        |
                                 AM_HAL_ADC_SLOT_CHSEL_VBATT  |
                                 AM_HAL_ADC_SLOT_WINDOW_EN    |
                                 AM_HAL_ADC_SLOT_ENABLE));
  
      am_hal_adc_slot_config(6, 0); // unused slot
  #if AM_PART_APOLLO
      am_hal_adc_slot_config(7, (AM_HAL_ADC_SLOT_AVG_1            |
                                 AM_HAL_ADC_SLOT_CHSEL_TEMP       |
                                 AM_HAL_ADC_SLOT_WINDOW_EN        |
                                 AM_HAL_ADC_SLOT_ENABLE));
  #endif
  #if AM_PART_APOLLO2
      am_hal_adc_slot_config(7, (AM_HAL_ADC_SLOT_AVG_1            |
                                 AM_HAL_ADC_SLOT_CHSEL_TEMP       |
                                 AM_HAL_ADC_SLOT_10BIT            |
                                 AM_HAL_ADC_SLOT_WINDOW_EN        |
                                 AM_HAL_ADC_SLOT_ENABLE));
  #endif
  
      //
      // Enable the ADC.
      //
      am_hal_adc_enable();
  }
  
  //*****************************************************************************
  //
  // Enable the ADC INIT TIMER 3A function and set for 0.5 second period.
  //
  //*****************************************************************************
  void
  adc_init_timer3A(void)
  {
      uint32_t ui32Period = 2000; // Set for 2 second (2000ms) period
  
      //
      // LFRC has to be turned on for this example because we are running this
      // timer off of the LFRC.
      //
      am_hal_clkgen_osc_start(AM_HAL_CLKGEN_OSC_LFRC);
  
      //
      // Set up timer 3A so start by clearing it.
      //
      am_hal_ctimer_clear(3, AM_HAL_CTIMER_TIMERA);
  
      //
      // Configure the timer to count 32Hz LFRC clocks but don't start it yet.
      //
      am_hal_ctimer_config(3, &g_sTimer3);
  
      //
      // Compute CMPR value needed for desired period based on a 32HZ clock.
      //
      ui32Period = ui32Period * 32 / 1000;
      am_hal_ctimer_period_set(3, AM_HAL_CTIMER_TIMERA,
                               ui32Period, (ui32Period >> 1));
  
      //
      // Enable the timer output "pin". This refers to the pin as seen from
      // inside the timer. The actual GPIO pin is neither enabled nor driven.
      //
      am_hal_ctimer_pin_enable(3, AM_HAL_CTIMER_TIMERA);
  
      //
      // Set up timer 3A as the trigger source for the ADC.
      //
      am_hal_ctimer_adc_trigger_enable();
  
      //
      // Start timer 3A.
      //
      am_hal_ctimer_start(3, AM_HAL_CTIMER_TIMERA);
  }
  
  //*****************************************************************************
  //
  // Main function.
  //
  //*****************************************************************************
  int
  main(void)
  {
      bool  bMeasured;
      float fTempF;
      int32_t i32BaseLevelCount;
      const float fReferenceVoltage = 1.5;
      float fVBATT;
      float fADCTempVolts;
      float fADCTempDegreesC;
      float fTemp, fVoltage, fOffset;
  
      //
      // Set the clock frequency.
      //
      am_hal_clkgen_sysclk_select(AM_HAL_CLKGEN_SYSCLK_MAX);
  
      //
      // Set the default cache configuration
      //
      am_hal_cachectrl_enable(&am_hal_cachectrl_defaults);
  
      //
      // Configure the board for low power operation.
      //
      am_bsp_low_power_init();
  
      //
      // Initialize device drivers for the LEDs on the board.
      //
      am_devices_led_array_init(am_bsp_psLEDs, AM_BSP_NUM_LEDS);
  
      //
      // Configure the Button 0 pin as a simple GPIO input.
      //
      am_hal_gpio_pin_config(AM_BSP_GPIO_BUTTON0, AM_HAL_PIN_INPUT);
  
      //
      // Initialize the SWO GPIO pin.
      //
      am_bsp_pin_enable(ITM_SWO);
  
      //
      // Enable the ITM.
      //
      am_hal_itm_enable();
  
      //
      // Enable debug printf messages using ITM on SWO pin
      //
      am_bsp_debug_printf_enable();
  
      //
      // Initialize the printf interface for ITM/SWO output.
      //
      am_util_stdio_printf_init((am_util_stdio_print_char_t) am_bsp_itm_string_print);
  
      //
      // Clear the terminal screen, and print a quick message to show that we're
      // alive.
      //
      am_util_stdio_terminal_clear();
      am_util_stdio_printf("ADC VBATT and Temperature Sensing Example.\n");
  
      //
      // Enable floating point.
      //
      am_hal_sysctrl_fpu_enable();
      am_hal_sysctrl_fpu_stacking_enable(true);
  
      //
      // Initialize CTIMER 3A to trigger the ADC every 0.5 seconds.
      //
      adc_init_timer3A();
  
      //
      // Print out ctimer initial register state.
      //
      am_util_stdio_printf("\n");
      am_util_stdio_printf("CTIMER3=0x%08X @ 0x%08X\n",
                           AM_REGVAL(REG_CTIMER_BASEADDR + AM_REG_CTIMER_TMR3_O),
                           REG_CTIMER_BASEADDR + AM_REG_CTIMER_TMR3_O);
      am_util_stdio_printf("CTIMER3=0x%08X @ 0x%08X\n",
                           AM_REGVAL(REG_CTIMER_BASEADDR + AM_REG_CTIMER_CMPRA3_O),
                           REG_CTIMER_BASEADDR + AM_REG_CTIMER_CMPRA3_O);
      am_util_stdio_printf("CTIMER3=0x%08X @ 0x%08X\n",
                           AM_REGVAL(REG_CTIMER_BASEADDR + AM_REG_CTIMER_CMPRB3_O),
                           REG_CTIMER_BASEADDR + AM_REG_CTIMER_CMPRB3_O);
      am_util_stdio_printf("CTIMER3=0x%08X @ 0x%08X\n",
                           AM_REGVAL(REG_CTIMER_BASEADDR + AM_REG_CTIMER_CTRL3_O),
                           REG_CTIMER_BASEADDR + AM_REG_CTIMER_CTRL3_O);
  
      //
      // Initialize the ADC.
      //
      adc_init();
  
      //
      // Print out ADC initial register state.
      //
      am_util_stdio_printf("\n");
      am_util_stdio_printf("ADC REGISTERS @ 0x%08X\n", (uint32_t)REG_ADC_BASEADDR);
      am_util_stdio_printf("ADC CFG   = 0x%08X\n", AM_REG(ADC, CFG));
      am_util_stdio_printf("ADC SLOT0 = 0x%08X\n", AM_REG(ADC, SL0CFG));
      am_util_stdio_printf("ADC SLOT1 = 0x%08X\n", AM_REG(ADC, SL1CFG));
      am_util_stdio_printf("ADC SLOT2 = 0x%08X\n", AM_REG(ADC, SL2CFG));
      am_util_stdio_printf("ADC SLOT3 = 0x%08X\n", AM_REG(ADC, SL3CFG));
      am_util_stdio_printf("ADC SLOT4 = 0x%08X\n", AM_REG(ADC, SL4CFG));
      am_util_stdio_printf("ADC SLOT5 = 0x%08X\n", AM_REG(ADC, SL5CFG));
      am_util_stdio_printf("ADC SLOT6 = 0x%08X\n", AM_REG(ADC, SL6CFG));
      am_util_stdio_printf("ADC SLOT7 = 0x%08X\n", AM_REG(ADC, SL7CFG));
  #if AM_PART_APOLLO
      am_util_stdio_printf("ADC WLIM  = 0x%08X\n", AM_REG(ADC, WLIM));
  #endif
  
      //
      // Print out the temperature trim values as recorded in OTP.
      //
      bMeasured = am_hal_adc_temp_trims_get(&fTemp, &fVoltage, &fOffset);
      am_util_stdio_printf("\n");
      am_util_stdio_printf("TRIMMED TEMP    = %.3f\n", fTemp);
      am_util_stdio_printf("TRIMMED VOLTAGE = %.3f\n", fVoltage);
      am_util_stdio_printf("TRIMMED Offset  = %.3f\n", fOffset);
      am_util_stdio_printf("Note - these trim values are '%s' values.\n",
                           bMeasured ? "calibrated" : "uncalibrated default");
      am_util_stdio_printf("\n");
  
      //
      // Enable the ADC interrupt in the NVIC.
      //
      am_hal_interrupt_enable(AM_HAL_INTERRUPT_ADC);
      am_hal_interrupt_master_enable();
  
      //
      // Enable the ADC interrupts in the ADC.
      //
      am_hal_adc_int_enable(AM_REG_ADC_INTEN_WCINC(1)     |
                            AM_REG_ADC_INTEN_WCEXC(1)     |
                            AM_REG_ADC_INTEN_FIFOOVR2(1)  |
                            AM_REG_ADC_INTEN_FIFOOVR1(1)  |
                            AM_REG_ADC_INTEN_SCNCMP(1)    |
                            AM_REG_ADC_INTEN_CNVCMP(1));
  
      //
      // Reset the sample count which will be incremented by the ISR.
      //
      g_ui32SampleCount = 0;
  
      //
      // Kick Start Timer 3 with an ADC software trigger in REPEAT used.
      //
      am_hal_adc_trigger();
  
      //
      // Track buffer depth for progress messages.
      //
      i32BaseLevelCount = g_ui32SampleCount;
  
      //
      // Wait here for the ISR to grab a buffer of samples.
      //
      while (1)
      {
          //
          // Print the battery voltage and temperature for each interrupt
          //
          if (g_ui32SampleCount > i32BaseLevelCount)
          {
              i32BaseLevelCount = g_ui32SampleCount;
  
              //
              // Compute the voltage divider output.
              //
              fVBATT = ((float)g_ui16ADCVDD_code) * 3.0f * fReferenceVoltage
                      / 1024.0f / 64.0f;
  
              //
              // Print the voltage divider output.
              //
              am_util_stdio_printf("VBATT = <%.3f> (0x%04X) ",
                                   fVBATT, g_ui16ADCVDD_code);
  
              //
              // Convert and scale the temperature.
              // Temperatures are in Fahrenheit range -40 to 225 degrees.
              // Voltage range is 0.825V to 1.283V
              // First get the ADC voltage corresponding to temperature.
              //
              fADCTempVolts = ((float)g_ui16ADCTEMP_code) * fReferenceVoltage
                            / (1024.0f * 64.0f);
  
              //
              // Now call the HAL routine to convert volts to degrees Celsius.
              //
              fADCTempDegreesC = am_hal_adc_volts_to_celsius(fADCTempVolts);
  
              //
              // print the temperature value in Celsius.
              //
              am_util_stdio_printf("TEMP = %.2f C (0x%04X) ",
                                   fADCTempDegreesC, g_ui16ADCTEMP_code);
  
              //
              // Print the temperature value in Fahrenheit.
              //
              fTempF = (fADCTempDegreesC * (9.0f / 5.0f)) + 32.0f;
              am_util_stdio_printf(" %.2f F", fTempF);
  
              //
              // Use button 0 to turn on or off the battery load resistor.
              //
  #if AM_PART_APOLLO
              if (!am_hal_gpio_input_bit_read(AM_BSP_GPIO_BUTTON0))
              {
                  am_util_stdio_printf("BATTERY LOAD RESISTOR ON\n");
                  am_hal_adc_batt_load_enable();
                  am_devices_led_on(am_bsp_psLEDs, 2);
              }
              else
              {
                  am_util_stdio_printf("\n");
                  am_hal_adc_batt_load_disable();
                  am_devices_led_off(am_bsp_psLEDs, 2);
              }
  #else
              am_util_stdio_printf("\n");
  #endif
          }
  
          //
          // Sleep here until the next ADC interrupt comes along.
          //
          am_devices_led_off(am_bsp_psLEDs, 0);
          am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP);
          am_devices_led_on(am_bsp_psLEDs, 0);
      }
  }
  