I need help speeding up my led matrix functions

No, what I meant was a hardware timer, in this case timer 2, this from the sketch I supplied:

ISR(TIMER2_COMPA_vect) {
  // timer2 interrupt service routine
  // multiplex display - called from timer
  . . .
  . . .
}


void setup() {

 . . . 
 . . .

 // set up timer 2 to call multiplex routine with a 250us period
  cli() ;
  TCNT2 = 0 ;
  TCCR2A = _BV( WGM21 ) ; //  CTC mode
  TCCR2B = _BV( CS22 )  ; //  PS /64,
  TIMSK2 = _BV( OCIE2A); // interrupt TIMER2_COMPA_vect
  OCR2A = 62 ;  // ~4kHz @ PS /64
  sei() ;
}

It uses timer 2 to create an interrupt every 250us. In the interrupt service routine, the next column of the display is scanned, in an endless cycle.
The code at the end of post #17 should display the test string but may again need tweaking.

I fixed the main issue with the operator new[] function, so now the code is running as better as ever. But I may still have issues concerning the timer and direct port manipulation, so I won't close the question right away.

Also, if I change the bit size of my size variable to 16 bits, nothing works anymore. But it works fine at 8 bits.

[EDIT]: here is the code:

/*
  24 x 5 led matrix
  Version 2.2
  by: ArduMasterPro44
*/

#define Data1 3
#define Clock1 4
#define Data2 5
#define Clock2 6
#define Latch 7

