Driver LEDs Matrix 8x8 Bicolor

Afin de réaliser mon projet Puissance 4 je me suis d'abord intéressé à l'affichage. Pour cela j'ai pris une matrice 8x8 en deux couleur (rouge + vert) une par joueur. Cela fait 128 LEDs à gérer.

Malheureusement je n'ai pas pu utiliser de driver de LEDs 8x8 alors j'ai essayé avec un TLC5940 PWM-Driver pour gérer les 2x8 lignes accompagné d'un 74HC595 pour les colonnes.

N'ayant pas d'expérience dans les affichages LEDs, notamment au niveau du courant nécessaire. J'ai joué la prudence en ajoutant 8 transistors pour soulager le 74HC595. Le driver PWM quant à lui supporte pas mal de courant.

image

Principe de fonctionnement

J'utilise le 74HC595 pour activer une colonne à la fois. Quand la colonne est active, le driver PWM est programmé pour allumer tout ou partie des 16 LEDs de la colonne. Je n'utilise pas le PWM de ce driver, trop lent avec ces 4096 niveaux d'intensité qui ne me sont d'aucune utilité puisque je dois gérer moi-même le balayage par colonne.

Cependant, en utilisant la capacité de 'dot correction' du driver, je peux fixer l'intensité de chacune des 16 LEDs avec un transfert série de 96 bits. (contre 192 pour le PWM sans parler qu'il faut générer une clock pour lui). Ainsi, le circuit est programmé pour allumer toutes les LEDs avec un PWM fictif et je balaie en jouant sur le 'dot correction' (fonctionnalité utile normalement pour corriger la luminosité des LEDs les unes par rapport aux autres).

image

J'ai préféré gérer l'affichage avec un microcontrolleur dédié pour le rendre plus facilement réutilisable pour d'autres projets. Mon choix s'est porté sur un ATtiny84 qui a le nombre de pattes adapté. Celui-ci communique en série avec le driver PWM et le registre à décalage (74HC595). Il offre une interface SPI pour recevoir les informations qui doivent être affichées à l'écran.

Pour l'heure, seule une fonction setPixel est implémentée par le SPI.

J'ai pas fini de mettre au propre le schéma électrique. Si ça vous intéresse contactez-moi

Le code ci-dessous a d'abord été développé avec l'environnement arduino. Ensuite, pour mieux gérer les timings je préfère partir de rien et utiliser Atmel Studio pour créer un projet. J'ai laissé en commentaire certaine équivalence avec les librairies arduino.

/*
 * Matrix8x8Driver.c
 * Created: 24.04.2013 17:25:27
 * Author: marcha@ludomedia.ch
 * this code is free software
 */


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#define byte unsigned char

#define B_PLD_SCLK              PB0
#define B_COM_SER               PB1
#define B_PLD_VPRG              PB2
#define A_SHREG_SRCLK   PA0
#define A_PLD_XLAT              PA1
#define A_PLD_GSCLK             PA2
#define A_PLD_BLANK             PA3

const byte LED_COLS[] PROGMEM  = { 4, 1, 5, 2, 6, 7, 3, 0 };
const byte LED_ROWS[] PROGMEM  = { 8, 9, 10, 0, 1, 11, 2, 3, 12, 4, 5, 13, 6, 7, 14, 15 };

byte screen[] = {
  030, 000, 000, 007, 007, 000, 000, 003,
  000, 000, 000, 007, 007, 000, 000, 000,
  000, 000, 000, 007, 007, 000, 000, 000,
  070, 070, 070, 077, 077, 070, 070, 070,
  070, 070, 070, 077, 077, 070, 070, 070,
  000, 000, 000, 007, 007, 000, 000, 000,
  000, 000, 000, 007, 007, 000, 000, 000,
  000, 000, 000, 007, 007, 000, 000, 033
};

//---------------------------------------------------------------- SPI --------------------------------------------------

unsigned char spi_state = 0;
unsigned char address;

ISR (USI_OVF_vect) { // SPI_STC_vect

        PORTA |= 1<<7;

        unsigned char c = USIBR;  // grab byte from SPI Buffer Register
        switch(spi_state) {
        case 0:
                if(c==0xA5) spi_state = 1;
                break;
        case 1:
                address = c & 0x3F; // screen max size
                spi_state = 2;
                break;
        case 2:
                screen[address] = c;
                spi_state = 0;
                break;
        }

        PORTA &= ~(1<<7);

}

//-------------------------------------------------------- Matrix Driver ------------------------------------------------------

