After working with some UNO R3 boards for several, years I discovered the UNO R4 and purchased one, the WiFi model, around Christmas. Since then, I have been exploring the ADC module of the RA4M1 device and discovering how to use it to its full capabilities. My first goal was to find out how to make fast readings in single-scan and continuous-scan modes, and to find out what the capabilities were there.
Since then, I have worked out how to use the external hardware trigger, called the "asynchronous" trigger in the RA4M1 hardware manual. I have also done so for the internal trigger from the Event Link Controller (ELC), called "synchronous" in the manual, which can also be connected to some of the board pins and to many "peripherals" of the RA4M1 device. I am presenting these as 2 Forum topics. This one describes the asynchronous trigger; the one about the synchronous trigger will follow shortly.
The asynchronous hardware trigger occurs when the signal on a certain digital pin falls from high to low. It is enabled by setting the TRGE and EXTRG bits to 1 in the A/D Control Register (ADCSR); see the hardware manual section 35.2.3 (page 1187).
There is only one board pin that can be used as the asynchronous trigger, called ADTRG0. This is the RA4M1 pin P102 (RA4M1 hardware manual Table 19.6, page 365). On the UNO WiFi, this is board pin D13, but on the UNO Minima it is board pin D5. (I am curious as to why some of the pin assignments are different between the WiFi and the Minima; it seems like an unnecessary hassle.) The RA4M1 manual says that pin P407 can be ADTRG0 (Table 19.11, page 370), but this pin is not connected to the outside world on either board.
On the WiFi, the trigger pin is the pin connected to the internal LED. The pin must be set to input, so the LED cannot be used in its usual way to communicate to the outside world. It will, however, show the high/low state of the trigger signal.
The trigger fires when the ADTRG0 pin falls from high to low. I have included the pullup resistor because I have been testing it with a pushbutton connected to the pin and the ground. If it is connected to a digital signal, the pullup is not needed.
Here is my sketch R2_ADC_AsyncTrig.ino:
#include "IRQManager.h"
/*
This sketch shows how to use the UNO R4 ADC in continuous mode
with an external hardware ("asynchronous") trigger.
The external trigger is on pin D13 on the UNO WiFi and on pin D5 on the
UNO Minima (I think). The trigger fires when the pin goes from high to
low. On the UNO WiFi, the on-board LED cannot be used in the usual ways
since the pin must be as the trigger input. The LED will show the state
of the signal on the pin.
This is free software, and you may use it in any way you wish.
No warranty is offered with regard to its usefulness to your application.
by Jack Short, February 2025
*/
// #defines from Susan Parker's file: susan_ra4m1_minima_register_defines.h
// System Clock Division Control Register (SCKDIVCR) register which controls
// the rate of Peripheral Module Clock C (PLCKC) which controls the sampling rate
#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
// Module Stop Control Register D
#define MSTP 0x40040000 // Module Registers
#define MSTP_MSTPCRD ((volatile unsigned int *)(MSTP + 0x7008)) // Module Stop Control Register D
#define MSTPD16 16 // ADC140 - 14-Bit A/D Converter Module
// 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
// 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 ADANSA0_ANSA00 0 // bit 0 (== 1) selects A/D channel AN000, which on UNO is A1
#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
// port registers for settng up the trigger pin
#define PORTBASE 0x40040000 // Port Base
#define PMISC_PWPR ((volatile unsigned char *)(PORTBASE + 0x0D03)) // Write-Protect Register - 19.2.6
#define P100PFS 0x0840 // Port 1 Pin Function Select Register
#define PFS_P102PFS ((volatile unsigned int *)(PORTBASE + P100PFS + ( 2 * 4))) // Pin 102, ADTRG0-capable pin: UNO WiFi - D13, UNO Minima - D5
/// fields in Pin Function Select Register
#define PFS_PODR 0 // Pin Output Data - 0: Low output; 1: High output
#define PFS_PIDR 1 // Pin Input State - Read 0: Low level; 1: High level
#define PFS_PDR 2 // Pin Direction - 0: Input (input pin); 1: Output (output pin)
#define PFS_PCR 4 // Pull-up Control - 1: Enable internal pull-up
#define PFS_NCODR 6 // N-Channel Open Drain Control - 1: NMOS open-drain output.
#define PFS_DSCR 10 // Port Drive Capability - 1: Middle drive; Default 0: Low drive
#define PFS_EOR 12 // Event on Rising - 1: Detect rising edge - Set EOR and EOF both to 1
#define PFS_EOF 13 // Event on Falling - 1: Detect falling edge - ... for Detect both edges
#define PFS_ISEL 14 // IRQ Input Enable - 1: Used as an IRQn input pin.
#define PFS_ASEL 15 // Analog Input Enable - 1: Used as an analog pin.
#define PFS_PMR 16 // Pin Mode Control - 1: Used as an I/O port for peripheral functions
#define PFS_PSEL_4_0 24 // Peripheral Function Select
// End of Susan Poter code
#define ADST (1 << ADCSR_ADST) // start-conversion bit
// pin function select bit positions as actual bits
#define _PODR 1 // Pin Output Data (bit position PODR is 0)
#define _PIDR (1 << PFS_PIDR) // Pin Input State
#define _PDR (1 << PFS_PDR) // Pin Direction
#define _PCR (1 << PFS_PCR) // Pull-up Control
#define _EOR (1 << PFS_EOR) // Event on Rising
#define _EOF (1 << PFS_EOF) // Event on Falling
#define EORF (_EOF | _EOR) // Event on either rising or falling edge
#define _ISEL (1 << PFS_ISEL) // IRQ Input Enable
#define _PMR (1 << PFS_PMR) // Pin Mode Control
#define CMDLEN 6 // length of a command sent to the Arduino
#define ARDCMD_NULL 0 // no command
#define ARDCMD_STARTSCAN 1 // start a scan without the trigger
#define ARDCMD_SETRATE 2 // set the sampling rate
//#define _12BITS 1 // un-comment this for 12-bit mode
#define BUFF_SIZE 1000 // fills most 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; // current buffer location for next reading
volatile bool bReadingReady; // TRUE when block of data ready to send
GenericIrqCfg_t cfg; // structure defined in IRQManager.h
// 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 if the analog reference voltage is < 5.0v, which it seems to be.
void ADC_ISR(); // ADC interrupt service routine
void StartScan(); // start a scan without the trigger
void StopScan(); // end the current scan
void SetRate(int nRateCode); // sets the sampling rate
void setup()
{
Serial.begin(115200);
while(!Serial);
// install the ADC IRQ
cfg.irq = FSP_INVALID_VECTOR; // initialize structure
cfg.ipl = 12; // priority level
cfg.event = ELC_EVENT_ADC0_SCAN_END; // ADC140_ADI interrupt
IRQManager::getInstance().addGenericInterrupt(cfg, ADC_ISR);// attach the ADC ISR
// set up ADC
*MSTP_MSTPCRD &= (0xFFFFFFFF - (0x01 << MSTPD16)); // clear the MSPD16 bit in Module Stop Control Register D (MSTPCRD) to use the ADC140 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 pin A1, its data register is ADDR00
// ADCSR, the A/D Control register is documented in Section 35.23 of the RA4M1 hardware manual (page 1087)
*ADC140_ADCSR &= 0x1FFF; // 0001111111111111b; clear ADCS scan mode bits: bits 14, 13 = 00
*ADC140_ADCSR |= 0x4000; // 0100 0000 0000 0000b; ADCS scan mode bits 14, 13 = 10
// set up exernal trigger pin
*ADC140_ADCSR |= 0x0300; // 0000 0011 0000 0000b; set the TRGE bit to enable the trigger and the EXTRG bit to selects the external "asynchronous" trigger
// set the function of pin P102 to ADTRG0
// the pin function select registers PFS.PxxxPFS (xxx is pin number) are documented in section 19.2.5 in the RA4M1 hardware manual (page 357 and following)
// Table 19.6 (page 365) shows that to enable the analog trigger on pin P102, set the 5-bit PSEL field of the PFS register to 01010b (0xA)
// section 19.5.1 (page 362) describes the register setting procedure below
*PMISC_PWPR &= 0x7F; // write protect register (Section 19.2.6, page 359), clear BOWI bit to enable writing the PFSWE bit
*PMISC_PWPR |= 0x40; // set PFSWE bit, enables writing to the PFS register
*PFS_P102PFS = (0xA << PFS_PSEL_4_0) | _PMR | _PCR | _EOF;// PFS 102: input with pullup, I/O port, PSEL = 01010b - ADTRG0 pin, D13 on UNO WiFi, D5 on UNO Minima
*PMISC_PWPR &= 0xBF; // clear PFSWE bit, disables writing to the PmnPFS register
*PMISC_PWPR |= 0x80; // set BOWI bit, disables writing to the PFSWE bit
nBuffIndex = 0; // starting buffer index
}
void loop()
{
if(bReadingReady) { // set by ISR, true when buffer is full
Serial.write(pBuff8, sizeof(buffer)); // send out the data
bReadingReady = false; // wait for next trigger
nBuffIndex = 0; // reset buffer index
delay(100);
}
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
StartScan(); // normally started by external trigger, allows untriggered start
break;
case ARDCMD_SETRATE: // set the sampling rate
SetRate(cCmdBuff[1]); // 2nd byte is rate code
break;
}
}
}
void ADC_ISR()
// ISR for ADC140_ADI
{
// The Interrupt Status Flag bit (IR, 0x10000) in the IELSRx register for the specific interrupt is 1 on entry to the ISR.
// It must be cleared to 0 or the system will hang, see section 13.2.6 (page 233) of the RA4M1 hardware manual for a description
// of the register(s).
*(ICU_IELSR00 + cfg.irq) &= ~(R_ICU_IELSR_IR_Msk); // reset interrupt controller using Susan Parker's #defines
// R_ICU->IELSR[cfg.irq] &= ~(R_ICU_IELSR_IR_Msk); // reset interrupt controller using core defines
if(nBuffIndex < BUFF_SIZE) { // if the buffer is not full
buffer[nBuffIndex] = *ADC140_ADDR00; // get a reading and save it
if(++nBuffIndex == BUFF_SIZE) { // advance buffer index, if the buffer is full
bReadingReady = true; // tell loop() to transmit data
StopScan(); // stop scanning
}
}
}
void StartScan()
// starts a scan without the trigger
{
*ADC140_ADCSR |= ADST; // set bit to start reading (still works even if external trigger is enabled)
nBuffIndex = 0; // start of buffer for new scan
bReadingReady = false; // not ready to transmit
}
void StopScan()
// stop scanning
{
*ADC140_ADCSR &= 0x7FFF; // clearing ADST bit stops scanning
}
void SetRate(int nRateCode)
// set the sampling rate
// the sampling rate is determined by the Periperal Module Clock C (PLCKC)
// its rate is set by the PCKC bits in the System Clock Division Control Register (SCKDIVCR),
// as described in the RA4M1 hardware manual section 8.2.1 (page 130)
// a bit must be set in the the Protect Register, PRCR, (section 12.2.1, page 222) before SCKDIVCR can be changed
{
StopScan(); // stop scanning
if(nRateCode >= 0 && nRateCode <= 6) { // make sure code passed to this function is valid
*SYSTEM_PRCR = 0xA501; // protect register PRKEY bits to 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
}
}
I will write a topic about the synchronous triggers with the ELC and get it out soon.
Hey, @susan-parker. You may already be aware of it, but I found a name conflict between one of your #defines and something in the core code. You defined IELSR as a constant, 0x6300, and you have macros defined for the several IELSR registers that use that constant as part of an offset from the base address. However, the core header R7FA4M1AB.h defines IELSR as uint32_t IELSR[96]. As I was going along on a sketch, I had #included the header with all of your definitions. In my ISR, I had the line
R_ICU->IELSR[cfg.irq] &= ~(R_ICU_IELSR_IR_Msk); as before. The compiler choked on it. I have changed IELSR in my copy of your file to ICU_IELSR and am using that now instead of R_ICU->IELSR[]....