Using the UNO R4 AGT1 and ELC for periodic ADC readings

I am continuing my voyage of discovery with the analog to digital converter (ADC) on the UNO R4; my board is the WiFi version. After developing code that can run the ADC at maximum (I think) speeds and use external triggers, I wanted to use the Event Link Controller (ELC) with the Asynchronous General Purpose Timer (AGT) to make timed periodic scans in the continuous-scan mode. I am using AGT1 since AGT0 is dedicated to delay(), millis() and so forth.

As I reported here, I had a little difficulty setting up the AGT1 with AGTLCLK as the clock source, but I solved that problem. I now have a sketch that uses the AGT ELC event to trigger ADC scans. Here is the code; as before I am using the register defines by @susan-parker:

#include "IRQManager.h"
/*
R4_FastADC_TimedScan.ino
This sketch illustrates using the AGT1 timer as an Event Link Controller (ELC) event
 to make repeated ADC scans at timed intervals.  It includes a software trigger to start
 data collection on a rising or falling edge; without it conversion starts at a random
 point in the waveform being read.

by Jack Short, March 6 2025

This is free software, no restrictions or limitations are placed on its use.
This software is provided "as is" without warranty of any kind with regard
to its usablilty or suitablity for any purpose.
*/

// #defines from Susan Parker's file:  susan_ra4m1_minima_register_defines.h
//  at https://github.com/TriodeGirl/RA4M1_Arduino_UNO-R4_Processor_Direct_Register_Addressing_Defines
// section and page numbes are from the RA4M1 hardware manual
//  https://www.renesas.com/us/en/document/mah/renesas-ra4m1-group-users-manual-hardware