uint8_t alphabet[95][4] = {
  { 3, 0x00, 0x00, 0x00 },  // " "
  { 1, 0x17, 0x00, 0x00 },  // "!"
  { 3, 0x03, 0x00, 0x03 },  // """
  { 3, 0x1F, 0x0A, 0x1F },  // "#"
  { 3, 0x0B, 0x1F, 0x0D },  // "$"
  { 3, 0x19, 0x04, 0x13 },  // "%"
  { 3, 0x0A, 0x15, 0x1A },  // "&"
  { 1, 0x03, 0x00, 0x00 },  // "'"
  { 2, 0x0E, 0x11, 0x00 },  // "("
  { 2, 0x11, 0x0E, 0x00 },  // ")"
  { 3, 0x05, 0x02, 0x05 },  // "*"
  { 3, 0x04, 0x0E, 0x04 },  // "+"
  { 1, 0x18, 0x00, 0x00 },  // ","
  { 3, 0x04, 0x04, 0x04 },  // "-"
  { 1, 0x10, 0x00, 0x00 },  // "."
  { 3, 0x18, 0x04, 0x03 },  // "/"
  { 3, 0x1F, 0x11, 0x1F },  // "0"
  { 3, 0x12, 0x1F, 0x10 },  // "1"
  { 3, 0x19, 0x15, 0x12 },  // "2"
  { 3, 0x11, 0x15, 0x1F },  // "3"
  { 3, 0x07, 0x04, 0x1F },  // "4"
  { 3, 0x17, 0x15, 0x1D },  // "5"
  { 3, 0x1E, 0x15, 0x1D },  // "6"
  { 3, 0x01, 0x01, 0x1F },  // "7"
  { 3, 0x1F, 0x15, 0x1F },  // "8"
  { 3, 0x17, 0x15, 0x1F },  // "9"
  { 1, 0x0A, 0x00, 0x00 },  // ":"
  { 1, 0x1A, 0x00, 0x00 },  // ";"
  { 3, 0x04, 0x0A, 0x11 },  // "<"
  { 3, 0x0A, 0x0A, 0x0A },  // "="
  { 3, 0x11, 0x0A, 0x04 },  // ">"
  { 3, 0x01, 0x15, 0x03 },  // "?"
  { 3, 0x0E, 0x11, 0x17 },  // "@"
  { 3, 0x1F, 0x05, 0x1F },  // "A"
  { 3, 0x1F, 0x15, 0x1B },  // "B"
  { 3, 0x1F, 0x11, 0x11 },  // "C"
  { 3, 0x1F, 0x11, 0x0E },  // "D"
  { 3, 0x1F, 0x15, 0x11 },  // "E"
  { 3, 0x1F, 0x05, 0x01 },  // "F"
  { 3, 0x1F, 0x11, 0x1D },  // "G"
  { 3, 0x1F, 0x04, 0x1F },  // "H"
  { 3, 0x11, 0x1F, 0x11 },  // "I"
  { 3, 0x18, 0x10, 0x1F },  // "J"
  { 3, 0x1F, 0x04, 0x1B },  // "K"
  { 3, 0x1F, 0x10, 0x10 },  // "L"
  { 3, 0x1F, 0x06, 0x1F },  // "M"
  { 3, 0x1F, 0x01, 0x1F },  // "N"
  { 3, 0x1F, 0x11, 0x1F },  // "O"
  { 3, 0x1F, 0x05, 0x07 },  // "P"
  { 3, 0x0F, 0x19, 0x0F },  // "Q"
  { 3, 0x1F, 0x05, 0x1A },  // "R"
  { 3, 0x17, 0x15, 0x1D },  // "S"
  { 3, 0x01, 0x1F, 0x01 },  // "T"
  { 3, 0x1F, 0x10, 0x1F },  // "U"
  { 3, 0x0F, 0x10, 0x1F },  // "V"
  { 3, 0x1F, 0x0C, 0x1F },  // "W"
  { 3, 0x1B, 0x04, 0x1B },  // "X"
  { 3, 0x07, 0x1C, 0x07 },  // "Y"
  { 3, 0x19, 0x15, 0x13 },  // "Z"
  { 2, 0x1F, 0x11, 0x00 },  // "["
  { 3, 0x03, 0x04, 0x18 },  // "\"
  { 2, 0x11, 0x1F, 0x00 },  // "]"
  { 3, 0x02, 0x01, 0x02 },  // "^"
  { 3, 0x10, 0x10, 0x10 },  // "_"
  { 2, 0x01, 0x02, 0x00 },  // "`"
  { 3, 0x0c, 0x12, 0x1E },  // "a"
  { 3, 0x1F, 0x12, 0x0C },  // "b"
  { 3, 0x1E, 0x12, 0x12 },  // "c"
  { 3, 0x0C, 0x12, 0x1F },  // "d"
  { 3, 0x0C, 0x1A, 0x14 },  // "e"
  { 3, 0x04, 0x1F, 0x05 },  // "f"
  { 3, 0x12, 0x15, 0x0E },  // "g"
  { 3, 0x1F, 0x04, 0x18 },  // "h"
  { 1, 0x1D, 0x00, 0x00 },  // "i"
  { 2, 0x10, 0x0D, 0x00 },  // "j"
  { 3, 0x1F, 0x04, 0x1A },  // "k"
  { 1, 0x1F, 0x00, 0x00 },  // "l"
  { 3, 0x1E, 0x04, 0x1E },  // "m"
  { 3, 0x1E, 0x02, 0x1E },  // "n"
  { 3, 0x1E, 0x12, 0x1E },  // "o"
  { 3, 0x1F, 0x09, 0x06 },  // "p"
  { 3, 0x06, 0x09, 0x1F },  // "q"
  { 3, 0x1E, 0x02, 0x04 },  // "r"
  { 3, 0x14, 0x16, 0x0A },  // "s"
  { 3, 0x02, 0x0F, 0x12 },  // "t"
  { 3, 0x1E, 0x10, 0x1E },  // "u"
  { 3, 0x0E, 0x10, 0x0E },  // "v"
  { 3, 0x1E, 0x08, 0x1E },  // "w"
  { 3, 0x1A, 0x04, 0x1A },  // "x"
  { 3, 0x17, 0x14, 0x0F },  // "y"
  { 3, 0x1A, 0x12, 0x16 },  // "z"
  { 3, 0x04, 0x1F, 0x11 },  // "{"
  { 1, 0x1F, 0x00, 0x00 },  // "|"
  { 3, 0x11, 0x1F, 0x04 },  // "}"
  { 3, 0x04, 0x0C, 0x08 }   // "~"
};

const uint16_t delayTime = 100;
const uint8_t rows = 24;
const uint8_t columns = 5;
uint32_t time;
char message[] = "This is a test string!";
uint16_t size = 0;
uint8_t length = strlen(message);
uint8_t * bitMap;

void setup() {
  pinMode(Data1, OUTPUT);
  pinMode(Clock1, OUTPUT);
  pinMode(Data2, OUTPUT);
  pinMode(Clock2, OUTPUT);
  pinMode(Latch, OUTPUT);
  Serial.begin(9600);

  for (uint8_t i = 0; i < length; i++) {  // Loop for every character in the message
    size += alphabet[message[i] - 32][0];
    if (i < length - 1) {  // Check if it isn't the last character of the message
      size++;
    }
  }

  bitMap = new uint8_t[size];

  uint16_t x = 0;                                              // Keeps track of the X-Axis bit
  for (uint8_t i = 0; i < length; i++) {                 // Loop for every character in the message
    for (uint8_t j = 0; j < alphabet[message[i] - 32][0]; j++) {  // Loop for every byte in the character
      bitMap[x] = alphabet[message[i] - 32][j + 1];               // Store the byte in the bitMap[] array
      x++;
    }
    if (i < length - 1) {  // Check if it isn't the last character of the message
      bitMap[x] = 0;            // Store a 0x00 byte in the bitMap[] array
      x++;
    }
  }
  Serial.println(size);
}

