LED matrix scrolling message display help

Hi all,
I have obtained a LED matrix module that consists of 12 x 5x7 LED modules with the columns being driven by MIC5891 shift registers and the rows are connected via a 74HC373 which is being used as a buffer as it's latches and output enable state lines are connected directly to 5V. I have no datasheet for this display and have managed to reverse engineer the connections.

I have tried driving the rows directly from the arduino pins and sending the column data via shiftout and SPI. I've also tried using a shift register to drive the rows and get the same result as if they are directly driven with 7 pins. The MIC5891 has it's latch pin permanently connected to 5V and the output enable pin is effectively being used as the latch. Unlike the HC595 the output enable pin must be high for data entry. The example code I've included uses SPI to send the column data and rows are driven using 7 arduino pins connected to the 'HC373.

What is happening is the first column in a group of 8 appears to be lit and the rest are ghosts of the 1st column. You can make out the blocks of 8 characters as they scroll across the display it's just that only one column is lit.

I understand how the display needs to be refreshed as in you send the data for row 1, enable it, wait then move to row 2 and turn off row 1 etc. However I can't see what I'm doing wrong. The example program I'm using seems to be widely available on Google and modified slightly so the code must work. The module is not faulty as it was removed from a working sign and I tried a demo program in standard AVR C designed for the 74HC164 which has no latch which worked but it was very dim.

I have some experience with C++ but by no means an expert. Can someone take a look at my code to see what I'm doing wrong?

I presume its because it's a display that is 7.5 modules wide due to them being 5x7 matrix modules. There are 8 shift registers on the board.

EDIT: I have changed the message to "." so I've determined it is repeating the data for every row. Tried this with shiftout to drive the rows and same result. Displaying a full stop results in just a single column being displayed.

/*////////////////////////////////////////////////////////////////////////////////
 * Arduino code to display scrolling characters on 4 or more 7x8 LED matrix.    *
 * The no: of matrices can be increased with a small change in code.            *
 * Comments are given in each statement for editing.                            *
 * Circuit designed and program modified by Soumit Das,B.Tech AEIE,RCCIIT Kol   *
 * Contact : soumitdas99@gmail.com                                              *
/*////////////////////////////////////////////////////////////////////////////////

#include <SPI.h>

char msg[] ="HELLO Adrian";//Change the text here.
int scrollspeed=20;//Set the scroll speed ( lower=faster)

int x;
int y;

//Columns
//int clockPin = 13; //Arduino pin connected to Clock Pin 11 of 74HC595
//int latchPin = 10; //Arduino pin connected to Latch Pin 12 of 74HC595
//int dataPin = 11;  //Arduino pin connected to Data Pin 14 of 74HC595

int ssPin = 10;

//Rows

const int rows[7] = { 2, 3, 4, 5, 6, 7, 8 };

//BITMAP
//Bits in this array represents one LED of the matrix
// 8 is # of rows, 6 is # of LED matrices (8x8 format)
byte bitmap[8][8]; 

int numZones = sizeof(bitmap) / 8; // One Zone refers to one 8 x 8 Matrix ( Group of 8 columns)
int maxZoneIndex = numZones-1;
int numCols = numZones * 8;

//FONT DEFENITION

***lookup table removed to get under 9000 character post limit)

void setup() 
{
 SPI.begin();
 //pinMode(latchPin, OUTPUT);
 //pinMode(clockPin, OUTPUT);
 //pinMode(dataPin, OUTPUT);
 pinMode (ssPin,OUTPUT);

 for (int rowPin = 0; rowPin < 7; rowPin++) 
 {
   pinMode(rows[rowPin], OUTPUT); // set row pins as outputs
 }
 
 //Clear bitmap
 for (int row = 0; row < 8; row++) 
 {
   for (int zone = 0; zone <= maxZoneIndex; zone++) 
   {
     bitmap[row][zone] = 0; 
   }
 }
}

//FUNCTIONS
// Displays bitmap array in the matrix
void RefreshDisplay()
{
 
  for (int row = 0; row < 7; row++) 
  {
   
   SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3));
   digitalWrite(ssPin,HIGH);
   int rowbit = 1 << row;
   digitalWrite(rows[rowbit], HIGH);
   
   //Shift out to each matrix
   for (int zone = maxZoneIndex; zone >= 0; zone--) 
   {    
     SPI.transfer(bitmap[row][zone]); 
     delayMicroseconds(1);
   }

   
   digitalWrite(rows[rowbit], LOW); // turn previous row off.
   digitalWrite(ssPin,LOW);
   SPI.endTransaction();
   

   //Wait
   delayMicroseconds(300); // increasing this delay makes display brighter
 }
}