// interrupt controller
#define ICUBASE         0x40000000                                         // ICU Base - See 13.2.6 page 233, 32 bits -
#define ICU_IELSR       0x6300                                             // ICU Event Link Setting Register n
#define ICU_IELSR00     ((volatile unsigned int *)(ICUBASE + ICU_IELSR))   // IELSR register 0
#define IRQ_AGT1_AGTI   0x21                                               // AGT1 underflow interrupt
#define IRQ_ADC140_ADI  0x29                                               // ADC14 scan-end interrupt
#define ICU_IELSR_IR    0x10000                                            // Interrupt Status Flag bit (IR, bit 16), in the IELSRx register, ISR must clear
// system clock divider
#define SYSTEM          0x40010000                                         // ICU Base - See 13.2.6 page 233
#define SYSTEM_PRCR     ((volatile unsigned short *)(SYSTEM + 0xE3FE))     // Protect Register
#define SYSTEM_SCKDIVCR ((volatile unsigned int *)(SYSTEM + 0xE020))       // System Clock Division Control Register
// Event Link Contrioller registers
#define ELCBASE         0x40040000                                         // Event Link Controller
#define ELC_ELCR        ((volatile unsigned char  *)(ELCBASE + 0x1000))    // Event Link Controller Register
#define ELC_ELSR        0x1010                                             // Event Link Setting Registers
#define ELC_ELSR08      ((volatile unsigned short *)(ELCBASE + ELC_ELSR +( 8 * 4))) // ELC_AD00 - ADC14A
// Module Stop Control Registers C and D
#define MSTP            0x40040000                                         // Module Registers
#define MSTP_MSTPCRC    ((volatile unsigned int   *)(MSTP + 0x7004))       // Module Stop Control Register C
#define MSTPC14         14                                                 // ELC   - Event Link Controller Module
#define MSTP_MSTPCRD    ((volatile unsigned int   *)(MSTP + 0x7008))       // Module Stop Control Register D
#define MSTPD2           2                                                 // AGT1   - Asynchronous General Purpose Timer 1 Module
#define MSTPD16         16                                                 // ADC140 - 14-Bit A/D Converter Module
// ADC registers
#define ADCBASE         0x40050000                                         // ADC Base
#define ADC140_ADCSR    ((volatile unsigned short *)(ADCBASE + 0xC000))    // A/D Control Register
#define ADCSR_ADST      15                                                 // A/D Conversion Start bit
#define ADC140_ADANSA0  ((volatile unsigned short *)(ADCBASE + 0xC004))    // A/D Channel Select Register A0
#define ADC140_ADCER    ((volatile unsigned short *)(ADCBASE + 0xC00E))    // A/D Control Extended Register
#define ADC140_ADSTRGR  ((volatile unsigned short *)(ADCBASE + 0xC010))    // A/D Conversion Start Trigger Select Register
#define ADC140_ADDR00   ((volatile unsigned short *)(ADCBASE + 0xC020))    // A1 data register
// ====  Asynchronous General Purpose Timer (AGT) =====
#define AGTBASE 0x40084000
#define AGT0_AGT      ((volatile unsigned short *)(AGTBASE))         // AGT Counter Register
#define AGT1_AGT      ((volatile unsigned short *)(AGTBASE + 0x100))
#define AGT0_AGTCMA   ((volatile unsigned short *)(AGTBASE + 0x002)) // AGT Compare Match A Register
#define AGT1_AGTCMA   ((volatile unsigned short *)(AGTBASE + 0x102))
#define AGT0_AGTCMB   ((volatile unsigned short *)(AGTBASE + 0x004)) // AGT Compare Match B Register
#define AGT1_AGTCMB   ((volatile unsigned short *)(AGTBASE + 0x104))
// 8 bit registers
#define AGT0_AGTCR    ((volatile unsigned char  *)(AGTBASE + 0x008))  // AGT Control Register
#define AGT1_AGTCR    ((volatile unsigned char  *)(AGTBASE + 0x108))  //
#define AGTCR_TSTART  0  // R/W - AGT Count Start; 1: Count starts, 0: Count stops
#define AGTCR_TCSTF   1  // R   - AGT Count Status Flag; 1: Count in progress, 0: Count is stopped
#define AGTCR_TSTOP   2  // W   - AGT Count Forced Stop; 1: The count is forcibly stopped, 0: Writing 0 is invalid!!!
#define AGT0_AGTMR1   ((volatile unsigned char  *)(AGTBASE + 0x009))  // AGT Mode Register 1
#define AGT1_AGTMR1   ((volatile unsigned char  *)(AGTBASE + 0x109))  //
#define AGT0_AGTMR2   ((volatile unsigned char  *)(AGTBASE + 0x00A))  // AGT Mode Register 2
#define AGT1_AGTMR2   ((volatile unsigned char  *)(AGTBASE + 0x10A))  //
#define AGT0_AGTIOC   ((volatile unsigned char  *)(AGTBASE + 0x00C))  // AGT I/O Control Register
#define AGT1_AGTIOC   ((volatile unsigned char  *)(AGTBASE + 0x10C))  //
#define AGTIOC_TOE    2  // AGTOn Output Enable
#define AGT0_AGTISR   ((volatile unsigned char  *)(AGTBASE + 0x00D))  // AGT Event Pin Select Register
#define AGT1_AGTISR   ((volatile unsigned char  *)(AGTBASE + 0x10D))  //
#define AGT0_AGTCMSR  ((volatile unsigned char  *)(AGTBASE + 0x00E))  // AGT Compare Match Function Select Register
#define AGT1_AGTCMSR  ((volatile unsigned char  *)(AGTBASE + 0x10E))  //
#define AGT0_AGTIOSEL ((volatile unsigned char  *)(AGTBASE + 0x00F))  // AGT Pin Select Register
#define AGT1_AGTIOSEL ((volatile unsigned char  *)(AGTBASE + 0x10F))  //
// end of Parker code

// AGT clock sources
#define AGTSRC_PCLKB    0                                   // PCLKB (24 MHz)
#define AGTSRC_PCLKB_8  1                                   // PCLKB / 8 (3 MHz)
#define AGTSRC_PCLKB_2  3                                   // PCLKB / 2 (12 MHz)
#define AGTSRC_AGTLCLK  4                                   // divided clock specified by AGTLCLK (LOCO) (32.768 kHz)
#define AGTSRC_AGT0_UF  5                                   // AGT0 underflow (1 ms)
#define AGTSRC_AGTSCLK  6                                   // divided clock specified by AGTSCLK (SOSC) (unusable on UNO R4)