void loop() {

  int16_t i = -24;
  while (i < size) {
    if (millis() - time >= delayTime) {
      i++;
      time = millis();
    }
    shiftText(i, rows, columns);
  }
}

void shiftText( int16_t idx, uint8_t bitsX, uint8_t bitsY) {
  for (uint8_t i = 0; i < bitsX; i++) {
    shiftByte(Data1, Data2, Clock1, Clock2, Latch, MSBFIRST, LSBFIRST, i, stringToChar(i + idx), bitsX, bitsY);  // Shift the byte to the matrix
  }
}

uint8_t stringToChar(int16_t bit) {  // Converts one peice of the character according to the bit variable
  if (bit >= size) {
    return 0x00;
  } else if (bit < 0) {
    return 0x00;
  } else {
    return bitMap[bit];
  }
}

void shiftByte(uint8_t data1, uint8_t data2, uint8_t clock1, uint8_t clock2, uint8_t latch, uint8_t bitOrderX, uint8_t bitOrderY, uint8_t pin, uint8_t val, uint8_t bitsX, uint8_t bitsY) {
  digitalWrite(latch, LOW);
  shiftBits(data1, clock1, bitOrderX, bit(pin), bitsX);
  shiftBits(data2, clock2, bitOrderY, ~val, bitsY);
  digitalWrite(latch, HIGH);
}

void shiftBits(uint8_t data, uint8_t clock, uint8_t bitOrder, uint32_t value, uint8_t bits) {
  for (uint8_t i = 0; i < bits; i++) {
    if (bitOrder == LSBFIRST) {
      digitalWrite(data, value & 1);
      value >>= 1;
    } else if (bitOrder == MSBFIRST) {
      digitalWrite(data, (value & bit(bits - 1)) != 0);
      value <<= 1;
    }
    digitalWrite(clock, HIGH);
    digitalWrite(clock, LOW);
  }
}

And obviously, I will add the next features later ( timer and direct port manipulation).

I have some ideas that could both simplify and speed up this code, and remove the need for direct port manipulation, hardware timers or allocating memory for a buffer, dynamic or static. It would be pretty much a total re-write, however, so let me know if you are at all interested and prepared to work through it with my help.

There is certainly plenty of scope for optimisation even starting with the hardware and using the SPI peripheral or even a LED driver chip/chips (Max7221, HT1632 etc.) or pushing out one row at a time instead of one column (need 19 more resistors). With the same hardware, my preference would be to use a timer to drive the multiplexing if there is going to be any heavy or blocking activity in the loop which could result in noticeable flicker.

The 1:24 multiplex ratio certainly does make timing tricky. To avoid flicker, the whole display would need to be scanned at least 50 times per second, which means less than 1ms per column. It also limits the current, since all 5 LEDs in a column are powered by a single '595 pin, which has a 35mA limit.

But if @ardumasterpro44 insists on sticking to this strategy, I was going to suggest that it is not necessary to shift out 24 + 5 bits to illuminate the LEDs in a column. It can be done by shifting only 1 + 5 bits per column, because the columns are lit in left-to-right sequence.

With the current code, illuminating a single column of LEDs probably requires about 90 calls to digitalWrite(), which take around 3.4us each, over 300us in total, plus the other code lines. To achieve a good flicker-free refresh rate, you might only have 600~800us for each column in total, so half the time could be taken up by digitalWrite().

If the code was more efficient, only about 20 calls to digitalWrite() would be needed per column, which would take only ~70us.

Maybe I don't understand what you have said correctly, but I see no way of getting away from the fact that on every multiplexing cycle you have to push out 32 bits to the shift register chain. That has the effect of simply illuminating the next column, at least as I see it. I gave him a complete solution at the end of post #17 using quite a simple strategy (the equivalent of 4 digitalWrite() operations per (one column) cycle). As I said, I believe the timer solution is good to isolate the display multiplexing from the activities in the loop(). However, the benefits of that depend on what is happening in the loop(). Further, if he says he wants later to vary the brightness of the display, then one possibility is to use another timer to, within the middle of a multiplexing cycle, switch the leds off prematurely. A cruder solution would be to simply insert empty cycles within the multiplexing. Anyway, there are always many ways of achieving similar results and with multiplexing you can very quickly try different solutions and see how these perform.

