SAMD51 ADC0 and ADC1 Multiplexing with DMA

Hey raidersnake,

Thanks for the tips. My project is essentially just logging 4 0-3.3V inputs to an SD card at up to 2KHz. It's part of a bigger sensor that my group is working on. I've been poking and prodding at it, and managed to get it to run with your advice. However, I don't get either of the results1... variables to ever go true. I'm not sure how terrible of an issue this is, lol.

I think I may have some misunderstandings about this program. If you could answer some of my questions I might be able to understand this better:

  1. What exactly is the difference between the two result arrays? I've been able to get readings from all 4 pins in just adcResults0. adcResults1 doesn't get updated (see 7).
  2. When setting up the Channel 2 descriptor for DMA Sequencing, why is the srcaddr set to the end of the inputCtrl0 array? I would think it should be set to the start.
  3. When setting up the Channel 3 and 4 descriptors, is there a reason why dstaddr is not just set to &adcResults*[1024]?
  4. When setting up the Channel 0 and 1 descriptors, why is dstaddr set to the end of the adcResults arrays?
  5. Could you explain what the NVIC_SetPriority() and NVIC_EnableIRQ() calls do?
  6. For the adcResults* arrays, is "part 0" the first half and "part 1" the second half? I assume the DMAC is operating on one half of each at any one time, leaving the other half safe to use.
  7. Could you explain how the two Handler() functions at the end are used? I don't see them called anywhere, but throwing a print statement in either shows that, while the Chanenl 3 handler is called continuously, the Channel 4 handler is only called once (that I can tell) at the very beginning of the program. Also, results1Part0Ready and results1Part1Ready don't go true besides near the start of the program. I assume these two issues are related. Any ideas for this one?
  8. Less of a code question, but I've noticed that when holding a single pin to 3.3V, if the other pins are floating/not connected, the other three pins seem to be influenced by the pin at 3.3V:

    Do you think this is simply because three of the pins are floating? Or would you say that, if I connected the other pins to their own inputs, every pin would influence the reading of every other pin?

For reference, here is my code. I've made a couple edits here and there, but the meat of it is the same. It's set up to work with the Arduino IDE's serial plotter.

#include "FreeStack.h"
#include "SdFat.h"
#include "sdios.h"

#define error(s) sd.errorHalt(&Serial, F(s))
const uint8_t SD_CS_PIN = 10;
#define SPI_CLOCK SD_SCK_MHZ(50)
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK)
#define NUM_SAMPLES_2 2048 // two times the number of samples per pin. i.e. each pin will get half of this number in samples

SdFs sd;
FsFile file;

#define DEBUG_R0_P0 1
#define DEBUG_R0_P1 0
#define DEBUG_R1_P0 1
#define DEBUG_R1_P1 1
#define DEBUG_SD_BEGIN 1

volatile boolean results0Part0Ready = false;
volatile boolean results0Part1Ready = false;
volatile boolean results1Part0Ready = false;
volatile boolean results1Part1Ready = false;
uint16_t adcResults0[NUM_SAMPLES_2];                                                       // ADC results array 0
uint16_t adcResults1[NUM_SAMPLES_2];                                                       // ADC results array 1

// ADC0 INPUTCTRL register MUXPOS settings 
uint32_t inputCtrl0[] = { ADC_INPUTCTRL_MUXPOS_AIN0,                              // AIN0 = A0
                          ADC_INPUTCTRL_MUXPOS_AIN5,                              // AIN5 = A1
                          ADC_INPUTCTRL_MUXPOS_AIN3,                              // AIN3 = A3
                          ADC_INPUTCTRL_MUXPOS_AIN4 };                            // AIN4 = A4