#define AGTMR1_TCK      4                                   // bit shift for loading clock-source bits into AGTMR1
#define AGT_TIMER       0                                   // AGT mode - timer

#define AGT_AGTCR_TUNDF 0x20                                // TUNDEF bit in AGTCR, clear it in ISR to acknowledge underflow interrupt

// AGTLCLK divisor codes
#define DIVCODE_1_1     0                                   // 1 / 1
#define DIVCODE_1_2     1                                   // 1 / 2
#define DIVCODE_1_4     2                                   // 1 / 4
#define DIVCODE_1_8     3                                   // 1 / 8
#define DIVCODE_1_16    4                                   // 1 / 16
#define DIVCODE_1_32    5                                   // 1 / 32
#define DIVCODE_1_64    6                                   // 1 / 64
#define DIVCODE_1_128   7                                   // 1 / 128

#define ICU_IELSR_IR    0x10000                             // Interrupt Status Flag bit (IR, bit 16), in the IELSRx register, ISR must clear it

#define ELCON           0x80                                // enable bit in the Event Link Controller Register (ELCR)
#define ADST            0x8000                              // (1 << ADCSR_ADST), start-conversion bit in the ADCSR register 

// commands from controller computer program
#define CMDLEN            6                                 // length of a command sent to the Arduino from controller program
#define ARDCMD_NULL       0                                 // no command
#define ARDCMD_STARTSCAN  1                                 // start sampling
#define ARDCMD_STOPSCAN   2                                 // stop sampling
#define ARDCMD_SETRATE    3                                 // set the sampling rate
#define ARDCMD_TRIGTYPE   4                                 // set trigger type for software trigger
#define ARDCMD_TRIGLVL    5                                 // set trigger level for software trigger
#define ARDCMD_SAMPRATE   6                                 // set sampling rate for ELC event

//#define _12BITS 1                                           // un-comment this for 12-bit mode

#define BUFF_SIZE 1000                                      // fills a good portion of a computer screen

unsigned short buffer[BUFF_SIZE];                           // readings go here
uint8_t *pBuff8 = (uint8_t *)buffer;                        // byte pointer for Serial.write()

int nBuffIndex;                                             // buffer location for next reading
volatile bool bReadingReady;                                // TRUE when a block of data is ready to send

GenericIrqCfg_t cfgAdcIrq;                                  // structure defined in IRQManager.h for settng an IRQ

// zero level for input that has a 2.5 volt offset added
#ifdef _12BITS
#define ZERO_LVL 2048                                       // half of 2^12
#else
#define ZERO_LVL 8192                                       // half of 2^14
#endif
// note:  the above will need adjustment for the analog reference voltage being < 5.0v.

void ADC_ISR();                                             // ADC interrupt service routine
void StartScan();                                           // start a scan without the AGT1 event, or start AGT1 after it has been stopped
void StopScan();                                            // end the current scan, may stop AGT1 too
void SetRate(int nRateCode);                                // sets ADC scan rate
void SetTrigType(int nTrgTyp);                              // set software trigger type - none, rising edge or falling edge
void SetTrigLevel(unsigned char *pData);                    // set software trigger level to bracket on rising or falling edge
void SetSampleRate(unsigned char *pData);                   // convert 4-byte rate from little to big-endian and set the AGT1 rate
bool SetAGTRate(unsigned long nMicrosec);                   // set AGT1 rate
void StartAGT();                                            // start AGT1
void StopAGT();                                             // stop AGT1

// for converting a long value between 
// little-endian (least-significant byte first) and big-endian (most-significant byte first)
// the RA4M1 uses the big-endian format
typedef union  {
  unsigned long n;                                          // long value
  unsigned char c[4];                                       // bytes to swap
  } LongChar;