@ardumasterpro44's circuit has two clock and two data pins. One pair for the 24-bit (3x8) shift register driving the columns and the other pair for the 5-bit (8 but 3 unused) shift register driving the rows. Only the latch pin is common to both chains.

For the 24-bit register, to move on to the next column to the right, only one bit needs to be shifted in. All the other bits move one place to the right.

I see what you mean, so your saying that I would only need two write cycles per shift for the x registers?

2 or 3 digitalWrite() to shift the "1" one place along, ready for the next column

Yes of course. I see what you mean now. Excellent idea.

I'm currently trying to make it, but my first row is dimly on when it shouldn't.

As always, if we can't see the changes you made to the code, we can't help.

Oops, sorry. Here are the changes:

/*
  24 x 5 led matrix
  Version 2.2
  by: ArduMasterPro44
*/

#define Data1 3
#define Clock1 4
#define Data2 5
#define Clock2 6
#define Latch 7

uint8_t alphabet[95][4] = {
  { 3, 0x00, 0x00, 0x00 },  // " "
  { 1, 0x17, 0x00, 0x00 },  // "!"
  { 3, 0x03, 0x00, 0x03 },  // """
  { 3, 0x1F, 0x0A, 0x1F },  // "#"
  { 3, 0x0B, 0x1F, 0x0D },  // "$"
  { 3, 0x19, 0x04, 0x13 },  // "%"
  { 3, 0x0A, 0x15, 0x1A },  // "&"
  { 1, 0x03, 0x00, 0x00 },  // "'"
  { 2, 0x0E, 0x11, 0x00 },  // "("
  { 2, 0x11, 0x0E, 0x00 },  // ")"
  { 3, 0x05, 0x02, 0x05 },  // "*"
  { 3, 0x04, 0x0E, 0x04 },  // "+"
  { 1, 0x18, 0x00, 0x00 },  // ","
  { 3, 0x04, 0x04, 0x04 },  // "-"
  { 1, 0x10, 0x00, 0x00 },  // "."
  { 3, 0x18, 0x04, 0x03 },  // "/"
  { 3, 0x1F, 0x11, 0x1F },  // "0"
  { 3, 0x12, 0x1F, 0x10 },  // "1"
  { 3, 0x19, 0x15, 0x12 },  // "2"
  { 3, 0x11, 0x15, 0x1F },  // "3"
  { 3, 0x07, 0x04, 0x1F },  // "4"
  { 3, 0x17, 0x15, 0x1D },  // "5"
  { 3, 0x1E, 0x15, 0x1D },  // "6"
  { 3, 0x01, 0x01, 0x1F },  // "7"
  { 3, 0x1F, 0x15, 0x1F },  // "8"
  { 3, 0x17, 0x15, 0x1F },  // "9"
  { 1, 0x0A, 0x00, 0x00 },  // ":"
  { 1, 0x1A, 0x00, 0x00 },  // ";"
  { 3, 0x04, 0x0A, 0x11 },  // "<"
  { 3, 0x0A, 0x0A, 0x0A },  // "="
  { 3, 0x11, 0x0A, 0x04 },  // ">"
  { 3, 0x01, 0x15, 0x03 },  // "?"
  { 3, 0x0E, 0x11, 0x17 },  // "@"
  { 3, 0x1F, 0x05, 0x1F },  // "A"
  { 3, 0x1F, 0x15, 0x1B },  // "B"
  { 3, 0x1F, 0x11, 0x11 },  // "C"
  { 3, 0x1F, 0x11, 0x0E },  // "D"
  { 3, 0x1F, 0x15, 0x11 },  // "E"
  { 3, 0x1F, 0x05, 0x01 },  // "F"
  { 3, 0x1F, 0x11, 0x1D },  // "G"
  { 3, 0x1F, 0x04, 0x1F },  // "H"
  { 3, 0x11, 0x1F, 0x11 },  // "I"
  { 3, 0x18, 0x10, 0x1F },  // "J"
  { 3, 0x1F, 0x04, 0x1B },  // "K"
  { 3, 0x1F, 0x10, 0x10 },  // "L"
  { 3, 0x1F, 0x06, 0x1F },  // "M"
  { 3, 0x1F, 0x01, 0x1F },  // "N"
  { 3, 0x1F, 0x11, 0x1F },  // "O"
  { 3, 0x1F, 0x05, 0x07 },  // "P"
  { 3, 0x0F, 0x19, 0x0F },  // "Q"
  { 3, 0x1F, 0x05, 0x1A },  // "R"
  { 3, 0x17, 0x15, 0x1D },  // "S"
  { 3, 0x01, 0x1F, 0x01 },  // "T"
  { 3, 0x1F, 0x10, 0x1F },  // "U"
  { 3, 0x0F, 0x10, 0x1F },  // "V"
  { 3, 0x1F, 0x0C, 0x1F },  // "W"
  { 3, 0x1B, 0x04, 0x1B },  // "X"
  { 3, 0x07, 0x1C, 0x07 },  // "Y"
  { 3, 0x19, 0x15, 0x13 },  // "Z"
  { 2, 0x1F, 0x11, 0x00 },  // "["
  { 3, 0x03, 0x04, 0x18 },  // "\"
  { 2, 0x11, 0x1F, 0x00 },  // "]"
  { 3, 0x02, 0x01, 0x02 },  // "^"
  { 3, 0x10, 0x10, 0x10 },  // "_"
  { 2, 0x01, 0x02, 0x00 },  // "`"
  { 3, 0x0c, 0x12, 0x1E },  // "a"
  { 3, 0x1F, 0x12, 0x0C },  // "b"
  { 3, 0x1E, 0x12, 0x12 },  // "c"
  { 3, 0x0C, 0x12, 0x1F },  // "d"
  { 3, 0x0C, 0x1A, 0x14 },  // "e"
  { 3, 0x04, 0x1F, 0x05 },  // "f"
  { 3, 0x12, 0x15, 0x0E },  // "g"
  { 3, 0x1F, 0x04, 0x18 },  // "h"
  { 1, 0x1D, 0x00, 0x00 },  // "i"
  { 2, 0x10, 0x0D, 0x00 },  // "j"
  { 3, 0x1F, 0x04, 0x1A },  // "k"
  { 1, 0x1F, 0x00, 0x00 },  // "l"
  { 3, 0x1E, 0x04, 0x1E },  // "m"
  { 3, 0x1E, 0x02, 0x1E },  // "n"
  { 3, 0x1E, 0x12, 0x1E },  // "o"
  { 3, 0x1F, 0x09, 0x06 },  // "p"
  { 3, 0x06, 0x09, 0x1F },  // "q"
  { 3, 0x1E, 0x02, 0x04 },  // "r"
  { 3, 0x14, 0x16, 0x0A },  // "s"
  { 3, 0x02, 0x0F, 0x12 },  // "t"
  { 3, 0x1E, 0x10, 0x1E },  // "u"
  { 3, 0x0E, 0x10, 0x0E },  // "v"
  { 3, 0x1E, 0x08, 0x1E },  // "w"
  { 3, 0x1A, 0x04, 0x1A },  // "x"
  { 3, 0x17, 0x14, 0x0F },  // "y"
  { 3, 0x1A, 0x12, 0x16 },  // "z"
  { 3, 0x04, 0x1F, 0x11 },  // "{"
  { 1, 0x1F, 0x00, 0x00 },  // "|"
  { 3, 0x11, 0x1F, 0x04 },  // "}"
  { 3, 0x04, 0x0C, 0x08 }   // "~"
};