typedef struct                                                                    // DMAC descriptor structure
{
    uint16_t btctrl;
    uint16_t btcnt;
    uint32_t srcaddr;
    uint32_t dstaddr;
    uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor (*wrb)[DMAC_CH_NUM] __attribute__ ((aligned (16)));          // Write-back DMAC descriptors (changed to array pointer)
dmacdescriptor (*descriptor_section)[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors (changed to array pointer)
dmacdescriptor descriptor __attribute__ ((aligned (16)));                         // Place holder descriptor

dmacdescriptor linked_descriptor[2] __attribute__ ((aligned (16)));



void setup() {
    Serial.begin(115200);                                                           // Start the native USB port
    while(!Serial);                                                                 // Wait for the console to open

    #if DEBUG_SD_BEGIN
        // Initialize SD card before setting up DMA
        if (!sd.begin(SD_CONFIG)) {
        Serial.println("SD initialization failed!");
        while (1);  // Halt if SD card initialization fails
        } else {
        Serial.println("SD initialization succeeded!");
        }
    #endif

  
    pinMode(10, OUTPUT);                                                            // Initialise the output on D10 for debug purposes
    PORT->Group[PORTB].DIRSET.reg = PORT_PB23;                                      // Initialise the output on D11 for debug purposes TODO: make it autoselect based on board 

    descriptor_section =  reinterpret_cast<dmacdescriptor(*)[DMAC_CH_NUM]>(DMAC->BASEADDR.reg); //point array pointer to BASEADDR defined by SD.begin
    wrb =  reinterpret_cast<dmacdescriptor(*)[DMAC_CH_NUM]>(DMAC->WRBADDR.reg);                 //point array pointer to WRBADDR defined by SD.begin
    DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                    // Enable the DMAC peripheral and all priority levels


    // ADC0 DMA Sequencing setup  
    DMAC->Channel[2].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 2 to priority level 3 (highest)
    DMAC->Channel[2].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_SEQ) |         // Set DMAC to trigger on ADC0 DMAC sequence
                                   DMAC_CHCTRLA_BURSTLEN_SINGLE |                   // One beat per burst
                                   DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
    DMAC->Channel[2].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                         // Activate the suspend (SUSP) interrupt on DMAC channel 2

    descriptor.descaddr = (uint32_t)&(*descriptor_section)[2];                      // Set up a circular descriptor
    descriptor.srcaddr = (uint32_t)inputCtrl0 + sizeof(uint32_t) * 4;               // Configure the DMAC to set the (???)
    descriptor.dstaddr = (uint32_t)&ADC0->DSEQDATA.reg;                             // Write the INPUT CTRL 
    descriptor.btcnt = 4;//TODO TODO TODO TODO TODO                                 // Beat count is 2 (???) (code originally had it at 4) TODO TODO TODO TODO TODO
    descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_WORD |                                 // Beat size is WORD (32-bits)
                        DMAC_BTCTRL_SRCINC |                                        // Increment the source address
                        DMAC_BTCTRL_STEPSEL_SRC |                                   // Indicates that the source address is using these step settings
                        DMAC_BTCTRL_STEPSIZE_X1 |                                   // The Step size is set to 1x beats
                        DMAC_BTCTRL_VALID;                                          // Descriptor is valid
    
    memcpy(&(*descriptor_section)[2], &descriptor, sizeof(descriptor));             // Copy the descriptor to the descriptor section
    

// ADC0
    DMAC->Channel[3].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 3 to priority level 3 (highest)
    DMAC->Channel[3].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC0_DMAC_ID_RESRDY) |      // Set DMAC to trigger when ADC0 result is ready
                                   DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer

    descriptor.descaddr = (uint32_t)&(*descriptor_section)[0];                      // Set up a circular descriptor
    descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                               // Take the result from the ADC0 RESULT register
    descriptor.dstaddr = (uint32_t)adcResults0 + sizeof(uint16_t) * (NUM_SAMPLES_2/2);           // Place it in the middle of adcResults0 array
    descriptor.btcnt = (NUM_SAMPLES_2/2);                                                        // Beat count
    descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                        DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                        DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                        DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 3 after block transfer

    memcpy(&(*descriptor_section)[3], &descriptor, sizeof(descriptor));             // Copy the descriptor to the descriptor section


    descriptor.descaddr = (uint32_t)&(*descriptor_section)[3];                      // Set up a circular descriptor
    descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                               // Take the result from the ADC0 RESULT register
    descriptor.dstaddr = (uint32_t)&adcResults0[NUM_SAMPLES_2/2] + sizeof(uint16_t) * (NUM_SAMPLES_2/2);    // Place it in the adcResults0 array
    descriptor.btcnt = (NUM_SAMPLES_2/2);                                                        // Beat count
    descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                        DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                        DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                        DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 0 after block transfer

    memcpy(&(*descriptor_section)[0], &descriptor, sizeof(descriptor));             // Copy the descriptor to the descriptor section



    NVIC_SetPriority(DMAC_3_IRQn, 0);                                               // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 3
    NVIC_EnableIRQ(DMAC_3_IRQn);                                                    // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)
    
    DMAC->Channel[3].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                         // Activate the suspend (SUSP) interrupt on DMAC channel 3


  
// ADC1
    DMAC->Channel[4].CHPRILVL.reg = DMAC_CHPRILVL_PRILVL_LVL3;                      // Set DMAC channel 4 to priority level 3 (highest)
    DMAC->Channel[4].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC1_DMAC_ID_RESRDY) |      // Set DMAC to trigger when ADC0 result is ready
                                   DMAC_CHCTRLA_TRIGACT_BURST;                      // DMAC burst transfer
    descriptor.descaddr = (uint32_t)&(*descriptor_section)[1];                          // Set up a circular descriptor
    descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                               // Take the result from the ADC0 RESULT register
    descriptor.dstaddr = (uint32_t)adcResults1 + sizeof(uint16_t) * (NUM_SAMPLES_2/2);           // Place it in the adcResults1 array
    descriptor.btcnt = (NUM_SAMPLES_2/2);                                                        // Beat count
    descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                        DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                        DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                        DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 4 after block transfer

    memcpy(&(*descriptor_section)[4], &descriptor, sizeof(descriptor));             // Copy the descriptor to the descriptor section
    

    descriptor.descaddr = (uint32_t)&(*descriptor_section)[4];                         // Set up a circular descriptor
    descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                               // Take the result from the ADC0 RESULT register
    descriptor.dstaddr = (uint32_t)&adcResults1[1024] + sizeof(uint16_t) * (NUM_SAMPLES_2/2);                                    // Place it in the adcResults1 array
    descriptor.btcnt = (NUM_SAMPLES_2/2);                                                        // Beat count
    descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                                // Beat size is HWORD (16-bits)
                        DMAC_BTCTRL_DSTINC |                                        // Increment the destination address
                        DMAC_BTCTRL_VALID |                                         // Descriptor is valid
                        DMAC_BTCTRL_BLOCKACT_SUSPEND;                               // Suspend DMAC channel 4 after block transfer
    memcpy(&linked_descriptor[1], &descriptor, sizeof(descriptor));                 // Copy the descriptor to the descriptor section

    NVIC_SetPriority(DMAC_4_IRQn, 0);                                               // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC Channel 4
    NVIC_EnableIRQ(DMAC_4_IRQn);                                                    // Connect DMAC Channel 0 to Nested Vector Interrupt Controller (NVIC)
    
    DMAC->Channel[4].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                         // Activate the suspend (SUSP) interrupt on DMAC channel 0

  
    // ADC Register Settings
    ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN3_Val;                     // Set the analog input to A2
    while(ADC1->SYNCBUSY.bit.INPUTCTRL);                                            // Wait for synchronization
    ADC1->SAMPCTRL.bit.SAMPLEN = 0x00;                                              // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz 
    while(ADC1->SYNCBUSY.bit.SAMPCTRL);                                             // Wait for synchronization
    ADC1->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART |                                   // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
                         ADC_DSEQCTRL_INPUTCTRL;                                    // Change the ADC0 INPUTCTRL register on DMAC sequence
    ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;                                       // Set ADC resolution to 12 bits 
    while(ADC1->SYNCBUSY.bit.CTRLB);                                                // Wait for synchronization
    ADC1->CTRLA.bit.SLAVEEN = 1;                                                    // Set ADC1 to slave, ADC0 to master, both share CTRLA register
    
    ADC0->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN0_Val;                     // Set the analog input to A0
    while(ADC0->SYNCBUSY.bit.INPUTCTRL);                                            // Wait for synchronization
    ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                                              // Extend sampling time by SAMPCTRL ADC cycles (12 + 1 + 2)/750kHz = 20us = 50kHz
    while(ADC0->SYNCBUSY.bit.SAMPCTRL);                                             // Wait for synchronization  
    ADC0->DSEQCTRL.reg = ADC_DSEQCTRL_AUTOSTART |                                   // Auto start a DMAC conversion upon ADC0 DMAC sequence completion
                         ADC_DSEQCTRL_INPUTCTRL;                                    // Change the ADC0 INPUTCTRL register on DMAC sequence
    ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT;                                       // Set ADC resolution to 12 bits 
    while(ADC0->SYNCBUSY.bit.CTRLB);                                                // Wait for synchronization
    ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV4;                                     // Divide Clock ADC GCLK by 4 (48MHz/64 = 750kHz) (12 + 1)/750kHz = 17.3us sample time 
    ADC0->CTRLA.bit.ENABLE = 1;                                                     // Enable the ADC
    while(ADC0->SYNCBUSY.bit.ENABLE);                                               // Wait for synchronization
    ADC0->SWTRIG.bit.START = 1;                                                     // Initiate a software trigger to start an ADC conversion
    while(ADC0->SYNCBUSY.bit.SWTRIG);                                               // Wait for synchronization




    DMAC->Channel[2].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 2
    DMAC->Channel[3].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 3
    DMAC->Channel[4].CHCTRLA.bit.ENABLE = 1;                                        // Enable DMAC channel 4
    delay(1);                                                                       // Wait a millisecond
}