void writeGS(unsigned int rows) {

        PORTB &= ~(1<<B_COM_SER); // digitalWrite(COM_SER, LOW);
        PORTB |= 1<<B_PLD_SCLK; // digitalWrite(PLD_SCLK, HIGH);
        PORTB &= ~(1<<B_PLD_SCLK); // digitalWrite(PLD_SCLK, LOW);

        int row_mask = 0x1;
        for(byte n=0;n<16;n++) {
                for(byte b=0;b<12;b++) {
                                               
                        // digitalWrite(COM_SER, rows & row_mask ? HIGH: LOW);
                        if(rows & row_mask) PORTB |= 1<<B_COM_SER;
                        else PORTB &= ~(1<<B_COM_SER);
                       
                        PORTB |= 1<<B_PLD_SCLK; // digitalWrite(PLD_SCLK, HIGH);
                        PORTB &= ~(1<<B_PLD_SCLK); // digitalWrite(PLD_SCLK, LOW);
                }
                row_mask<<=1;
        }
       
        PORTA |= 1<<A_PLD_BLANK; // digitalWrite(PLD_BLANK, HIGH);
        PORTA |= 1<<A_PLD_XLAT; // digitalWrite(PLD_XLAT, HIGH);
        PORTA &= ~(1<<A_PLD_XLAT); // digitalWrite(PLD_XLAT, LOW);
        PORTA &= ~(1<<A_PLD_BLANK); // digitalWrite(PLD_BLANK, LOW);
}

void shiftCol(byte v) {
        // digitalWrite(COM_SER, v ? LOW : HIGH);
        if(v) PORTB &= ~(1<<B_COM_SER);
        else PORTB |= 1<<B_COM_SER;
        PORTA |= 1<<A_SHREG_SRCLK; // digitalWrite(SHREG_SRCLK, HIGH);
        PORTA &= ~(1<<A_SHREG_SRCLK); // digitalWrite(SHREG_SRCLK, LOW);
}

void update() {
        static byte y = 8;

        PORTA |= 1<<A_PLD_BLANK; // digitalWrite(PLD_BLANK, HIGH);
        PORTA &= ~(1<<A_PLD_BLANK); // digitalWrite(PLD_BLANK, LOW);

        y--;
        shiftCol(y==0);

        byte col_index = pgm_read_byte_near(LED_COLS + y);
        PORTB |= 1<<B_PLD_VPRG; // digitalWrite(PLD_VPRG, HIGH);
        byte offset = col_index<<3;
        for(byte n=0;n<16;n++) {
                byte row_index = pgm_read_byte_near(LED_ROWS + n);
                byte c = screen[offset|row_index>>1];
                if(row_index&1) c = c & 07;
                else c>>=3;
                byte v = c<<3;
                for(byte b=0;b<6;b++) {
                        // digitalWrite(COM_SER, v & 0x20 ? HIGH : LOW);
                        if(v & 0x20) PORTB |= 1<<B_COM_SER;
                        else PORTB &= ~(1<<B_COM_SER);
                        PORTB |= 1<<B_PLD_SCLK; // digitalWrite(PLD_SCLK, HIGH);
                        PORTB &= ~(1<<B_PLD_SCLK); // digitalWrite(PLD_SCLK, LOW);
                        v<<=1;
                }
        }
       
        PORTA |= 1<<A_PLD_XLAT; // digitalWrite(PLD_XLAT, HIGH);
        PORTA &= ~(1<<A_PLD_XLAT); // digitalWrite(PLD_XLAT, LOW);
        PORTB &= ~(1<<B_PLD_VPRG); // digitalWrite(PLD_VPRG, LOW);

        if(y==0) y = 8;

        PORTA |= 1<<A_PLD_GSCLK; // digitalWrite(PLD_GSCLK, HIGH);
        PORTA &= ~(1<<A_PLD_GSCLK); // digitalWrite(PLD_GSCLK, LOW);
}

int main(void) {

        PORTA &= 0b11010000;
        PORTB &= 0b11111000;
                       
        DDRB = 0b00000111; // PB0-PB2
        DDRA = 0b10101111; // PA7, MISO, PA0-PA3

        writeGS(0xFFFF);
        for(int i=0;i<8;i++) shiftCol(0);
       
        USICR = 1<<USIOIE | 1<<USIWM0 | 1<<USICS1; // OverFlow Interrupt, 3 Wire mode, Clock External positive Edge

        sei();
       
    while(1) {
          update();
          for(int i=0;i<500;i++); // delayMicroseconds(500);
    }
       
}