const uint16_t delayTime = 100;
const uint8_t rows = 24;
const uint8_t columns = 5;
uint32_t time;
char message[] = "This is a test string!";
uint8_t size = 0;
uint8_t length = strlen(message);
uint8_t* bitMap;

void setup() {
  pinMode(Data1, OUTPUT);
  pinMode(Clock1, OUTPUT);
  pinMode(Data2, OUTPUT);
  pinMode(Clock2, OUTPUT);
  pinMode(Latch, OUTPUT);
  Serial.begin(9600);

  for (uint8_t i = 0; i < length; i++) {  // Loop for every character in the message
    size += alphabet[message[i] - 32][0];
    if (i < length - 1) {  // Check if it isn't the last character of the message
      size++;
    }
  }

  bitMap = new uint8_t[size];

  uint16_t x = 0;                                                 // Keeps track of the X-Axis bit
  for (uint8_t i = 0; i < length; i++) {                          // Loop for every character in the message
    for (uint8_t j = 0; j < alphabet[message[i] - 32][0]; j++) {  // Loop for every byte in the character
      bitMap[x] = alphabet[message[i] - 32][j + 1];               // Store the byte in the bitMap[] array
      x++;
    }
    if (i < length - 1) {  // Check if it isn't the last character of the message
      bitMap[x] = 0;       // Store a 0x00 byte in the bitMap[] array
      x++;
    }
  }
  Serial.println(size);
}

