/*
    Programme de chenillard illustrant l'utilisation de la ligne série.

    Il est basé sur LED_TIMER pour ce qui est de la manière de faire défiler les
    patterns d'allumage, avec une petite différence cependant concernant les I/O
    utilsées pour les LEDs. En effet, PD0 et PD1 correspondent à RXD et TXD. Ces deux
    LEDs ont donc été transférées sur PC0 et PC1 respectivement.
    
    Cible : ATMega8
    Quartz : 8MHz
    Compilateur : avr-gcc (WInAVR)
*/

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

// compteur du nombre d'interruptions timer
volatile int tmr_cnt = 0 ;

// vu le prescaling et l'étendue du compteur, un intervalle de 100ms correspond à 3 interruptions
#define _100_MS         3

// les différentes séquences de patterns d'allumage des LEDs
unsigned char seq_simple[] = {
    0b10000000,
    0b01000000,
    0b00100000,
    0b00010000,
    0b00001000,
    0b00000100,
    0b00000010,
    0b00000001,
    0b00000010,
    0b00000100,
    0b00001000,
    0b00010000,
    0b00100000,
    0b01000000
} ;

unsigned char seq_2x[] = {
    0b10000000,
    0b00100000,
    0b00001000,
    0b00000010,
    0b00000001,
    0b00000100,
    0b00010000,
    0b01000000,
    0b00000000
} ;

unsigned char seq_fill[] = {
    0b10000000,
    0b11000000,
    0b11100000,
    0b11110000,
    0b11111000,
    0b11111100,
    0b11111110,
    0b11111111,
    0b01111111,
    0b00111111,
    0b00011111,
    0b00001111,
    0b00000111,
    0b00000011,
    0b00000001,
    0b00000000
} ;

unsigned char seq_alternate[] = {
    0b10101010,
    0b10101010,
    0b01010101,
    0b01010101,
    0b10101010,
    0b10101010,
    0b01010101,
    0b01010101,
    0b10101010,
    0b10101010,
    0b01010101,
    0b01010101
} ;

unsigned char seq_flash[] = {
    0b11111111,
    0b00000000,
    0b11111111,
    0b00000000,
    0b11111111,
    0b00000000
} ;

typedef struct {
    int size ;
    unsigned char* patterns;
} SEQUENCE_DESC ;

SEQUENCE_DESC sequences[] = {
    {sizeof(seq_simple), seq_simple},
    {sizeof(seq_2x), seq_2x},
    {sizeof(seq_fill), seq_fill},
    {sizeof(seq_alternate), seq_alternate} ,
    {sizeof(seq_flash), seq_flash}
} ;

// index de la séquence courante
volatile unsigned char seq_num = 0 ;
// séquence courante
volatile unsigned char* sequence ;
// index du pattern courant
volatile unsigned char pattern_num = 0 ;

// echaînement de toutes les séquences ?
unsigned char run_all = 1 ;

// défilement en pause ?
volatile char paused = 0 ;

// prototypes des fonctions référencées
static void next_sequence(void) ;
static void previous_sequence(void) ;

/*
    Interrupt handler pour l'overflow du timer 0
*/
SIGNAL (SIG_OVERFLOW0) {
    unsigned char pattern ;

    if (paused) return ;

      // mise à jour du compteur d'interruptions
      tmr_cnt++ ;
      // si on est arrivé au nombre nécessaire à l'obtention d'un délai de 0.2s,
      // il est temps de changer de motif
      if (tmr_cnt == 2 *_100_MS) {
          // remise à 0 du compteur d'interruptions
        tmr_cnt = 0 ;

        // récupération du pattern courant
        pattern = sequence[pattern_num] ;

        // on modifie l'état des sorties avec le pattern courant
        PORTD = (PORTD & 0x03) | (pattern & 0xFC) ;
        PORTC = (PORTC & 0xFC) | (pattern & 0x03) ;

        // avance de l'index du pattern, avec passage à la séquece suivante ou
        // rebouclage si en fin de séquence
        if (++pattern_num == sequences[seq_num].size) {
            // si en mode enchaînement des séquences, on passe à la suivante
            if (run_all) {
                next_sequence();
            } else {
                pattern_num = 0 ;
            }
        }
    }
}