// software trigger
#define TRIGTYPE_NONE    0                                  // no trigger, don't wait to start reading
#define TRIGTYPE_RISING  1                                  // rising-edge trigger:  fires when readings go from below to above the trigger value
#define TRIGTYPE_FALLING 2                                  // falling-edge trigger:  fires when readings go from above to below the trigger value
int nTrigType;                                              // trigger type, one of the above; set to TRIGTYPE_NONE to disable the software trigger
volatile unsigned int nTrigLevel;                           // trigger level, trigger fires when readings bracket this value
volatile unsigned int nRt1, nRt2;                           // consecutive readings, used by software trigger
volatile bool bWaiting;                                     // true while waiting for the software trigger

bool bAGTRunning;                                           // true if AGT1 is running, false if it has been stopped.

void setup() {
  Serial.begin(115200);
  while(!Serial);
// install the ADC IRQ
  cfgAdcIrq.irq = FSP_INVALID_VECTOR;                       // initialize structure
  cfgAdcIrq.ipl = 12;                                       // priority level
  cfgAdcIrq.event = (elc_event_t)IRQ_ADC140_ADI;            // ADC140_ADI interrupt
  IRQManager::getInstance().addGenericInterrupt(cfgAdcIrq, 
                                                 ADC_ISR);  // attach the ADC ISR
// set up the ADC
  *MSTP_MSTPCRD &= (0xFFFFFFFF - (0x01 << MSTPD16));        // clear MSTPD16 bit in Module Stop Control Register D to activate the ADC module
#ifdef _12BITS
  *ADC140_ADCER = 0;                                        // 12-bit resolution, no self-diagnosis, flush right
#else
  *ADC140_ADCER = 6;                                        // 14-bit resolution, no self-diagnosis, flush right
#endif

  *ADC140_ADANSA0 = 1;                                      // using analog pin A1
  *ADC140_ADCSR &= 0x1FFF;                                  // clear ADCS bits
  *ADC140_ADCSR |= 0x4000;                                  // ADCS bits = 10b, continuous scan mode

// not using addition or averaging:  ADADS0, ADADS1 and ADADC are left in their reset state, all 0

// set up the ELC event to start scans
  *ADC140_ADCSR |= 0x0200;                                  // set TRGE bit to 1 and EXTRG bit to 0; enables the ELC event to start a scan
  *ADC140_ADSTRGR &= 0xC0FF;                                // clear ADSTRGR:TRSA bits
  *ADC140_ADSTRGR |= 0x0900;                                // TRSA bits = 001001b (9):  respond to event from ELC_AD00

// set up the ELC module
  *MSTP_MSTPCRC &= (0xFFFFFFFF - (0x01 << MSTPC14));        // clear MSTPC14 bit in Module Stop Control Register C to activate the ELC module
  *ELC_ELSR08 = IRQ_AGT1_AGTI;                              // set AGT1 as the ADC event source 
  *ELC_ELCR |= ELCON;                                       // enable the ELC

// set up AGT1, clear all AGT registers
  *AGT1_AGTCR = 0;                                          // AGT Control Register
  *AGT1_AGTMR1 = 0;                                         // AGT Mode Register 1
  *AGT1_AGTMR2 = 0;                                         // AGT Mode Register 2
  *AGT1_AGTIOC = 0;                                         // AGT I/O Control Register
  *AGT1_AGTISR = 0;                                         // AGT Event Pin Select Register
  *AGT1_AGTCMSR = 0;                                        // AGT Compare Match Function Select Register
  *AGT1_AGTIOSEL = 0;                                       // AGT Pin Select Register
  *MSTP_MSTPCRD &= (0xFFFFFFFF - (0x01 << MSTPD2));         // clear MSTPD2 bit in Module Stop Control Register D to activate AGT1
  SetAGTRate(1000000);                                      // 1 second period to start
  StartAGT();                                               // start AGT1

  nTrigType = TRIGTYPE_RISING;                              // software trigger type - set to TRIGTYPE_NONE to disable the software trigger
  //nTrigType = TRIGTYPE_NONE;                                // no software trigger
  nTrigLevel = ZERO_LVL;                                    // trigger level: trigger when readings cross the zero value
  nRt1 = 0xFFFFFFFF;                                        // this tells the software trigger to initialize itself by taking the first reference reading on the next interrupt
  bWaiting = nTrigType != TRIGTYPE_NONE;                    // wait for trigger if there is one

  nBuffIndex = 0;                                           // starting buffer index

}