void loop() {

  int16_t i = -24;
  while (i < size) {
    if (millis() - time >= delayTime) {
      i++;
      time = millis();
    }
    shiftText(i, rows, columns);
  }
}

void shiftText(int16_t idx, uint8_t bitsX, uint8_t bitsY) {
  digitalWrite(Latch, LOW);
  digitalWrite(Data1, HIGH);
  digitalWrite(Clock1, HIGH);
  digitalWrite(Clock1, LOW);
  digitalWrite(Data1, LOW);
  digitalWrite(Latch, HIGH);
  for (uint8_t i = 0; i < bitsX; i++) {
    digitalWrite(Latch, LOW);
    if (i != 0) {
      digitalWrite(Clock1, HIGH);
      digitalWrite(Clock1, LOW);
    }

    shiftBits(Data2, Clock2, LSBFIRST, ~stringToChar(i + idx), bitsY);
    digitalWrite(Latch, HIGH);  // Shift the byte to the matrix
    delay(10);
  }
}

uint8_t stringToChar(int16_t bit) {  // Converts one peice of the character according to the bit variable
  if (bit >= size) {
    return 0x00;
  } else if (bit < 0) {
    return 0x00;
  } else {
    return bitMap[bit];
  }
}

void shiftBits(uint8_t data, uint8_t clock, uint8_t bitOrder, uint32_t value, uint8_t bits) {
  for (uint8_t i = 0; i < bits; i++) {
    if (bitOrder == LSBFIRST) {
      digitalWrite(data, value & 1);
      value >>= 1;
    } else if (bitOrder == MSBFIRST) {
      digitalWrite(data, (value & bit(bits - 1)) != 0);
      value <<= 1;
    }
    digitalWrite(clock, HIGH);
    digitalWrite(clock, LOW);
  }
}

(The shiftText() function)

Never mind, I just fixed it:

/*
  24 x 5 led matrix
  Version 2.2
  by: ArduMasterPro44
*/

#define Data1 3
#define Clock1 4
#define Data2 5
#define Clock2 6
#define Latch 7

