Si pero no.
No puedes usar delay() ni delayMicroseconds() dentro de una rutina de atención de interrupción.
Además la rutina tiene que ser lo más corta posible, muy rápida.
Cuando me pueda poner a verlo con tu esquema te lo explico mejor pero te doy la idea.
Si tienes que apagar los LED si o si antes de encender los siguientes, dentro de la misma rutina de interrupción, primero apagas los que ya están encendidos y luego enciendes los nuevos.
Lo que no tengo claro todavía (no pude dedicarme) es si realmente necesitas apagarlos como haces tu o si ya se apagan solos al cambiar de columna.
Dame un tiempo, pero piénsalo como te dije más arriba, olvidate de los delays que de eso se encarga el timer y apaga los LEDs encendidos justo antes de encender los siguientes.
Te dejo de muestra la rutina que uso para un cronómetro con display de 7 segmentos y 8 digitos sobre un Nano de los que solo uso 6 en esta versión (ya se que no es lo mismo que lo tuyo, es solo como ejemplo de como encaralo)
ISR(TIMER1_COMPA_vect){
static byte dcount = 0; // contador de digitos, se incrementa con cada interrupción para apuntar al digito a mostrar
byte lobyte, hibyte; // necesario para "repartir" un byte en 2 ports diferentes según a que PORT pertenece cada pin utilizado
// desactiva todos los cátodos (conectados a los pines analógicos del Nano, usados en modo digital)
PORTC = (PORTC & 0xC0) | 0b00111111;
// Actualiza los segmentos del digito actual
lobyte = SEGMENTS[digits[dcount]]; // matriz donde almaceno los segmentos a encender para formar los dígitos
lobyte |= (dpoints >> dcount) << 7; // encender punto decimal? (Usa el punto como separador de min, seg y centésimas, dpoints es la máscara de bits que marca el punto de cual digito debe encender)
hibyte = lobyte >> 6; // tomo los 2 bits superiores
lobyte = lobyte << 2; // aislo los 6 bits inferiores y "acomodo" a su posición en el port
PORTD = (PORTD & 0x03) | lobyte; // enciende parte de los segmentos
PORTB = (PORTB & 0xFC) | hibyte; // y la otra parte
// Activa catodos en secuencia en A0 a A6 segun dcont (aquí realmente se encienden los segmentos)
PORTC = (PORTC & 0xC0) | CATHODES[dcount]; // CATHODES almacena las mascaras bits para cada cátodo, podría simplemente desplazar bits pero así es más rápido
dcount++; // incrementa el digito para la proxima llamada
dcount %= 6; // pone a 0 cuando llega a 6
}
Te lo documenté lo mejor que pude, espero lo entiendas.
Como ves, los segmentos quedan encendidos hasta que llega la próxima interrupción que lo primero que hace es apagarlos al deshabilitar los cátodos.
En definitiva cada dígito está 1 mseg encendido y 5 mseg apagado pero el brillo es muy bueno y no hay parpadeo.
El tiempo de encendido se varía cambiando la frecuencia del timer, yo usé 1KHz porque lo uso de base de tiempo para el cronómetro pero en otras pruebas bajando hasta 150Hz no tenía parpadeo.
Como tienes muchas columnas vas a tener que trabajar con frecuencias altas, empieza con 1KHz a ver si no hay parpadeo.
Te dejo link a un Calculador de timer y lo que para mí es la biblia de las interrupciones
Saludos