// Converts row and colum to bitmap bit and turn it off/on
void Plot(int col, int row, bool isOn)
{
 int zone = col / 8;
 int colBitIndex = x % 8;
 byte colBit = 1 << colBitIndex;
 if (isOn)
   bitmap[row][zone] =  bitmap[y][zone] | colBit;
 else
   bitmap[row][zone] =  bitmap[y][zone] & (~colBit);
}
// Plot each character of the message one column at a time, updated the display, shift bitmap left.
void XProcess()
{
 for (int charIndex=0; charIndex < (sizeof(msg)-1); charIndex++)
 {
   int alphabetIndex = msg[charIndex] - ' '; // the @ wasn't here originally, was a space
   if (alphabetIndex < 0) alphabetIndex=0;
   
   //Draw one character of the message
   // Each character is 5 columns wide, loop two more times to create 2 pixel space betwen characters (FOR ME ITS 1 EXTRA)
   for (int col = 0; col < 6; col++)
   {
     for (int row = 0; row < 7; row++)
     {
       // Set the pixel to what the alphabet say for columns 0 thru 4, but always leave columns 5 and 6 blank.
       bool isOn = 0; 
       if (col<5) isOn = bitRead( alphabets[alphabetIndex][col], 6-row ) == 1;
       Plot( numCols-1, row, isOn); //Draw on the rightmost column, the shift loop below will scroll it leftward.
     }
     for (int refreshCount=0; refreshCount < scrollspeed; refreshCount++)
       RefreshDisplay();
     //Shift the bitmap one column to left
     for (int row=0; row<7; row++)
     {
       for (int zone=0; zone < numZones; zone++)
       {
         //This right shift would show as a left scroll on display because leftmost column is represented by least significant bit of the byte.
         bitmap[row][zone] = bitmap[row][zone] >> 1;
         // Shift over lowest bit from the next zone as highest bit of this zone.
         if (zone < maxZoneIndex) bitWrite(bitmap[row][zone], 6, bitRead(bitmap[row][zone+1],0));
       }
     }
   }
 }
}
void loop() {
 XProcess();
}

I thought I'd give it one more go. Went back to the idea of using a shift register to drive the rows and it seems that the output from the shift register for row scanning needed to be inverted. Added ^ 0xFF to the end of the line so it now reads

shiftOut(dataPin2, clockPin2, MSBFIRST, rowbit ^ 0xFF); //Transmit data

I've edited it to use SPI now too. Much brighter and faster than Shitfout.

This line will not do nothing, delayMicroseconds() only really works at 3-4 and then multiples of 4.

delayMicroseconds(1);

You could gain some ontime by setting the SPI clock to 8 MHz instead of the default 4.
Use the set clock divider to 2 setting. The default is 4.

Or change 2000000 to 8000000 in this line

SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE3));

Thanks for your suggestion. When I change it to 8Mhz the display is garbled. The MIC5891 datasheet indicates it cannot handle 8Mhz but I’ve set it to the below and it’s now working great with a bright enough display. I removed the delay as that was just for testing to see if it had any effect.

void RefreshDisplay()
{
 for (int row = 0; row < 8; row++) 
 {
   int rowbit = 1 << row;
   PORTD &= ~(1<<PD7); // set latch pin low
   shiftOut(dataPin, clockPin, MSBFIRST, rowbit ^ 0xFF);   //Transmit data & invert bits
   PORTD |= (1<<PD7); // set latch pin high
   
   //Start sending column bytes
   SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0)); 
   PORTB |= (1<<PB2); // set pin 10 (SS) high
  
   //Shift out to each matrix
   for (int zone = maxZoneIndex; zone >= 0; zone--) 
   {
     SPI.transfer(bitmap[row][zone]);
   }

   PORTB &= ~(1<<PB2); // set pin 10 (SS) low
   SPI.endTransaction();
   delayMicroseconds(300); // multiples of 3-4 works best
 }
}