uint8_t alphabet[95][4] = {
  { 3, 0x00, 0x00, 0x00 },  // " "
  { 1, 0x17, 0x00, 0x00 },  // "!"
  { 3, 0x03, 0x00, 0x03 },  // """
  { 3, 0x1F, 0x0A, 0x1F },  // "#"
  { 3, 0x0B, 0x1F, 0x0D },  // "$"
  { 3, 0x19, 0x04, 0x13 },  // "%"
  { 3, 0x0A, 0x15, 0x1A },  // "&"
  { 1, 0x03, 0x00, 0x00 },  // "'"
  { 2, 0x0E, 0x11, 0x00 },  // "("
  { 2, 0x11, 0x0E, 0x00 },  // ")"
  { 3, 0x05, 0x02, 0x05 },  // "*"
  { 3, 0x04, 0x0E, 0x04 },  // "+"
  { 1, 0x18, 0x00, 0x00 },  // ","
  { 3, 0x04, 0x04, 0x04 },  // "-"
  { 1, 0x10, 0x00, 0x00 },  // "."
  { 3, 0x18, 0x04, 0x03 },  // "/"
  { 3, 0x1F, 0x11, 0x1F },  // "0"
  { 3, 0x12, 0x1F, 0x10 },  // "1"
  { 3, 0x19, 0x15, 0x12 },  // "2"
  { 3, 0x11, 0x15, 0x1F },  // "3"
  { 3, 0x07, 0x04, 0x1F },  // "4"
  { 3, 0x17, 0x15, 0x1D },  // "5"
  { 3, 0x1E, 0x15, 0x1D },  // "6"
  { 3, 0x01, 0x01, 0x1F },  // "7"
  { 3, 0x1F, 0x15, 0x1F },  // "8"
  { 3, 0x17, 0x15, 0x1F },  // "9"
  { 1, 0x0A, 0x00, 0x00 },  // ":"
  { 1, 0x1A, 0x00, 0x00 },  // ";"
  { 3, 0x04, 0x0A, 0x11 },  // "<"
  { 3, 0x0A, 0x0A, 0x0A },  // "="
  { 3, 0x11, 0x0A, 0x04 },  // ">"
  { 3, 0x01, 0x15, 0x03 },  // "?"
  { 3, 0x0E, 0x11, 0x17 },  // "@"
  { 3, 0x1F, 0x05, 0x1F },  // "A"
  { 3, 0x1F, 0x15, 0x1B },  // "B"
  { 3, 0x1F, 0x11, 0x11 },  // "C"
  { 3, 0x1F, 0x11, 0x0E },  // "D"
  { 3, 0x1F, 0x15, 0x11 },  // "E"
  { 3, 0x1F, 0x05, 0x01 },  // "F"
  { 3, 0x1F, 0x11, 0x1D },  // "G"
  { 3, 0x1F, 0x04, 0x1F },  // "H"
  { 3, 0x11, 0x1F, 0x11 },  // "I"
  { 3, 0x18, 0x10, 0x1F },  // "J"
  { 3, 0x1F, 0x04, 0x1B },  // "K"
  { 3, 0x1F, 0x10, 0x10 },  // "L"
  { 3, 0x1F, 0x06, 0x1F },  // "M"
  { 3, 0x1F, 0x01, 0x1F },  // "N"
  { 3, 0x1F, 0x11, 0x1F },  // "O"
  { 3, 0x1F, 0x05, 0x07 },  // "P"
  { 3, 0x0F, 0x19, 0x0F },  // "Q"
  { 3, 0x1F, 0x05, 0x1A },  // "R"
  { 3, 0x17, 0x15, 0x1D },  // "S"
  { 3, 0x01, 0x1F, 0x01 },  // "T"
  { 3, 0x1F, 0x10, 0x1F },  // "U"
  { 3, 0x0F, 0x10, 0x1F },  // "V"
  { 3, 0x1F, 0x0C, 0x1F },  // "W"
  { 3, 0x1B, 0x04, 0x1B },  // "X"
  { 3, 0x07, 0x1C, 0x07 },  // "Y"
  { 3, 0x19, 0x15, 0x13 },  // "Z"
  { 2, 0x1F, 0x11, 0x00 },  // "["
  { 3, 0x03, 0x04, 0x18 },  // "\"
  { 2, 0x11, 0x1F, 0x00 },  // "]"
  { 3, 0x02, 0x01, 0x02 },  // "^"
  { 3, 0x10, 0x10, 0x10 },  // "_"
  { 2, 0x01, 0x02, 0x00 },  // "`"
  { 3, 0x0c, 0x12, 0x1E },  // "a"
  { 3, 0x1F, 0x12, 0x0C },  // "b"
  { 3, 0x1E, 0x12, 0x12 },  // "c"
  { 3, 0x0C, 0x12, 0x1F },  // "d"
  { 3, 0x0C, 0x1A, 0x14 },  // "e"
  { 3, 0x04, 0x1F, 0x05 },  // "f"
  { 3, 0x12, 0x15, 0x0E },  // "g"
  { 3, 0x1F, 0x04, 0x18 },  // "h"
  { 1, 0x1D, 0x00, 0x00 },  // "i"
  { 2, 0x10, 0x0D, 0x00 },  // "j"
  { 3, 0x1F, 0x04, 0x1A },  // "k"
  { 1, 0x1F, 0x00, 0x00 },  // "l"
  { 3, 0x1E, 0x04, 0x1E },  // "m"
  { 3, 0x1E, 0x02, 0x1E },  // "n"
  { 3, 0x1E, 0x12, 0x1E },  // "o"
  { 3, 0x1F, 0x09, 0x06 },  // "p"
  { 3, 0x06, 0x09, 0x1F },  // "q"
  { 3, 0x1E, 0x02, 0x04 },  // "r"
  { 3, 0x14, 0x16, 0x0A },  // "s"
  { 3, 0x02, 0x0F, 0x12 },  // "t"
  { 3, 0x1E, 0x10, 0x1E },  // "u"
  { 3, 0x0E, 0x10, 0x0E },  // "v"
  { 3, 0x1E, 0x08, 0x1E },  // "w"
  { 3, 0x1A, 0x04, 0x1A },  // "x"
  { 3, 0x17, 0x14, 0x0F },  // "y"
  { 3, 0x1A, 0x12, 0x16 },  // "z"
  { 3, 0x04, 0x1F, 0x11 },  // "{"
  { 1, 0x1F, 0x00, 0x00 },  // "|"
  { 3, 0x11, 0x1F, 0x04 },  // "}"
  { 3, 0x04, 0x0C, 0x08 }   // "~"
};

