Architecture Logicielle
L'architecture logicielle de la carte contrôleur est relativement simple.
On reçoit des trames provenant du bus I²C via le handler d'interruption et les empiler dans une file d'attente (FIFO), ensuite, périodiquement, on dépile la file d'attente et traite les trames enregistrées pour afficher les informations sur l'écran LCD.
Voici les sources du contrôleur PIC16f876A, versionnées sous Github :
Dialogue avec le bus I²C
Initialisation
Le micro-contrôleur doit être initialisé en tant qu'esclave sur le bus I²C, à une adresse particulière, ici, 0x76.
D'après la datasheet du micro, de nombreux registres (cinq pour être exact) doivent être configurés pour faire fonctionner l'I²C.
- MSSP Control Register (SSPCON) - Registre de configuration et de controle
- MSSP Control Register 2 (SSPCON2) - Registre de configuration et de contrôle
- MSSP Status Register (SSPSTAT) - Registre d'états
- Serial Receive/Transmit Buffer Register (SSPBUF) - Buffer de réception et transmission de données
- MSSP Address Register (SSPADD) - Registre d'adresse de l'esclave
#define PIC16F876A_I2C_SLAVE_ADDR 0x76 #define SCL RC3 #define SDA RC4 int8_t i2c_init(void) { // Synchronous Serial Port Disable bit SSPEN = 0; // Set RC3 as Input TRISC3 = 1; // Set RC4 as Input TRISC4 = 1; // Synchronous Serial Port Mode Select bit // 0110 => I2C Slave mode, 7-bit address SSPM3 = 0; SSPM2 = 1; SSPM1 = 1; SSPM0 = 0; // Set the Slave address SSPADD = PIC16F876A_I2C_SLAVE_ADDR << 1; // 7 bits address = Bits 7654 321 // CKP: SCK Release Control bit CKP = 1; // Synchronous Serial Port Enable bit SSPEN = 1; SSPCON2 = 0x01; //SMP: Slew Rate Control bit SMP = 1; //enable I²C interrupt SSPIE = 1; // clear SSPIF interrupt flag SSPIF = 0; return RET_OK; }
Fonctionnement
L'envoi et la réception d'une trame I²C correspond à un schéma type de base :
Les différents bits du registre de status permettent de renseigner le programme sur le type d'opération en cours :
- Bit de START
- Octet d'adresse + bit de lecture ou d'écriture
- Octet(s) de données
- Bit de STOP
Les différents bits du registre de status permettent de renseigner le programme sur le type d'opération en cours :
- Opération d'écriture du maître (lecture de l'esclave), le dernier octet reçu est une adresse.
- Bit 0 : Buffer full bit = 1
- Bit 2 : Read or Write bit = 0
- Bit 3 : Start bit = 1
- Bit 5 : Data or Address bit = 0
- Opération d'écriture du maître (lecture de l'esclave), le dernier octet reçu est une donnée.
- Bit 0 : Buffer full bit = 1
- Bit 2 : Read or Write bit = 0
- Bit 3 : Start bit = 1
- Bit 5 : Data or Address bit = 1
- Opération de lecture du maître (écriture de l'esclave), le dernier octet reçu est une adresse.
- Bit 0 : Buffer full bit = 0
- Bit 2 : Read or Write bit = 1
- Bit 3 : Start bit = 1
- Bit 5 : Data or Address bit = 0
- Opération de lecture du maître (écriture de l'esclave), le dernier octet reçu est une donnée.
- Bit 0 : Buffer full bit = 0
- Bit 2 : Read or Write bit = 1
- Bit 3 : Start bit = 1
- Bit 5 : Data or Address bit = 1
J'ai opté pour un fonctionnement du bus I²C en interruption, ce qui signifie que dès qu'il y a une activité sur le bus, le programme entre dans la routine d'interruption, regarde le type d'opération en cours et enregistre les données dans la file d'attente, si nécessaire.
Dialogue avec l'écran LCD 2x16 caractères
Initialisation
Deux modes d'initialisation sont possibles, mode 4 bits et mode 8 bits. Pour économiser quelques entrées du micro, j'ai choisi le premier mode (4 bits).
L'organigramme est assez simple pour l'initialisation, On réalise une attente de 15 ms puis on met les broches DB5 et DB4 à l'état haut, tout en laissant les autres à l'état bas.
Il faut toujours envoyer une impulsion positive d'au moins 450 ns, après la mise à l'état haut.
On réalise ensuite, une attente de 4.1 ms, suivie de la mise à l'état haut des broches DB5 et DB4.
Comme précédemment, on fait une impulsion d'au moins 450 ns pour valider la commande.
On réalise, pour finir, une attente de 100 us, suivie de la mise à l'état haut des broches DB5 et DB4 puis une attente de validation.
int8_t lcd_init(void) { int8_t i8_ret = 0; ADCON1 = 0x06; // make the corresponding PORTA pin an output TRISA = 0; // clear all output pins PORTA = 0; // clear all three output pins LCD_RS = 0; LCD_EN = 0; // Wait for more than 15 ms after VCC rises to 4.5 V __delay_ms(15); // RS R/W DB7 DB6 DB5 DB4 // 0 0 0 0 1 1 LCD_D4 = 1; LCD_D5 = 1; LCD_D6 = 0; LCD_D7 = 0; // Il faut toujours envoyer une impulsion positive d'au moins 450ns, // après la mise à l'état haut des broches DB5 et DB4, sur la broche EN. LCD_EN = 1; NOP(); LCD_EN = 0; // Wait for more than 4.1 ms __delay_ms(5); LCD_EN = 1; NOP(); LCD_EN = 0; // Wait for more than 100 μs __delay_us(200); LCD_EN = 1; NOP(); LCD_EN = 0; __delay_us(200); // RS R/W DB7 DB6 DB5 DB4 // 0 0 0 0 1 0 => interface four bits mode LCD_D4 = 0; LCD_D5 = 1; LCD_D6 = 0; LCD_D7 = 0; LCD_EN = 1; NOP(); LCD_EN = 0; __delay_ms(5); // => Set interface length // RS R/W DB7 DB6 DB5 DB4 // 0 0 0 0 1 0 // 0 0 N F * * lcd_write_byte(0x28); // => Display On, Cursor On, Cursor Blink Off // RS R/W DB7 DB6 DB5 DB4 // 0 0 0 0 0 0 // 0 0 1 0 0 0 lcd_write_byte(0x08); // => Clear screen // RS R/W DB7 DB6 DB5 DB4 // 0 0 0 0 0 0 // 0 0 0 0 0 1 lcd_write_byte(0x01); // => Set entry Mode // RS R/W DB7 DB6 DB5 DB4 // 0 0 0 0 0 0 // 0 0 0 1 D S lcd_write_byte(0x06); // RS R/W DB7 DB6 DB5 DB4 // 0 0 0 0 0 0 // 0 0 1 D C B lcd_write_byte(0x0C); // erase display lcd_clear_display(); return i8_ret; }
la primitive d'écriture pour l'écran LCD est la suivante :
int8_t lcd_write_byte(const uint8_t /* in */ ui8_byte) { LCD_D4 = (ui8_byte >> 4) & 0x01; LCD_D5 = (ui8_byte >> 5) & 0x01; LCD_D6 = (ui8_byte >> 6) & 0x01; LCD_D7 = (ui8_byte >> 7) & 0x01; LCD_EN = 1; NOP(); LCD_EN = 0; if(LCD_RS == 1) { __delay_us(200); } else { __delay_ms(5); } LCD_D4 = ui8_byte & 0x01; LCD_D5 = (ui8_byte >> 1) & 0x01; LCD_D6 = (ui8_byte >> 2) & 0x01; LCD_D7 = (ui8_byte >> 3) & 0x01; LCD_EN = 1; NOP(); LCD_EN = 0; if(LCD_RS == 1) { __delay_us(200); } else { __delay_ms(5); } return RET_OK; }
fonctionnement
De nombreuses commandes permettent de jouer avec l'écran (récapitulées dans ce tableau, tiré de la datasheet) :
toutes les commandes envoyées à l'écran sont basées sur la primitive d'écriture, exemples :
Effacer l'écran :
void lcd_clear_display(void) { LCD_RS = 0; lcd_write_byte(0x01); }
Retourner au début de l'écran :
void lcd_return_home(void) { LCD_RS = 0; lcd_write_byte(0x02); }
Ecrire un caractère sur l'écran :
void lcd_put_char(const char_t /* in */ i8_char) { LCD_RS = 1; // write character lcd_write_byte(i8_char); LCD_RS = 0; }
File d'attente : FiFo
J'ai mis en place une file d'attente statique de type FiFo (First In, First Out), ce qui signifie que les premières données empilées dans la mémoire seront les premières à être dépilées et une fois arrivé au bout de la file d'attente, on boucle sur le début de la file d'attente. Elle est statique car la mémoire d'un micro-contrôleur est limitée, donc on déclare une taille figée de FiFo (64 octets).
Concrètement, on a un tableau d'octets déclaré et deux pointeurs (un de lecture et un d'écriture) qui vont se balader sur ce tableau, avec comme contrainte que le pointeur de lecture ne dépasse jamais le pointeur d'écriture.
#define FIFO_MAX_SIZE 64 /*!< Buffer size max */ uint8_t gpui8_buffer[FIFO_MAX_SIZE]; /*!< Static buffer */ void fifo_init(void) { uint8_t ui8_idx = 0; // initialise buffer for(ui8_idx = 0; ui8_idx < FIFO_MAX_SIZE; ui8_idx ++) { gpui8_buffer[ui8_idx] = 0; } // initialise pointers gpui8_read_buf = &gpui8_buffer[0]; gpui8_write_buf = &gpui8_buffer[0]; gb_flag_cur_state_buf = BUFFER_STAT_EMPTY; return; }
Messagerie I²C
Une messagerie simple a été conçue pour avoir accès aux fonctions principales de l'afficheur LCD et afficher la plupart des informations à l'écran.
Lien vers la première partie du tutoriel : Afficheur alphanumérique (Partie 1)
Lien vers la troisième partie du tutoriel : Afficheur alphanumérique (Partie 3)
Aucun commentaire:
Enregistrer un commentaire