void loop()
{
   if(bReadingReady) {                                      // set by the ISR when the buffer is full
    Serial.write(pBuff8, sizeof(buffer));                   // send out the data
    bReadingReady = false;                                  // need a new scan
    nBuffIndex = 0;                                         // reset buffer pointer
    nRt1 = 0xFFFFFFFF;                                      // tells software trigger, if used, to initialize itself on next interrupt
    bWaiting = nTrigType != TRIGTYPE_NONE;                  // wait for software trigger, if used, on next scan event
    }
   else if(Serial.available() > 0) {                        // if there is a command from the controlling program
   unsigned char cCmdBuff[CMDLEN];                          // command buffer
    delay(2);                                               // make sure all command bytes have been received, probably superfluous
    Serial.readBytes(cCmdBuff, CMDLEN);                     // read command, always CMDLEN bytes even if less are needed
    switch(cCmdBuff[0]) {                                   // first byte is the command code
      case ARDCMD_STARTSCAN:                                // start a scan by operator command
        if(cCmdBuff[1])                                     // boolean value; if true
          StartAGT();                                       //  start AGT1, it will start the next scan via the ELC
         else                                               // cCmdBuff[1] is false
          StartScan();                                      //  start a scan without the ELC event from AGT1
        break;
      case ARDCMD_STOPSCAN:                                 // stop a scan by operator command
        StopScan();                                         //  it's normally stopped by the ISR when the buffer is full
        if(cCmdBuff[1])                                     // boolean value, if true
          StopAGT();                                        //  stop AGT1 too; no ELC event until ARDCMD_STARTSCAN is received with cCmdBuff[1] != 0
        break;
      case ARDCMD_SETRATE:                                  // set the ADC scan rate
        SetRate(cCmdBuff[1]);                               // 2nd byte is rate code
        break;
      case ARDCMD_TRIGTYPE:                                 // set the trigger type for the software trigger
        SetTrigType(cCmdBuff[1]);                           // 2nd byte is trigger type code
        break;
      case ARDCMD_TRIGLVL:                                  // set software trigger level
        SetTrigLevel(cCmdBuff + 1);                         // 2nd byte is LSB and 3rd byte is MSB of 2-byte trigger level
        break;
      case ARDCMD_SAMPRATE:                                 // set the AGT event rate
        SetSampleRate(cCmdBuff + 1);                        // 2nd byte is LSB of 4-byte rate value
        break;
      }
    }
}

void SoftTrigger()
// software trigger for starting a scan on a rising or falling edge
{
  if(nRt1 == 0xFFFFFFFF)                                    // if this is the first interrupt of a read sequence
    nRt1 = *ADC140_ADDR00;                                  //  get current reading to initialize trigger first value
   else {                                                   // not the first interrupt in a sequence, nRt1 has been initialized
    nRt2 = *ADC140_ADDR00;                                  // get the current reading for the second value
    if((nTrigType == TRIGTYPE_RISING &&                     // if trigger is rising-edge
        nRt1 < nTrigLevel && nRt2 >= nTrigLevel) ||         //  the trigger fires when nRt1 < trigger level while nR2 is >= the trigger level
      (nTrigType == TRIGTYPE_FALLING &&                     // else if it is a falling-edge trigger
            nRt1 > nTrigLevel && nRt2 <= nTrigLevel)) {     //  the trigger fires when nRt1 < trigger level while nR2 is <= the trigger level
      bWaiting = false;                                     // trigger has fired, not waiting any more
      buffer[nBuffIndex++] = nRt2;                          // save current reading as first reading in the scan
      }													    
     else                                                   // trigger condition not met
      nRt1 = nRt2;                                          // 2nd reading becomes new 1st reading
    }
}