void loop() 
{  
//   Serial.println("Loop Start");
    Serial.print("hiii:");
    Serial.print(5000);
    Serial.print(",");

    Serial.print("lo:");
    Serial.print(0);
    Serial.print(",");
//   debugPrintStatus();

    if (results0Part0Ready)                                                         // Display the results in results0 array
    {
        #if DEBUG_R0_P0
            Serial.print("a1_reading:");
            Serial.print(adcResults0[0]);
            Serial.print(", ");
            Serial.print("a3_reading:");
            Serial.print(adcResults0[1]);
            Serial.print(", ");
            Serial.print("a4_reading:");
            Serial.print(adcResults0[2]);
            Serial.print(", ");
            Serial.print("a0_reading:");
            Serial.print(adcResults0[3]);
        #endif

        Serial.println();
        results0Part0Ready = false;                                                   // Clear the results0 ready flag
    }

    if (results0Part1Ready)                                                         // Display the results in results1 array
    {
        #if DEBUG_R0_P1
            Serial.print("a3_reading:");
            Serial.print(adcResults0[1024]);
            Serial.print(", ");
            Serial.print("a4_reading:");
            Serial.print(adcResults0[1025]);
            Serial.print(", ");
            Serial.print("a0_reading:");
            Serial.print(adcResults0[1026]);
            Serial.print(", ");
            Serial.print("a1_reading:");
            Serial.print(adcResults0[1027]);
        #endif

        Serial.println();
        results0Part1Ready = false;                                                   // Clear the results1 ready flag
    }

    if (results1Part0Ready)                                                         // Display the results in results0 array
    {
        #if DEBUG_R1_P0
            Serial.print("3_reading:");
            Serial.print(ADC1->RESULT.reg);
            Serial.print(",");
        #endif

        Serial.println();
        results1Part0Ready = false;                                                   // Clear the results0 ready flag
    }

    if (results1Part1Ready)                                                         // Display the results in results1 array
    {
        #if DEBUG_R1_P1
            Serial.print("4_reading:");
            Serial.print(ADC1->RESULT.reg);
            Serial.print(",");
        #endif

        Serial.println();
        results1Part1Ready = false;                                                   // Clear the results1 ready flag
    }
}

