Tweek12x8 LED Matrix library to drive header pins D2-D12?

First ever post here and completely new to the Arduino. The goal is to control an LED matrix display, identical to the on-board 12x8, using the D2 (P104) through D12 (P411) pins on the header. I'd like to leverage all the great work put into the subject library but its' not clear to me where the 12 output pins that drive the on-board matrix (P003,P004,P011,...P233) are defined. It would be a bonus if both displays could be driven simultaneously. Any guidance would be greatly appreciated.

What board ? edit: ah sorry the UNO R4.

It is rather unclear what you are talking about. Maybe if you share with us the sample code.
edit: well i gues you will have to have a look through the core to find that.

Yes, the UNO R4 Wifi with the 12x8 LED matrix on-board. I'm using the MatrixIntro example as a reference. I've looked at Arduino_LED_Matrix.h in detail but since there are no comments, I'm guessing the pin mapping is done in the turnLed() function?

Perhaps looking at the code that begins at this link and does pin mapping here, may help to answer your question.

Thank you, reading your links gave me more insight.

Although I don't have my external matrix constructed yet, by monitoring the state of D2-D12, it seems these minor mods to Arduino_LED_Matrix.h might do it.

static const int pin_zero_index = 28;   //On-board LED matrix
static const int pin_zero_index_ob = 2; //Off-board LED matrix connected to D2-D12 header pins