void ADC_ISR()
// ISR for ADC140_ADI
{
  *(ICU_IELSR00 + cfgAdcIrq.irq) &= ~(ICU_IELSR_IR);        // reset interrupt controller
  if(nTrigType && bWaiting)                                 // if using the software trigger and waiting for it to fire
    SoftTrigger();                                          // check the trigger
   else if(nBuffIndex < BUFF_SIZE) {                        // no trigger or trigger has fired; if the buffer is not full
    buffer[nBuffIndex++] = *ADC140_ADDR00;                  //  get a reading, save it and advance the buffer index
    if(nBuffIndex == BUFF_SIZE) {                           // if the buffer is now full
      bReadingReady = true;                                 // tell loop() to transmit data
      StopScan();                                           // stop scanning, wait for an ELC event from the AGT to go again
      }
    }
}

void StartScan()
// start a scan without the ELC trigger
{
  nBuffIndex = 0;                                           // start of buffer for a new scan
  *ADC140_ADCSR |= ADST;                                    // set bit to start reading (still works even with ELC trigger enabled)
  bReadingReady = false;                                    // not ready to transmit
}

void StopScan()
// stop a scan
{
  *ADC140_ADCSR &= 0x7FFF;                                  // clearing ADST bit stops scanning
}

void SetRate(int nRateCode)
// set the ADC scan rate:
// set the PCKC bits for the Periperal Module Clock C (PLCKC) in the System Clock Division Control Register (SCKDIVCR), 
//  as described in the RA4M1 hardware manual section 8.2.1 (page 130)
{
bool bAGTOn = bAGTRunning;                                  // will be true if AGT1 is currently running
  StopScan();                                               // stop scanning
  if(bAGTRunning)                                           // if AGT1 is running
    StopAGT();                                              //  no ELC event while changing scan rate
  if(nRateCode >= 0 && nRateCode <= 6) {                    // make sure code passed to this function is valid
    *SYSTEM_PRCR = 0xA501;                                  // enable writing to the clock registers
    *SYSTEM_SCKDIVCR &= 0xFFFFFF8F;                         // zero all PCKC bits
    *SYSTEM_SCKDIVCR |= (nRateCode << 4);                   // put in new PCKC value
    *SYSTEM_PRCR = 0xA500;                                  // disable writing to the clock registers
    }
  if(bAGTOn)                                                // if the AGT was running
    StartAGT();                                             // restart it
}

void SetTrigType(int nTrgTyp)
// set the software trigger type
{
  // no need to stop anything for this
  if(nTrgTyp >= TRIGTYPE_NONE && 
                              nTrgTyp <= TRIGTYPE_FALLING)  // make sure code passed to this function is valid
    nTrigType = nTrgTyp;                                    //  if it is, save it
}

void SetTrigLevel(unsigned char *pData)
// set trigger level, a 12- or 14-bit, 2-byte, number
// the RA4M1 device uses the big-endian (MSB first) format for multi-byte values
// my Windows computer uses the little-endian (LSB first) format
// the bytes in the value must be swapped
{
  nTrigLevel = *pData | ((unsigned short)pData[1] << 8);    // trigger level is (first byte) | (second byte << 8)
}

void SetSampleRate(unsigned char *pData)
// set the sampling rate - the rate of AGT1 ELC event
// the rate value is an unsigned long, a 4-byte value 
// it must be converted from little-endian to big-endian
{
bool bAGTOn = bAGTRunning;                                  // will be true if AGT1 is currently running
LongChar nLch;
  // convert 4-byte little-endian to big-endian
  nLch.c[3] = *pData++;                                     // low byte to high byte
  nLch.c[2] = *pData++;                                     // 2nd byte to 3rd byte
  nLch.c[1] = *pData++;                                     // 3rd byte to 2nd byte
  nLch.c[0] = *pData;                                       // high byte to low byte
  if(bAGTRunning)                                           // if AGT1 is running
    StopAGT();                                              //  stop it to change rate
  SetAGTRate(nLch.n);                                       // set AGT1 rate
  if(bAGTOn)                                                // if AGT1 was running
    StartAGT();                                             // restart it
}

#define AGT_MAX 0xFFFF                                      // AGT counters are 16-bit