const uint16_t delayTime = 100;
const uint8_t rows = 24;
const uint8_t columns = 5;
uint32_t time;
char message[] = "This is a test string!";
uint8_t size = 0;
uint8_t length = strlen(message);
uint8_t* bitMap;

void setup() {
  pinMode(Data1, OUTPUT);
  pinMode(Clock1, OUTPUT);
  pinMode(Data2, OUTPUT);
  pinMode(Clock2, OUTPUT);
  pinMode(Latch, OUTPUT);
  Serial.begin(9600);

  for (uint8_t i = 0; i < length; i++) {  // Loop for every character in the message
    size += alphabet[message[i] - 32][0];
    if (i < length - 1) {  // Check if it isn't the last character of the message
      size++;
    }
  }

  bitMap = new uint8_t[size];

  uint16_t x = 0;                                                 // Keeps track of the X-Axis bit
  for (uint8_t i = 0; i < length; i++) {                          // Loop for every character in the message
    for (uint8_t j = 0; j < alphabet[message[i] - 32][0]; j++) {  // Loop for every byte in the character
      bitMap[x] = alphabet[message[i] - 32][j + 1];               // Store the byte in the bitMap[] array
      x++;
    }
    if (i < length - 1) {  // Check if it isn't the last character of the message
      bitMap[x] = 0;       // Store a 0x00 byte in the bitMap[] array
      x++;
    }
  }
  Serial.println(size);
}

void loop() {

  int16_t i = -24;
  while (i < size) {
    if (millis() - time >= delayTime) {
      i++;
      time = millis();
    }
    shiftText(i, rows, columns);
  }
}

void shiftText(int16_t idx, uint8_t bitsX, uint8_t bitsY) {
  for (uint8_t i = 0; i < bitsX; i++) {
    digitalWrite(Latch, LOW);
    if (i == 0) {
      digitalWrite(Data1, HIGH);
      digitalWrite(Clock1, HIGH);
      digitalWrite(Clock1, LOW);
      digitalWrite(Data1, LOW);
    } else {
      digitalWrite(Clock1, HIGH);
      digitalWrite(Clock1, LOW);
    }
    shiftBits(Data2, Clock2, LSBFIRST, ~stringToChar(i + idx), bitsY);
    digitalWrite(Latch, HIGH);  // Shift the byte to the matrix
  }
}

uint8_t stringToChar(int16_t bit) {  // Converts one peice of the character according to the bit variable
  if (bit >= size) {
    return 0x00;
  } else if (bit < 0) {
    return 0x00;
  } else {
    return bitMap[bit];
  }
}

void shiftBits(uint8_t data, uint8_t clock, uint8_t bitOrder, uint32_t value, uint8_t bits) {
  for (uint8_t i = 0; i < bits; i++) {
    if (bitOrder == LSBFIRST) {
      digitalWrite(data, value & 1);
      value >>= 1;
    } else if (bitOrder == MSBFIRST) {
      digitalWrite(data, (value & bit(bits - 1)) != 0);
      value <<= 1;
    }
    digitalWrite(clock, HIGH);
    digitalWrite(clock, LOW);
  }
}

So now it takes 20 less write cycles per shiftText() calling.

But as I already asked, if I change uint8_t size = 0; to uint16_t size = 0; the code doesn't work anymore. Any idea why?

If by "write cycles" you mean calls to digitalWrite(), it should be more like 70 calls less.

I can't think of a reason right now. When you say "doesn't work" what does that mean, exactly?

Oh, my bad, before it was 5090, now it's 3410. So 5090 - 3410 = 1680 less!

Sorry, it's 1680 digitalWrite() calls less per shiftText() call.

And by doesn't work, I mean it keeps the display in a pattern forever until you reset it. And when you reset it, you see it scan very quickly until it stabilises into another pattern. For example, all the leds light up simultaneously.

I don't get that, those figures are way too large, and the saving is not enough as a percentage.

With the original code, shifting out 24+5 bits, at 3x digitalWrite() for each bit = 87 calls, plus 2 calls for the latch pin.

With the updated code, shifting out 1+5 bits = 18 calls plus 2 for the latch pin.

So that's a saving of 67 calls to digitalWrite().

To update the entire display, the saving would be 67x24 = 1608 calls (or 2088 - 480 = 1608). That's a 77% saving, not a 33% saving.

In these code lines

  int16_t i = -24;
  while (i < size) {

an int16_t is compared to a uint_16t instead of a uint_8t. But that should make no difference, should it?

We are missing something obvious here...

EDIT: hmmmm.... What does C/C++ do when you compare an unsigned value to a signed value? Does it convert both to signed, or both to unsigned?