// descripteur de buffer circulaire pour les communications série
typedef struct {
    volatile unsigned char head ;            // pointeur d'écriture
    volatile unsigned char tail ;            // pointeur de lecture
    volatile unsigned char nbchars ;        // nombre de caractères en attente
    volatile unsigned char size ;            // taille du buffer
} BUFFER_DESC ;

// buffer de réception
char rxbuff[32] ;
volatile BUFFER_DESC rxdesc ;

// buffer d'émission
char txbuff[32] ;
volatile BUFFER_DESC txdesc ;

/*
    Interrupt handler USART Rx complete

    Déclenchée dès qu'un caractère est disponible dans le registre de réception
*/
SIGNAL (SIG_UART_RECV) {
//void RXC_handler(void) {
    // lecture du caractère reçu
    char c = UDR ;

    // si buffer pas plein, on le stocke
    if (rxdesc.nbchars < rxdesc.size) {
        // stockage du caractère
        rxbuff[rxdesc.head] = c ;
        // avance du pointeur de tête avec gestion du rebouclage
        if (++rxdesc.head == rxdesc.size) rxdesc.head = 0 ;
        // actualisation du nombre de caractères en attente
        rxdesc.nbchars++ ;
    }
}

/*
    Interrupt handler USART Data Register Empty

    Déclenchée tant que le registre de transmission est vide
*/
SIGNAL (SIG_UART_DATA) {
    char c ;

    // extrait le caractère à envoyer depuis le buffer
    c = txbuff[txdesc.tail] ;
    // met à jour le nombre de caractères en attente dans le buffer
    --txdesc.nbchars ;
    // s'il n'y en a plus d'autre, on désactive l'interruption
    if (txdesc.nbchars == 0) UCSRB &= ~(1 << UDRIE) ;
    // met à jour le pointeur d'extraction, en gérant le rebouclage
    if (++txdesc.tail == txdesc.size) txdesc.tail = 0 ;
    // et envoie le caractère
    UDR = c ;
}

/*
    Initialisation des ports
*/
void port_init(void) {
    // configuration du port D
     // - PD2..PD7 en sortie
     DDRD  = 0xFC ;
    // - toutes les LEDs éteintes
     PORTD = 0x00 ;

    // configuration du port C
     // - PC0 et PC1 en sortie
     DDRC  = DDRC | 0x03 ;
    // - toutes les LEDs éteintes (en laissant /RESET à 1)
     PORTC = 0x40 ;
}

/*
      Initialisation de l'USART
*/
void usart_init(void) {
    // vitesse : 9600 bauds => UBRR = 51 pour une horloge à 8MHz (cf p 158)
    #define BAUD 51
    UBRRH = (unsigned char)(BAUD >> 8) ;
    UBRRL = (unsigned char)BAUD ;

    // format de frame : 8bits, pas de parité, 1 stop bit
    UCSRC = (1 << URSEL)    // sélection de UCSRC (cf DS p 149 & 153)
          | (1 << UCSZ1) | (1 << UCSZ0)         // 8-bits
          ;

      // enable Rx et Tx
      UCSRB = (1 << RXEN)            // enable receive
          | (1 << TXEN)         // enable transmit
          | (1 << RXCIE)        // enable Receive Complete interrupt
          ;

    rxdesc.head = 0 ;
    rxdesc.tail = 0 ;
    rxdesc.nbchars = 0 ;
    rxdesc.size = sizeof(rxbuff) ;

    txdesc.head = 0 ;
    txdesc.tail = 0 ;
    txdesc.nbchars = 0 ;
    txdesc.size = sizeof(txbuff) ;
}

/*
    Lecture d'un caractère sur l'USART
*/
unsigned char usart_getc(void) {
    unsigned char c ;

    // attente de disponibilité d'un caractère
    while (rxdesc.nbchars == 0) ;

    // masquage des interruptions pour éviter une altération du buffer pendant
    // le traitement
    cli() ;

    // extraction du caractère
    c = rxbuff[rxdesc.tail] ;
    // mise à jour du pointeur d'extraction, avec rebouclage éventuel
    if (++rxdesc.tail == rxdesc.size) rxdesc.tail = 0 ;
    // ... et du nombre de caractères en attente
    rxdesc.nbchars-- ;

    // rétablissement des interruptions
    sei() ;

    return c ;
}

/*
    Envoi d'un caractère sur l'USART
*/
void usart_putc(unsigned char c) {
    // si le buffer de transmission est plein, on attend qu'il y ait de la place
    while (txdesc.nbchars == txdesc.size) ;

    // masquage des interruptions pendant l'accès au buffer, pour être certain
    // que personne d'autre ne va le modifier
    cli() ;

    // ajout du caractère
    txbuff[txdesc.head] = c ;
    // mise à jour du pointeur d'injection, avec rebouclage éventuel
    if (++txdesc.head == txdesc.size) txdesc.head = 0 ;

    // si c'est le premier caractère dans le buffer, on active l'interruption
    // Data Register Empty de l'USART pour commencer à "pomper" le buffer via
    // le handler de l'interruption
    if (txdesc.nbchars == 0) UCSRB |= (1 << UDRIE) ;

    // mise à jour du nombre de caractères en attente
    txdesc.nbchars++ ;

    // autorisation des interruptions pour continuer le travail
    sei() ;
}

/*
    Envoi d'une chaîne de caractères sur l'USART
*/
void usart_puts(char *s) {
    unsigned char c ;

    while ((c = *s++) != 0) {
        usart_putc(c) ;
    }
}

/*
    Initialisation des périphériques
*/
void init_devices(void) {
     cli();                         // inhibition des interruptions (pour avoir la paix)
     port_init();                // initialisation de prts
    usart_init() ;                 // initialisation de l'UART

    // MCU Control Register
    // - interruptions externes sur niveau bas de la ligne
    // - sleep mode désactivé
     MCUCR = 0x00;

     // General Interrupt Control
     // - INT0 et INT1 inactives
     GICR  = 0x00;

     // Timer/Counter Interrupt Mask
     // - activation de l'interrupt d'overflow du timer 0
     TIMSK = 0x01;

     // Timer/Counter 0 Control Register
     // - pre-scaling réglé sur 1024
     TCCR0 = 0x05;

     sei();                           // autorisation des interruptions
}

/*
    Passage à la séquence suivante, avec rebouclage en fin de liste
*/
static void next_sequence(void) {
    // masquage des interruptions car modification du contexte  utilisé par
    // la handler de TMR0 intertupt
    cli() ;

    if (++seq_num == sizeof(sequences) / sizeof (SEQUENCE_DESC)) seq_num = 0 ;
    sequence = sequences[seq_num].patterns ;
    pattern_num = 0 ;

    sei() ;
}

/*
    Passage à la séquence précédente, avec rebouclage en début de liste
*/
static void previous_sequence(void) {
    cli() ;

    if (seq_num > 0) {
        --seq_num ;
    } else {
        seq_num = sizeof(sequences) / sizeof (SEQUENCE_DESC) - 1 ;
    }
    sequence = sequences[seq_num].patterns ;
    pattern_num = 0 ;

    sei() ;
}

//- --------------------------------------------------------------------------------
int main(void)
{
    seq_num = 0 ;
    sequence = sequences[seq_num].patterns ;

    init_devices();

    usart_puts(
            "\fLED chaser started...\r\n"
            "Commands : <space>, +, -, *\r\n\n"
            ) ;

    while (1) {
        // attente de le prochaine commande
        unsigned char cmde = usart_getc() ;

        // analyse et traitement de la commande reçue
        switch (cmde) {
            // pause du défilement
            case ' ' :
                paused = ~paused ;
                if (paused) {
                    usart_puts("Paused\r\n") ;
                } else {
                    usart_puts("Resumed\r\n") ;
                }
                break ;

            // séquence suivante
            case '+' :
                cli() ;        // on ne doit pas être interrompu pendant le changement
                run_all = 0 ;        // passage en séquence simple
                next_sequence() ;
                sei() ;
                usart_puts("Next sequence selected\r\n") ;
                break ;

            // séquence précédente
            case '-' :
                cli() ;
                run_all = 0 ;
                previous_sequence() ;
                sei() ;
                usart_puts("Previous sequence selected\r\n") ;
                break ;

            // enchaînement de toutes les séquences
            case '*' :
                run_all = 1 ;
                usart_puts("Playing all sequences\r\n") ;
                break ;
        }
    }
}