bool SetAGTRate(unsigned long nMicrosec)
// set rate of AGT1, the sampling rate - how often a 1000-point scan is made
// the maximum possible period is 255996093 microseconds, a little over 255.99 seconds (4 min 15.99 sec)
// Slower rates need to come from some other timer
{
bool bEvenMSec = (nMicrosec % 1000) == 0;                   // true if period evenly divisible by 1000 (it's an even millisecond)
unsigned long nNumMSec = nMicrosec / 1000;                  // number of whole millisecods in the period value
unsigned char nSrcCode = AGTSRC_PCLKB;                      // starting source code for AGTMR1 - PCLKB
unsigned char nDivCode = DIVCODE_1_1;                       // starting divisor code for AGTMR2 - 1/1
unsigned long nTicks = nMicrosec * 24;                      // number of ticks of 24-MHz PCLKB
unsigned short nCount;                                      // count to be loaded into AGT1
  // if the requested rate is an even millisecond (right 3 decimal digits all 0) in the range of 1 - 65535
  // we can use AGT0 underflow as the clock which is exactly (within the accuracy limits of the LOCO) 1 kHz
  // this gets the most exact match for any rate from 1 ms to 6.5 minutes
  if(bEvenMSec && nNumMSec > 0 && nNumMSec <= AGT_MAX ) {   // if the rate is an even milliseond, and is 1 to AGT_MAX milliseconds
    nSrcCode = AGTSRC_AGT0_UF;                              // clock = AGT0 underflow, it is a 1 ms clock
    nCount = nNumMSec;                                      // count is number of milliseconds, range is 1 - 65535 ms
    }
  // not using AGT0; if interval is short enough, use 24 MHz PCLKB as the source clock
   else if(nTicks <= AGT_MAX)                               // if total ticks is <= 16-bit maximum (65535 / 24 = about 2730 µs or 2.73 ms )
    nCount = nTicks;                                        //  use undivided PCLKB, range 1 µs - 2.7 ms
   else if(nTicks <= AGT_MAX * 2) {                         // else if ticks are < (max * 2)
    nSrcCode = AGTSRC_PCLKB_2;                              //  use PCLKB / 2 - 12 MHz
    nCount = nTicks / 2;                                    //  get count - range 2.7 - 5.4 ms (can be lower, but lower already taken care of)
    }
   else if(nTicks <= AGT_MAX * 8) {                         // else if ticks are < (max * 8)
    nSrcCode = AGTSRC_PCLKB_8;                              //  use PCLKB / 8 - 3 MHz
    nCount = nTicks / 8;                                    //  count, range 5.4 - 21.8 ms
    }
   else {                                                   // no AGT0 and interval too long for PCLKB
    nSrcCode = AGTSRC_AGTLCLK;                              // use AGTLCLK which is the LOCO running at 32.768 kHz (32.768 µs / tick)
                                                            // 32768 because that divided by 2 15 times is 1 sec
    nTicks = (unsigned long)(32768.0 * nMicrosec / 1000000.0); // convert µs to 32768ths sec
    if(nTicks <= AGT_MAX)                                   // if total ticks is <= 16-bit maximum
      nCount = nTicks;                                      //  use undivided AGTLCLK, range 30.5 ms - 1.9 sec
     else if(nTicks <= AGT_MAX * 2) {                       // else if ticks are < (max * 2)
      nDivCode = DIVCODE_1_2;                               //  use AGTLCLK / 2 - 16.384 kHz
      nCount = nTicks / 2;                                  //  get count, range 1.9 - 3.9 sec
      }
     else if(nTicks <= AGT_MAX * 4) {                       // else if ticks are < (max * 4)
      nDivCode = DIVCODE_1_4;                               //  use AGTLCLK / 4 - 8.192 kHz
      nCount = nTicks / 4;                                  //  get count, range 3.9 - 7.9 sec
      }
     else if(nTicks <= AGT_MAX * 8) {                       // else if ticks are < (max * 8)
      nDivCode = DIVCODE_1_8;                               //  use AGTLCLK / 8 - 4.096 kHz
      nCount = nTicks / 8;                                  //  get count, range 7.9 - 15.9 sec
      }
     else if(nTicks <= AGT_MAX * 16) {                      // else if ticks are < (max * 16)
      nDivCode = DIVCODE_1_16;                              //  use AGTLCLK / 16 - 2.048 kHz
      nCount = nTicks / 16;                                 //  get count, range 15.9 - 31.9 sec
      }
     else if(nTicks <= AGT_MAX * 32) {                      // else if ticks are < (max * 32)
      nDivCode = DIVCODE_1_32;                              //  use AGTLCLK / 32 - 1.024 kHz
      nCount = nTicks / 32;                                 //  get count, range 31.9 - 63.9 sec
      }
     else if(nTicks <= AGT_MAX * 64) {                      // else if ticks are < max * 64
      nDivCode = DIVCODE_1_64;                              //  use AGTLCLK / 64 - 512 Hz
      nCount = nTicks / 64;                                 //  get count, range 63.9 - 127.9 sec
      }
     else if(nTicks <= AGT_MAX * 128) {                     // else if ticks are < max * 128 
      nDivCode = DIVCODE_1_128;                             //  use AGTLCLK / 128 - 256 Hz
      nCount = nTicks / 128;                                //  get count, range 127.9 - 255.9 sec
      }
     else                                                   // 255996093 microseconds (255.99 sec.) gives a starting count of 65535 for AGTL / 128 , 255998046 overflows to here
      return false;                                         // interval too long for AGT
    }
  *AGT1_AGTMR1 = (nSrcCode << AGTMR1_TCK) | AGT_TIMER;      // timer mode, and click source
  *AGT1_AGTMR2 = nDivCode;                                  // divisor code (0 if source is not AGTLCLK)
  *AGT1_AGT = (unsigned short)nCount;                       // count goes in counter reload register
  return true;
}