void DMAC_3_Handler()                                                             // Interrupt handler for DMAC channel 3
{
    // Serial.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    static uint8_t count0 = 0;                                                      // Initialise the count 
    if (DMAC->Channel[3].CHINTFLAG.bit.SUSP)                                        // Check if DMAC channel 3 has been suspended (SUSP) 
    {  
        DMAC->Channel[3].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;                       // Restart the DMAC on channel 3
        DMAC->Channel[3].CHINTFLAG.bit.SUSP = 1;                                      // Clear the suspend (SUSP)interrupt flag
        if (count0)                                                                   // Test if the count0 is 1
        {
            results0Part1Ready = true;                                                  // Set the results 0 part 1 ready flag
        }
        else
        {
            results0Part0Ready = true;                                                  // Set the results 0 part 0 ready flag
        }
        count0 = (count0 + 1) % 2;                                                    // Toggle the count0 between 0 and 1 
        digitalWrite(10, HIGH);                                                       // Toggle the output high then low on D10 for debug purposes
        delayMicroseconds(3);                                                         // Short delay for visualization
        digitalWrite(10, LOW);
    }
}

void DMAC_4_Handler()                                                             // Interrupt handler for DMAC channel 4
{
    Serial.println("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");

    static uint8_t count1 = 0;                                                      // Initialise the count 
    if (DMAC->Channel[4].CHINTFLAG.bit.SUSP)                                        // Check if DMAC channel 4 has been suspended (SUSP) 
    {  
        DMAC->Channel[4].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;                       // Restart the DMAC on channel 4
        DMAC->Channel[4].CHINTFLAG.bit.SUSP = 1;                                      // Clear the suspend (SUSP)interrupt flag
        if (count1)                                                                   // Test if the count1 is 1
        {
            results1Part1Ready = true;                                                  // Set the results 1 part 1 ready flag
        }
        else
        {
            results1Part0Ready = true;                                                  // Set the results 1 part 0 ready flag
        }
        count1 = (count1 + 1) % 2;                                                    // Toggle the count1 between 0 and 1 
        PORT->Group[PORTB].OUTSET.reg = PORT_PB23;    // Toggle D10 HIGH              // Toggle the output high then low on D11 for debug purposes
        delayMicroseconds(3);                                                         // Short delay for visualization
        PORT->Group[PORTB].OUTCLR.reg = PORT_PB23;    // Toggle D10 LOW
    }
}