static void turnLed(int idx, bool on) {
  R_PORT0->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT0_MASK);
  R_PORT2->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT2_MASK);

  if (on) {
    //On-board 8x12 LED matrix
    bsp_io_port_pin_t pin_a = g_pin_cfg[pins[idx][0] + pin_zero_index].pin;
    R_PFS->PORT[pin_a >> 8].PIN[pin_a & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_HIGH;

    bsp_io_port_pin_t pin_c = g_pin_cfg[pins[idx][1] + pin_zero_index].pin;
    R_PFS->PORT[pin_c >> 8].PIN[pin_c & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;

    //Off-board matrix via D2-D12 header pins
    bsp_io_port_pin_t pin_b = g_pin_cfg[pins[idx][0] + pin_zero_index_ob].pin;
    R_PFS->PORT[pin_b >> 8].PIN[pin_b & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_HIGH;

    bsp_io_port_pin_t pin_d = g_pin_cfg[pins[idx][1] + pin_zero_index_ob].pin;
    R_PFS->PORT[pin_d >> 8].PIN[pin_d & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;

  }
}

I've got enough of a matrix constructed to tell that the code in my last update doesn't work as intended and currently stuck figuring out how the original author determined the value of 28 for the constant pin_zero_index. I've added some serial.print() statements to echo idx, port, pin and state info for both the on-board and off-board matrix for various pin_zero_index_ob values. After some trial and error , a value of 5 selects the correct port and pin values for the TRUE state of idx0 but not the FALSE. Here is a snapshop of my modified version of Arduino_MAtrix.h and will try to attach a spreadsheet with additional details including how I've mapped the header pins driving the off-board matrix.

#pragma once

#include "Arduino.h"
#include "FspTimer.h"
#include "gallery.h"

#define NUM_LEDS    96

#if __has_include("ArduinoGraphics.h")
#include <ArduinoGraphics.h>
#define MATRIX_WITH_ARDUINOGRAPHICS
#endif

// const int pin_zero_index = 28; //use this for on-board LED matrix
//static const int pin_zero_index_ob = 2;  //use this for off-board LED matrix connected to D2-D12 header pins

static const uint8_t pins[][2] = {
  { 7, 3 }, // 0
  { 3, 7 },
  { 7, 4 },
  { 4, 7 },
  { 3, 4 },
  { 4, 3 },
  { 7, 8 },
  { 8, 7 },
  { 3, 8 },
  { 8, 3 },
  { 4, 8 }, // 10
  { 8, 4 },
  { 7, 0 },
  { 0, 7 },
  { 3, 0 },
  { 0, 3 },
  { 4, 0 },
  { 0, 4 },
  { 8, 0 },
  { 0, 8 },
  { 7, 6 }, // 20
  { 6, 7 },
  { 3, 6 },
  { 6, 3 },
  { 4, 6 },
  { 6, 4 },
  { 8, 6 },
  { 6, 8 },
  { 0, 6 },
  { 6, 0 },
  { 7, 5 }, // 30
  { 5, 7 },
  { 3, 5 },
  { 5, 3 },
  { 4, 5 },
  { 5, 4 },
  { 8, 5 },
  { 5, 8 },
  { 0, 5 },
  { 5, 0 },
  { 6, 5 }, // 40
  { 5, 6 },
  { 7, 1 },
  { 1, 7 },
  { 3, 1 },
  { 1, 3 },
  { 4, 1 },
  { 1, 4 },
  { 8, 1 },
  { 1, 8 },
  { 0, 1 }, // 50
  { 1, 0 },
  { 6, 1 },
  { 1, 6 },
  { 5, 1 },
  { 1, 5 },
  { 7, 2 },
  { 2, 7 },
  { 3, 2 },
  { 2, 3 },
  { 4, 2 },
  { 2, 4 },
  { 8, 2 },
  { 2, 8 },
  { 0, 2 },
  { 2, 0 },
  { 6, 2 },
  { 2, 6 },
  { 5, 2 },
  { 2, 5 },
  { 1, 2 },
  { 2, 1 },
  { 7, 10 },
  { 10, 7 },
  { 3, 10 },
  { 10, 3 },
  { 4, 10 },
  { 10, 4 },
  { 8, 10 },
  { 10, 8 },
  { 0, 10 },
  { 10, 0 },
  { 6, 10 },
  { 10, 6 },
  { 5, 10 },
  { 10, 5 },
  { 1, 10 },
  { 10, 1 },
  { 2, 10 },
  { 10, 2 },
  { 7, 9 },
  { 9, 7 },
  { 3, 9 },
  { 9, 3 },
  { 4, 9 },
  { 9, 4 },
};

#define LED_MATRIX_PORT0_MASK       ((1 << 3) | (1 << 4) | (1 << 11) | (1 << 12) | (1 << 13) | (1 << 15))
#define LED_MATRIX_PORT2_MASK       ((1 << 4) | (1 << 5) | (1 << 6) | (1 << 12) | (1 << 13))

#define LED_MATRIX_PORT1_MASK       ((1 << 3) | (1 << 12) | (1 << 11) | (1 << 7) | (1 << 6) | (1 << 5) | (1 << 4))  //Do these need to be in numeric ascending order?
#define LED_MATRIX_PORT3_MASK       ((1 << 3) | (1 << 4))
#define LED_MATRIX_PORT4_MASK       ((1 << 10) | (1 << 11))

static const int pin_zero_index = 28;   //On-board LED matrix. Q. How was this value determined?

 //Off-board LED matrix connected to D2-D13 header pins
//static const int pin_zero_index_ob = 0; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR1 PIN12 HIG POR1 PIN5 LOW
//static const int pin_zero_index_ob = 1; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR3 PIN4 HIG POR1 PIN6 LOW
//static const int pin_zero_index_ob = 2; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR3 PIN3 HIG POR1 PIN7 LOW
//static const int pin_zero_index_ob = 3; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR1 PIN3 HIG POR1 PIN11 LOW
//static const int pin_zero_index_ob = 28;//idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR2 PIN5 HIG POR0 PIN12 LOW
//static const int pin_zero_index_ob = 4; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR4 PIN11 HIG POR1 PIN12 LOW
static const int pin_zero_index_ob = 5; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR4 PIN10 HIG POR3 PIN4 LOW
//static const int pin_zero_index_ob = 6; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR1 PIN2 HIG POR3 PIN3 LOW
//static const int pin_zero_index_ob = 7; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR0 PIN14 HIG POR1 PIN3 LOW
//static const int pin_zero_index_ob = 8; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR0 PIN0 HIG POR4 PIN11 LOW
//static const int pin_zero_index_ob = 9; //idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR0 PIN1 HIG POR4 PIN10 LOW
//static const int pin_zero_index_ob = 10;//idx0 POR2 PIN5 HIG POR0 PIN12 LOW POR0 PIN2 HIG POR1 PIN2 LOW
//static const int pin_zero_index_ob = 11; //lost com6 comms. had to do a double tap on reset button, then download a known good sketch.
//static const int pin_zero_index_ob = 12; //lost com6 comms. had to do a double tap on reset button, then download a known good sketch.


static void turnLed(int idx, bool on) {
  //On-board 8x12 LED matrix  
  R_PORT0->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT0_MASK);
  R_PORT2->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT2_MASK);

  //Off-board matrix via D2-D12 header pins
  R_PORT1->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT1_MASK);
  R_PORT3->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT3_MASK);
  R_PORT4->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT4_MASK);

  if (on) {
    //On-board 8x12 LED matrix
    bsp_io_port_pin_t pin_a = g_pin_cfg[pins[idx][0] + pin_zero_index].pin;
    R_PFS->PORT[pin_a >> 8].PIN[pin_a & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_HIGH;

    bsp_io_port_pin_t pin_c = g_pin_cfg[pins[idx][1] + pin_zero_index].pin;
    R_PFS->PORT[pin_c >> 8].PIN[pin_c & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;
Serial.print("idx");
Serial.print(idx, DEC);

Serial.print(" POR");
Serial.print(pin_a >> 8, DEC);
Serial.print(" PIN");
Serial.print(pin_a & 0xFF, DEC);
Serial.print(" HIG");

Serial.print(" POR");
Serial.print(pin_c >> 8, DEC);
Serial.print(" PIN");
Serial.print(pin_c & 0xFF, DEC);
Serial.print(" LOW");

    //Off-board matrix via D2-D12 header pins
    bsp_io_port_pin_t pin_b = g_pin_cfg[pins[idx][0] + pin_zero_index_ob].pin;
    R_PFS->PORT[pin_b >> 8].PIN[pin_b & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_HIGH;

    bsp_io_port_pin_t pin_d = g_pin_cfg[pins[idx][1] + pin_zero_index_ob].pin;
    R_PFS->PORT[pin_d >> 8].PIN[pin_d & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;

Serial.print(" POR");
Serial.print(pin_b >> 8, DEC);
Serial.print(" PIN");
Serial.print(pin_b & 0xFF, DEC);
Serial.print(" HIG");

Serial.print(" POR");
Serial.print(pin_d >> 8, DEC);
Serial.print(" PIN");
Serial.print(pin_d & 0xFF, DEC);
Serial.print(" LOW");

Serial.println();
    
  }
}

static uint32_t reverse(uint32_t x)
{
    x = ((x >> 1) & 0x55555555u) | ((x & 0x55555555u) << 1);
    x = ((x >> 2) & 0x33333333u) | ((x & 0x33333333u) << 2);
    x = ((x >> 4) & 0x0f0f0f0fu) | ((x & 0x0f0f0f0fu) << 4);
    x = ((x >> 8) & 0x00ff00ffu) | ((x & 0x00ff00ffu) << 8);
    x = ((x >> 16) & 0xffffu) | ((x & 0xffffu) << 16);
    return x;
}

// TODO: this is dangerous, use with care
#define loadSequence(frames)                loadWrapper(frames, sizeof(frames))
#define renderBitmap(bitmap, rows, columns) loadPixels(&bitmap[0][0], rows*columns)

static uint8_t __attribute__((aligned)) framebuffer[NUM_LEDS / 8];

//pw class ArduinoLEDMatrix
class ArduinoNEONMatrix
#ifdef MATRIX_WITH_ARDUINOGRAPHICS
    : public ArduinoGraphics
#endif
     {

public:
//pw ArduinoLEDMatrix()
    ArduinoNEONMatrix()
    #ifdef MATRIX_WITH_ARDUINOGRAPHICS
        : ArduinoGraphics(canvasWidth, canvasHeight)
    #endif
    {}
    // TODO: find a better name
    // autoscroll will be slower than calling next() at precise times
    void autoscroll(uint32_t interval_ms) {
        _interval = interval_ms;
    }
    void on(size_t pin) {
        turnLed(pin, true);
    }
    void off(size_t pin) {
        turnLed(pin, false);
    }
    int begin() {
        bool rv = true;
        uint8_t type;
        int8_t ch = FspTimer::get_available_timer(type);
        if(ch == -1) {
            return false;
        }
        // TODO: avoid passing "this" argument to remove autoscroll
        rv &= _ledTimer.begin(TIMER_MODE_PERIODIC, type, ch, 10000.0, 50.0, turnOnLedISR, this);
        rv &= _ledTimer.setup_overflow_irq();
        rv &= _ledTimer.open();
        rv &= _ledTimer.start();
        return rv;
    }
    void next() {
        uint32_t frame[3];
        frame[0] = reverse(*(_frames+(_currentFrame*4)+0));
        frame[1] = reverse(*(_frames+(_currentFrame*4)+1));
        frame[2] = reverse(*(_frames+(_currentFrame*4)+2));
        _interval = *(_frames+(_currentFrame*4)+3);
        _currentFrame = (_currentFrame + 1) % _framesCount;
        if(_currentFrame == 0){
            if(!_loop){
                _interval = 0;
            }
            if(_callBack != nullptr){
                _callBack();
            }
            _sequenceDone = true;
        }
        memcpy(framebuffer, (uint32_t*)frame, sizeof(frame));
    }
    void loadFrame(const uint32_t buffer[3]){
        uint32_t tempBuffer[][4] = {{
            buffer[0], buffer[1], buffer[2], 0
        }};
        loadSequence(tempBuffer);
        next();
        _interval = 0;
    }
    void renderFrame(uint8_t frameNumber){
        _currentFrame = frameNumber % _framesCount;
        next();
        _interval = 0;
    }
    void play(bool loop = false){
        _loop = loop;
        _sequenceDone = false;
        next();
    }
    bool sequenceDone(){
        if(_sequenceDone){
            _sequenceDone = false;
            return true;
        }
        return false;
    }

    void loadPixels(uint8_t *arr, size_t size){
        uint32_t partialBuffer = 0;
        uint8_t pixelIndex = 0;
        uint8_t *frameP = arr;
        uint32_t *frameHolderP = _frameHolder;
        while (pixelIndex < size) {
            partialBuffer |= *frameP++;
            if ((pixelIndex + 1) % 32 == 0) {
                *(frameHolderP++) = partialBuffer;
            }
            partialBuffer = partialBuffer << 1;
            pixelIndex++;
        }
        loadFrame(_frameHolder);
    };

    void loadWrapper(const uint32_t frames[][4], uint32_t howMany) {
        _currentFrame = 0;
        _frames = (uint32_t*)frames;
        _framesCount = (howMany / 4) / sizeof(uint32_t);
    }
    // WARNING: callbacks are fired from ISR. The execution time will be limited.
    void setCallback(voidFuncPtr callBack){
        _callBack = callBack;
    }

    void clear() {
        const uint32_t fullOff[] = {
        	0x00000000,
        	0x00000000,
        	0x00000000
        };
        loadFrame(fullOff);
#ifdef MATRIX_WITH_ARDUINOGRAPHICS
        memset(_canvasBuffer, 0, sizeof(_canvasBuffer));
#endif
    }


#ifdef MATRIX_WITH_ARDUINOGRAPHICS
    virtual void set(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
      if (y >= canvasHeight || x >= canvasWidth || y < 0 || x < 0) {
        return;
      }
      // the r parameter is (mis)used to set the character to draw with
      _canvasBuffer[y][x] = (r | g | b) > 0 ? 1 : 0;
    }

    void endText(int scrollDirection = NO_SCROLL) {
      ArduinoGraphics::endText(scrollDirection);
      renderBitmap(_canvasBuffer, canvasHeight, canvasWidth);
    }

    // display the drawing
    void endDraw() {
      ArduinoGraphics::endDraw();
      renderBitmap(_canvasBuffer, canvasHeight, canvasWidth);
    }

  private:
    static const byte canvasWidth = 12;
    static const byte canvasHeight = 8;
    uint8_t _canvasBuffer[canvasHeight][canvasWidth] = {{0}};
#endif

private:
    int _currentFrame = 0;
    uint32_t _frameHolder[3];
    uint32_t* _frames;
    uint32_t _framesCount;
    uint32_t _interval = 0;
    uint32_t _lastInterval = 0;
    bool _loop = false;
    FspTimer _ledTimer;
    bool _sequenceDone = false;
    voidFuncPtr _callBack;

    static void turnOnLedISR(timer_callback_args_t *arg) {
        static volatile int i_isr = 0;
        turnLed(i_isr, ((framebuffer[i_isr >> 3] & (1 << (i_isr % 8))) != 0));
        i_isr = (i_isr + 1) % NUM_LEDS;
        if (arg != nullptr && arg->p_context != nullptr) {
//           ArduinoLEDMatrix* _m = (ArduinoLEDMatrix*)arg->p_context;
            ArduinoNEONMatrix* _m = (ArduinoNEONMatrix*)arg->p_context;

            if (_m->_interval != 0 && millis() - _m->_lastInterval > _m->_interval) {
                _m->next();
                _m->_lastInterval = millis();
            }
        }
    }
};

Screenshot of the connection mapping and desired port & pin values for idx 0 and 1.
image