void StartAGT()
// start AGT1
{
  *AGT1_AGTCR |= 1;                                         // write 1 to TSTART to start AGT1
  bAGTRunning = true;                                       // let the rest of the sketch know
}

void StopAGT()
// stop AGT1
{
  *AGT1_AGTCR &= 0xFE;                                      // clear TSTART bit to stop AGT1
  bAGTRunning = false;                                      // let the rest of the sketch know
  while((*AGT1_AGTCR & 2) != 0);                            // wait for TCSTF bit to clear
}

In this and in my pervious posts on fast R4 ADC, I have used a 1 kHz sine wave with a 1 volt amplitude (2 v peak-to-peak) and a 2.5 v offset as the test signal.

The AGT can be set to many different rates from several megahertz to a period of just under 256 seconds; see the function SetAGTRate() in the sketch. If a longer period is needed and delay() and millis() are not needed, the AGT0 counter can be set to a slow rate and be used as the input to AGT1. A maximum period of 256 x 65530 seconds, about 194 days, can be obtained that way. That's a bit long, but useful periods like 15 minutes and 1 hour are obtainable this way.

The minimum period depends on the number of samples per scan, the ADC scan rate and the amount of time it takes to send the data out via Serial, if that is what is being done with the data. The example sketch makes a 1000-value scan once per second and sends the data out over the serial line at 115200 baud. Using continuous scan at the maximum rate and 14-bit resolution, a scan takes a bit more than 3 milliseconds to complete. Those 1000 readings are 2-byte short integers, so 2000 bytes of data need to be sent over the serial line. At 115200 baud, that calculates to a minimum of 138 msec of time needed to send a block of data, and there are other things going on like stop bits that add a few milliseconds to the time. I think that in this case the sampling period would need to be at least 150 msec. I am uncertain of my calculations on this; if someone would reply to this post with some info on how to make this calculation correctly, I would be most grateful.

I have been trying to use the Low Power Analog Comparator (ACMPLP) as an external event that triggers an ADC scan on a rising or falling edge of the signal. I can get it to make an interrupt or an ELC event, but the rising / falling edge setting makes no difference; sampling starts at random points in the waveform, not at the intended trigger point. I'll have a post on this soon.

1 Like