void debugPrintStatus() {
  // Print out DMAC channel status and flags
    Serial.println(F("=== DMAC Channel Status ==="));
    for (int i = 0; i < 5; i++) {
        Serial.print(F("Channel ")); Serial.println(i);
        Serial.print(F("  CHCTRLA: ")); Serial.println(DMAC->Channel[i].CHCTRLA.reg, HEX);
        Serial.print(F("  CHCTRLB: ")); Serial.println(DMAC->Channel[i].CHCTRLB.reg, HEX);
        Serial.print(F("  CHINTFLAG: ")); Serial.println(DMAC->Channel[i].CHINTFLAG.reg, HEX);
        Serial.print(F("  CHINTENSET: ")); Serial.println(DMAC->Channel[i].CHINTENSET.reg, HEX);
        Serial.print(F("  CHINTENCLR: ")); Serial.println(DMAC->Channel[i].CHINTENCLR.reg, HEX);

        // Print descriptor information
        Serial.print(F("  Descriptor Address: ")); Serial.println((uint32_t)&(*descriptor_section)[i], HEX);
        Serial.print(F("    BTCNT: ")); Serial.println((*descriptor_section)[i].btcnt);
        Serial.print(F("    SRCADDR: ")); Serial.println((uint32_t)(*descriptor_section)[i].srcaddr, HEX);
        Serial.print(F("    DSTADDR: ")); Serial.println((uint32_t)(*descriptor_section)[i].dstaddr, HEX);
        Serial.print(F("    DESCADDR: ")); Serial.println((uint32_t)(*descriptor_section)[i].descaddr, HEX);
    }

    // Print ADC status
    Serial.println(F("=== ADC Status ==="));
    Serial.print(F("ADC0 RESULT: ")); Serial.println(ADC0->RESULT.reg, HEX);
    Serial.print(F("ADC0 INTFLAG: ")); Serial.println(ADC0->INTFLAG.reg, HEX);
    Serial.print(F("ADC0 STATUS: ")); Serial.println(ADC0->STATUS.reg, HEX);
    Serial.print(F("ADC1 RESULT: ")); Serial.println(ADC1->RESULT.reg, HEX);
    Serial.print(F("ADC1 INTFLAG: ")); Serial.println(ADC1->INTFLAG.reg, HEX);
    Serial.print(F("ADC1 STATUS: ")); Serial.println(ADC1->STATUS.reg, HEX);

    // Print other important registers
    Serial.println(F("=== Other Important Registers ==="));
    Serial.print(F("DMAC BASEADDR: ")); Serial.println(DMAC->BASEADDR.reg, HEX);
    Serial.print(F("DMAC WRBADDR: ")); Serial.println(DMAC->WRBADDR.reg, HEX);
    Serial.print(F("DMAC CTRL: ")); Serial.println(DMAC->CTRL.reg, HEX);
}

Sorry for the wall of text! Any help would be greatly appreciated :slight_smile: