« SE4Binome2025-6 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
 
(59 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 47 : Ligne 47 :


===== Test carte SD =====
===== Test carte SD =====
[[Fichier:Correction pcb.jpg|right|alt=correction_pcb|vignette|correction_pcb]]
On teste ensuite un programme arduino simple au préalable pour voir si la carte SD est détectée. La carte n'étant pas détectée, on regarde alors :
On teste ensuite un programme arduino simple au préalable pour voir si la carte SD est détectée. La carte n'étant pas détectée, on regarde alors :


Ligne 52 : Ligne 53 :
# si la carte SD est défaillante en testant le programme avec une autre carte SD. On teste aussi sur un lecteur de carte SD pour voir si elle est détectée sur le pc ce qui est le cas -> le pb ne vient pas de là non plus.
# si la carte SD est défaillante en testant le programme avec une autre carte SD. On teste aussi sur un lecteur de carte SD pour voir si elle est détectée sur le pc ce qui est le cas -> le pb ne vient pas de là non plus.
# on s'intéresse maintenant à l'aspect matériel, on vérifie les soudures -> toujours pas de souci particulier.
# on s'intéresse maintenant à l'aspect matériel, on vérifie les soudures -> toujours pas de souci particulier.
# schématique et routage de la carte : on s'aperçoit alors que l'on a inversé le sens du 74LVC125 de l'unité U1A pour la conversion de niveau logique du MOSI en appuyant par erreur sur le raccourci clavier x qui inverse en "miroir" le sens du composant. Remarque : on aurait aussi vraiment du mettre le convertisseur de niveau logique sur la carte mémoire pour voir le problème plus vite. Conséquence : le routage n'est pas correct, on rajoute donc des fils pour faire les bonnes connexions et on coupe au cutter les mauvaises.
# schématique et routage de la carte : on s'aperçoit alors que l'on a inversé le sens du 74LVC125 de l'unité U1A pour la conversion de niveau logique du MOSI en appuyant par erreur sur le raccourci clavier x qui inverse en "miroir" le sens du composant. Remarque : on aurait aussi vraiment mettre le convertisseur de niveau logique sur la carte mémoire pour voir le problème plus vite. Conséquence : le routage n'est pas correct, on rajoute donc des fils pour faire les bonnes connexions et on coupe au cutter les mauvaises.
 
[INSERER PHOTO]


=== Software ===
=== Software ===


===== Programmation carte SD =====
===== Programmation carte SD =====
Maintenant que la carte SD fonctionne, on va s'atteler à essayer de comprendre son fonctionnement en stockant des données dedans à l'aide de code en C.
On ne programme pas la carte SD ici, on le fait directement sur la nucleo.
 
===== Ordonnanceur =====
Maintenant que notre shield est fonctionnel, nous pouvons réaliser notre ordonnanceur. A voir ici : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/03-Code/Carte_shield/ordonnanceur
 
Notre ordonnanceur est structuré de la manière suivante :
 
* un main.c
* lib qui contient les fichiers nécéssaires pour le main.c
*# Un dossier Hardware
*# Un dossier ordonnanceur
*# Un dossier Task
*# Un dossier USART
* build : un dossier qui stocke les exécutables à part
 
Description des fichiers et fonctions implémentées :
 
====== HARWARE ======
La première étape est de faire clignoter les leds qui serviront de futures tâches. On implémente donc les fonctions suivantes : <syntaxhighlight lang="c">
#define LEDs_PORT &PORTD
 
#define LED_CS1 3
#define LED_CS2 2
#define LED_CS3 1
#define LED_CS4 0
 
void toggleLedCS1(){
  *LEDs_PORT ^= (1 << LED_CS1);
}
 
void toggleLedCS2(){
  *LEDs_PORT ^= (1 << LED_CS2);
}
 
void toggleLedCS3(){
  *LEDs_PORT ^= (1 << LED_CS3);
}
 
void toggleLedCS4(){
  *LEDs_PORT ^= (1 << LED_CS4);
}
 
</syntaxhighlight>On réutilise ensuite la fonction setupPin qui permet d'initialiser nos leds.<syntaxhighlight lang="c">
typedef enum {
  INPUT,
  INPUT_PULL_UP,
  OUTPUT,
} pinmode;
 
#define LEDs_DDR &DDRD
 
void setupPin(volatile uint8_t *PORTx, volatile uint8_t *DDRx, uint8_t pin, pinmode mode) {
  switch (mode) {
  case INPUT: // Forcage pin à 0
    *DDRx &= ~(1 << pin);
    break;
  case INPUT_PULL_UP: // Forcage pin à 0
    *DDRx &= ~(1 << pin);
    *PORTx |= (1 << pin);
    break;
  case OUTPUT: // Forcage pin à 1
    *DDRx |= (1 << pin);
    break;
  }
}
</syntaxhighlight>On initialise ensuite l'horloge :<syntaxhighlight lang="c">
void setupClock() {
    // Activer possibilité de changer le prescaler
    CLKPR = (1 << CLKPCE);
    // Choix diviseur
    CLKPR = 0; 
}
</syntaxhighlight>Puis l'hardware complet :<syntaxhighlight lang="c">
void setupHardware(){
  setupClock();
 
  setupPin(LEDs_PORT, LEDs_DDR, LED_CS1, OUTPUT);
  setupPin(LEDs_PORT, LEDs_DDR, LED_CS2, OUTPUT);
  setupPin(LEDs_PORT, LEDs_DDR, LED_CS3, OUTPUT);
  setupPin(LEDs_PORT, LEDs_DDR, LED_CS4, OUTPUT);
 
  init_usart();
}
</syntaxhighlight>
 
====== USART ======
Dans le dossier USART, on définit les fonctions basiques pour initialiser, envoyer et recevoir depuis l'usart. L'usart nous servira par la suite pour certaines tâches.<syntaxhighlight lang="c">
#define BAUD_RATE 9600
#define F_CPU 16000000UL
#define UBRR_VALUE ((F_CPU/16/BAUD_RATE)-1)
 
void init_usart() {
  // Serial Initialization
  /*Set baud rate 9600 */
  UBRR0H = (unsigned char)(UBRR_VALUE >> 8);
  UBRR0L = (unsigned char)UBRR_VALUE;
 
  /* Enable receiver and transmitter */
  UCSR0B = (1 << RXEN0) | (1 << TXEN0);
 
  /* Frame format: 8data, No parity, 1stop bit */
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}
 
void usart_send(unsigned char data) {
  while (!(UCSR0A & (1 << UDRE0)));
  UDR0 = data;
}
 
unsigned char usart_receive() {
  while (!(UCSR0A & (1 << RXC0)));
  return UDR0;
}
</syntaxhighlight>
 
====== TASK ======
On pense ensuite aux tâches que l'on va implémenter dans notre ordonnanceur.
 
On implémente des tâches pour les leds :<syntaxhighlight lang="c">
 
void taskCS1() {
    while(1){ 
        toggleLedCS1();
        _delay_ms(1000);
    }
}
 
void taskCS2() {   
    while(1){ 
        toggleLedCS2();
        _delay_ms(500);
    } 
}
 
void taskCS3() {
    while(1){ 
        toggleLedCS3();
        _delay_ms(500);
    } 
}


[CONTINUER LA PROG]
void taskCS4() {
    while(1){ 
        toggleLedCS4();
        _delay_ms(250);
    } 
</syntaxhighlight>et pour le série :<syntaxhighlight lang="c">


===== Ordonnanceur =====
void taskSendSerialA() {
Maintenant que notre shield est fonctionnel, nous pouvons réaliser notre ordonnanceur. A voir ici : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/03%20-%20Code/Carte%20Shield/ordonnanceur.
  while (1) {
    usart_send('A');
    usart_send('\n');
    usart_send('\r');
    _delay_ms(500);
  }
}
 
void taskSendSerialB() {
  while (1) {
    usart_send('B');
    usart_send('\n');
    usart_send('\r');
    _delay_ms(250);
  }
}
</syntaxhighlight>On définit ensuite la structure générale pour les tâches.
 
Une tâche possède :
 
* un pointeur de fonction avec le nom de la tâche
* un pointeur pour l'adresse mémoire afin de savoir où est située la tâche dans la pile
* l'état de la tâche : actif ou non (endormi)
* le temps pour lequel la tâche reste endormie
<syntaxhighlight lang="c">
typedef enum{
    AWAKE,
    SLEEP,
} state_task;
 
typedef struct {
    void (*taskAddress)(void);  // fonction de la tache
    uint16_t stackPointer;      // pointeur de pile
    state_task state;              // AWAKE ou SLEEP
    uint16_t sleepTime;        // temps restant en ms
} process;
 
 
</syntaxhighlight>On définit donc un tableau de tâches et une constante pour savoir le nombre de tâches que l'on a. <syntaxhighlight lang="c">
process task[] = {
    // {taskSendSerialA, 0x0780, AWAKE, 0},
    {taskCS1, 0x0730, AWAKE, 0},
    // {taskSendSerialB, 0x06E0 , AWAKE, 0},
    // {taskCS2, 0x0690, AWAKE, 0},
    // {taskCS3, 0x06E0, AWAKE, 0},
    // {taskCS4, 0x0620, AWAKE, 0},
};
 
const uint8_t nbTasks = sizeof(task)/sizeof(task[0]);
</syntaxhighlight>
 
====== ORDONNANCEUR ======
Pour débuter notre ordonnanceur, on commence par créér un minuteur afin de faire clignoter deux leds à des périodes différentes. <syntaxhighlight lang="c">
void init_timer1(int diviseur, long periode_ms){
    tick_ms = periode_ms; 
 
    TCCR1A = 0;
    TCCR1B = (1 << WGM12); // CTC mode
 
    switch(diviseur){
        case 64: TCCR1B |= (1<<CS11)|(1<<CS10); break;
        case 256: TCCR1B |= (1<<CS12); break;
    }
 
    OCR1A = (F_CPU / diviseur) * periode_ms / 1000;
    TCNT1 = 0;
    TIMSK1 = (1 << OCIE1A);
}
</syntaxhighlight>Une fois que le minuteur a atteint son nombre de "ticks", il appelle l'ISR : Interrupt Service Routine, qui prend le relai.  
 
L'ISR a besoin de la pile, on implémente donc une fonction afin d'initialiser la pile. <syntaxhighlight lang="c">
void init_pile(int n){
    uint16_t savedSP = SP;
    uint16_t addr = (uint16_t)task[n].taskAddress;
 
    SP = task[n].stackPointer;
 
    // PC (low puis high)
    asm volatile("push %A0" :: "r"(addr));
    asm volatile("push %B0" :: "r"(addr));
 
    // r0-r31
    for(int i=0; i<32; i++)
        asm volatile("push __zero_reg__");
 
    // SREG = I=1
    uint8_t s = 0x80;
    asm volatile("push %0" :: "r"(s));
 
    task[n].stackPointer = SP;
    SP = savedSP;
}
</syntaxhighlight>
 
L'ISR procède ainsi :
 
* Elle commence par sauvegarder les registres grâce à la fonction assembleur SAVE_REGISTERS définie dans ordonnanceur.h, qui permet de sauvegarder les registres de la tâche interrompue
* On appelle ensuite l'ordonnanceur qui définit la manière dont les tâches sont exécutées.
* On restore ensuite le contexte, et tous les registres de la tâche que l'on va exécuter.
<syntaxhighlight lang="c">
ISR(TIMER1_COMPA_vect, ISR_NAKED){
   
    /* Sauvegarde du contexte de la tâche interrompue */
    SAVE_REGISTERS();
    task[currentTask].stackPointer = SP;
 
    /* Appel à l'ordonnanceur qui choisi la prochaine tache */
    scheduler();
 
    /* Récupération du contexte de la tâche ré-activée */
    SP = task[currentTask].stackPointer;
    RESTORE_REGISTERS();
 
    asm volatile("reti");
}
</syntaxhighlight>L'ordonnanceur est ici round-robin, il exécute donc les tâches les unes après les autres sans priorité pour certaines tâches.<syntaxhighlight lang="c">
void scheduler(void){
    currentTask = (currentTask + 1) % nbTasks;
}
 
</syntaxhighlight>Fonction afin de mettre une tâche en sommeil :<syntaxhighlight lang="c">
void sleep_ms(uint16_t t) {
    task[currentTask].state = SLEEP;
    task[currentTask].sleepTime = t / tick_ms;
</syntaxhighlight>


[EXPLICATIONS A DETAILLER]
====== MAIN ======
Dans le fichier main.c, on appelle diverses fonctions notamment le timer, qui appellera donc l'ISR puis le scheduler. On commence également par charger la première tâche sur la pile.<syntaxhighlight lang="c">
int main(void) {
  cli(); // désactiver interruptions
  setupHardware();
  // initialisation des piles
  init_tasks();
  // TIMER1 config à 20 ms
  init_timer1(64, 20);
  // charger la pile de la premi??re t??che
  SP = task[0].stackPointer;
  RESTORE_REGISTERS();
  asm volatile("reti");
  return 0;
}
</syntaxhighlight>


==Carte mère==
==Carte mère==
Ligne 93 : Ligne 377 :
<p style="clear: both;" />On peut également retrouver des pages supplémentaires afin de bien dimensionner notre quartz et les capacités aux alentours.
<p style="clear: both;" />On peut également retrouver des pages supplémentaires afin de bien dimensionner notre quartz et les capacités aux alentours.


Document AN2867- Guidelines for oscillator design on STM8AF/AL/S and STM32 MCUs/MPUs à retrouver sur le lien suivant :
Document AN2867- Guidelines for oscillator design on STM8AF/AL/S and STM32 MCUs/MPUs à retrouver sur le lien suivant : https://www.st.com/en/microcontrollers-microprocessors/stm32f410/documentation.html
https://www.st.com/en/microcontrollers-microprocessors/stm32f410/documentation.html
<p style="clear: both;" />
<p style="clear: both;" />


Ligne 113 : Ligne 396 :


====== Horloges ======
====== Horloges ======
On utilise ici deux sources d'horloge : on peut soit choisir l'oscillateur RC ou une horloge externe comprise entre 4-26 MHz (p18/142) ou bien la deuxième horloge est une horloge pour temps réels (donc pas une application qu'on vise) (p22/142).
On a ici deux horloges :  


[PRECISER VALEURS ET REFERENCES]
* Première horloge : on peut soit choisir l'oscillateur RC de 16 MHz ou une horloge externe comprise entre 4 et 26 MHz (p18/142)
* Deuxième horloge : horloge pour le temps réel de 32kHZ (donc pas une application qu'on vise) (p22/142)


====== Boot et configuration ======
====== Boot et configuration ======
Ligne 172 : Ligne 456 :
[[Fichier:Carte mere 3D.png|left|650px|alt=Carte mere 3D|vignette|Carte mere 3D]]
[[Fichier:Carte mere 3D.png|left|650px|alt=Carte mere 3D|vignette|Carte mere 3D]]
[[Fichier:Carte mere 3D backside.png|right|650px|alt=Carte mere 3D backside|vignette|Carte mere 3D backside]]
[[Fichier:Carte mere 3D backside.png|right|650px|alt=Carte mere 3D backside|vignette|Carte mere 3D backside]]
<p style="clear: both;" />
==== Brasure ====
INSERER PHOTO
<p style="clear: both;" />
<p style="clear: both;" />


Ligne 189 : Ligne 477 :
====== Test led ======
====== Test led ======
Pour commencer à comprendre, on utilise dans un premier temps l'IDE STM32CubeIDE dont l'on se passera ensuite afin d'être proche du matériel et de ne pas passer par plein de librairies.
Pour commencer à comprendre, on utilise dans un premier temps l'IDE STM32CubeIDE dont l'on se passera ensuite afin d'être proche du matériel et de ne pas passer par plein de librairies.
Dans notre fichier nucleo.ioc, on a toutes les spécifications de notre carte dont le pinout.
Dans notre fichier nucleo.ioc, on a toutes les spécifications de notre carte dont le pinout.
Premier programme test : on fait clignoter la led LD2. On voit sur le fichier nucleo.ioc que LD2 est sur le pin PA5 du microcontrôleur.
Premier programme test : on fait clignoter la led LD2. On voit sur le fichier nucleo.ioc que LD2 est sur le pin PA5 du microcontrôleur.
En allant dans l'arborescence à gauche, on va dans Core -> Src -> main.c.
En allant dans l'arborescence à gauche, on va dans Core -> Src -> main.c.
On ouvre le main et dans la boucle while, on va faire clignoter notre led D2 grâce à la librairie HAL (Hardware Abstraction Layer) dont l'on se passera par la suite (c'est juste pour les premiers tests).<syntaxhighlight lang="c">
On ouvre le main et dans la boucle while, on va faire clignoter notre led D2 grâce à la librairie HAL (Hardware Abstraction Layer) dont l'on se passera par la suite (c'est juste pour les premiers tests).<syntaxhighlight lang="c">
Ligne 223 : Ligne 514 :
# un fichier nommé linker.ld : à la différence des atmega, notre microcontrôleur a une organisation mémoire plus complexe donc ce fichier nous permet de choisir où placer chaque type de données (cf les sections elf) dans la mémoire du microcontrôleur. On y précise la taille de la flash : 128ko et la taille de la RAM : 32 ko ainsi que leurs adresses trouvées page 40/763 de la datasheet. On y initialise également les sections .text (le code), .data (variables initialisées) et .bss (variables non initialisées).
# un fichier nommé linker.ld : à la différence des atmega, notre microcontrôleur a une organisation mémoire plus complexe donc ce fichier nous permet de choisir où placer chaque type de données (cf les sections elf) dans la mémoire du microcontrôleur. On y précise la taille de la flash : 128ko et la taille de la RAM : 32 ko ainsi que leurs adresses trouvées page 40/763 de la datasheet. On y initialise également les sections .text (le code), .data (variables initialisées) et .bss (variables non initialisées).
# un fichier startup_stm32f410rx.s : un fichier assembleur qui gère les interruptions, les resets, l'initialisation des données que l'on copie colle depuis /stm32/stm32cubef4-v1-28-3/STM32Cube_FW_F4_V1.28.0/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc. Remarque sur ce qu'est thumb : sur notre stm32, on utilise ARM et thumb 2 dont le rôle est d'avoir un code plus compact en mélangeant des instructions à la fois de 16 et 32 bits.
# un fichier startup_stm32f410rx.s : un fichier assembleur qui gère les interruptions, les resets, l'initialisation des données que l'on copie colle depuis /stm32/stm32cubef4-v1-28-3/STM32Cube_FW_F4_V1.28.0/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc. Remarque sur ce qu'est thumb : sur notre stm32, on utilise ARM et thumb 2 dont le rôle est d'avoir un code plus compact en mélangeant des instructions à la fois de 16 et 32 bits.
<p style="clear: both;" /><p style="clear: both;" />Remarque :  commande à taper quand la carte "fail to connect" : <syntaxhighlight lang="c">
<p style="clear: both;" />
Remarque :  commande à taper quand la carte "fail to connect" : <syntaxhighlight lang="c">
st-flash --connect-under-reset erase
st-flash --connect-under-reset erase
</syntaxhighlight>
</syntaxhighlight>
<p style="clear: both;" />


====== Ordonnanceur ======
====== Ordonnanceur ======
Il faut également faire l'ordonnanceur que l'on doit maintenant adapter non plus au shield et aux avr mais à la carte mère et aux arm.
<p style="clear: both;" />
 
Il faut également faire l'ordonnanceur que l'on doit maintenant adapter non plus au shield et aux avr mais à la carte mère et aux arm. Notre ordonnanceur était originellement composé uniquement des fichiers ordonnanceur.c et ordonnanceur.h mais on a implémenté pas mal de fonctions donc on place celles-ci dans un nouveau fichier processus.c accompagné de son processus.h. Ci-dessous la description du contenu de ces 4 fichiers.
* processus.h :
Il inclut les librairies nécéssaires et les prototypes des fonctions implémentées dans processus.c
* processus.c :
 
# Fonction Delay : la fonction delay n'est pas présente sous arm donc on l'implémente ici.<syntaxhighlight lang="c">
void delay(volatile uint32_t t){
    while (t--){
        __asm__("nop");
    }
}
</syntaxhighlight>
# Tâches pour la led : ci-dessous des fonctions permettant d'allumer, éteindre et faire clignoter la led LD2 de la nucleo. <syntaxhighlight lang="c">
void init_led2(void){
    //active l’horloge pour GPIOA car ds les stm32, les peripheriques sont eteints pr economiser de l energie et on doit appeler la clock pr réveiller les gpio
    RCC->AHB1ENR |= (1 << 0);
    //on veut écrire 01 (état haut) et pas 11 (analogique) pour les broches 10 et 11 donc on doit procéder en deux étapes : d'abord effacer puis mettre en sortie
    GPIOA->MODER &= ~(0x3 << (5*2));
    GPIOA->MODER |=  (0x1 << (5*2));
}
 
void led2_on(void){
    GPIOA->ODR |= (1 << 5);
}
 
void led2_off(void){
    GPIOA->ODR &= ~(1 << 5);
}
 
void led2_blink(void){
    while(1){
        GPIOA->ODR ^= (1 << 5);
        delay(1000000);
    }
}
 
void led2_blink_task(void){
    static uint32_t counter = 0;
    counter++;
    if(counter >= 10){ // clignote toutes les 10 interruptions
        GPIOA->ODR ^= (1 << 5);
        counter = 0;
    }
}
</syntaxhighlight>
# Tâches pour le série : envoyer un caractère, une chaîne de caractère, recevoir et allumer une led quand l'utilisateur écrit la lettre 'a' dans le minicom.<syntaxhighlight lang="c">
void init_usart2(void){
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    //PA2 pour tx et PA3 pour rx
    GPIOA->MODER &= ~((3 << (2*2)) | (3 << (3*2)));
    GPIOA->MODER |=  (2 << (2*2)) | (2 << (3*2));
    GPIOA->AFR[0] &= ~((0xF << (4*2)) | (0xF << (4*3))); //efface registre alternate function
    GPIOA->AFR[0] |=  (7 << (4*2)) | (7 << (4*3)); //attribue AF7 dédié à usart2
    USART2->BRR = 0x0683; //baudrate à 9600
    USART2->CR1 = USART_CR1_TE | USART_CR1_RE; //transmit et receive enable pour tx et rx
    USART2->CR1 |= USART_CR1_UE; //usart enable
}


void usart2_send_char(char c){
    while (!(USART2->SR & USART_SR_TXE)); //qd le bit transmit data register empty est à 0 (donc data register possède des données)
    USART2->DR = c; //écrire le caractère
}
void usart2_send_string(char *s){
    while (*s){
        usart2_send_char(*s++);
    }
}
char usart2_receive(void){
    while (!(USART2->SR & USART_SR_RXNE)); //qd caractère
    return (char)(USART2->DR & 0xFF); //char sur 16bits on garde que bits de données
}
void led2_blink_serial(void){
    for (int i=0; i<10; i++){
        GPIOA->ODR ^= (1 << 5);
        delay(1000000);
    }
}
void led_serial(void){
    while(1){
        char c = usart2_receive();
        usart2_send_char(c); //echo vers Minicom
        if(c == 'a')
            led2_blink_serial();
    }
}
</syntaxhighlight>Commande minicom à taper dans le terminal (-o permet d'éviter que minicom envoie des commandes d'initialisation) :<syntaxhighlight lang="c">
sudo minicom -D /dev/ttyACM0 -b 9600 -o
</syntaxhighlight>Exemple de tâche (non ordonnancée pour le moment) où l'on affiche un message voulu, ici "heyyy" sur minicom[[Fichier:Minicom2.png|center|500px|alt=Minicom_|vignette|Minicom_]]
# Tâche avec le bouton : allumer la led LD2 quand on appuie sur le bouton user (en bleu, le noir étant dédié au reset).<syntaxhighlight lang="c">
void init_user_button(void){
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
    GPIOC->MODER &= ~(0x3 << (13 * 2)); //PC13 en entrée
    //pull up
    GPIOC->PUPDR &= ~(0x3 << (13 * 2));
    GPIOC->PUPDR |=  (0x1 << (13 * 2)); //01 : pull up
}
uint8_t user_button_pressed(void){
    return (GPIOC->IDR & (1 << 13)) == 0; //bouton actif à 0
}
void task_button_led(void){
    while(1){
        if(user_button_pressed()){
            led2_on();
        } else {
            led2_off();
        }
    }
}
</syntaxhighlight><p style="clear: both;" />
* ordonnanceur.h :
* ordonnanceur.h :


On y ajoute les librairies et la liste des fonctions implémentées dans ordonnanceur.c
On y ajoute les librairies, processus.h et la liste des fonctions implémentées dans ordonnanceur.c.


* ordonnanceur.c :
* ordonnanceur.c :
Ce fichier gère le minuteur, les interruptions, la gestion des tâches.
# minuteur : pour pouvoir effectuer une tâche pendant un certain temps. <syntaxhighlight lang="c">
extern uint32_t SystemCoreClock; //freq horloge
void init_minuteur(uint16_t prescaler, uint16_t periode_ms){
  RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; //active l'horloge de TIM6
  TIM6->PSC = prescaler - 1; //prescaler
  uint32_t tick_freq = SystemCoreClock/prescaler; //période
  //TIM6->ARR = (tick_freq * periode_ms)/1000-1; //Auto-Reload Register est la période avant que le timer fasse une interruption. (tick_freq * periode_ms)/1000 correspond au nb de ticks durant cette période.
  TIM6->ARR = (uint32_t)(((uint64_t)tick_freq * periode_ms)/1000 - 1);
  TIM6->CNT = 0; //reset compteur
  TIM6->DIER |= TIM_DIER_UIE; //interruption activée quand compteur atteint nb de ticks de ARR
  TIM6->CR1 |= TIM_CR1_CEN; //compteur
  NVIC_SetPriority(TIM6_DAC_IRQn, 0x1); //prio haute
  NVIC_EnableIRQ(TIM6_DAC_IRQn); //active l'interruption dans le NVIC (Nested Vector Interrupt Controller)
}
</syntaxhighlight>
On ajoute ensuite le shield sur la nucleo afin de pouvoir tester l'ordonnanceur avec plus de tâches puisque la nucleo seule possède très peu de leds.[[Fichier:Nucleo ordonnanceur.jpg|center|500px|alt=nucleo_ordonnanceur|vignette|nucleo_ordonnanceur]]
====== Système de fichier ======
Afin de débuter le système de fichiers, on connecte notre mémoire (la carte SD) à notre nucleo.[[Fichier:Nulceo sd.jpg|center|500px|alt=nucleo_sd|vignette|nucleo_sd]]
<p style="clear: both;" />
'''Test d'initialisation'''
On commence par initialiser notre carte SD grâce à fichier.c et en premier lieu cela ne fonctionnait pas on avait status=1 et erreur=8 (et en conséquent une taille nulle puisqu'il n'arrive pas à initialiser la carte). La carte n'était pas de bonne qualité et ne communiquait pas en SPI mais sans doute avec un autre protocole. Mais par la suite avec la carte SD donnée par M. Redon, on a réussi à la phase d'initialisation.
On obtient alors :
* son type, ici 2 (l'autre type étant 1 pour les micro cartes sd moins performantes)
* ainsi que sa taille : 3 911 860 secteurs. Un secteur étant de 512 octets, on retrouve bien la taille écrite sur notre carte à savoir 2Gb.
[[Fichier:Sd init.png|center|600px|alt=Sd init|vignette|Sd init]]
<p style="clear: both;" />
Suite à cela, on teste l'écriture, qui s'avère opérationelle (status = 1) puis l'écriture, elle aussi fonctionnelle puisque l'on affiche bien les 3 "blocs" voulus.
[[Fichier:Sd read test.png|600px|alt=Sd read test|vignette|Sd read test]]
[[Fichier:Sd write test.png|600px|alt=Sd write test|vignette|Sd write test|gauche]]
<p style="clear: both;" />'''Commandes pour le système de fichier'''
La prochaine étape est de coder les commandes nécéssaires telles que append, read, remove, rename, copy.
# format : efface l'entièreté du système de fichier.
# list : liste les noms des fichiers contenus dans le système de fichier, équivalent du "ls" sous linux.
# append : créé un fichier si non existant et ajoute du texte si le fichier existe déjà. Commande de la forme append fichier/données. Combine le "touch" (create dans l'énoncé) et l'écriture de données.
# read : permet de lire le contenu d'un fichier, équivalent du "cat".
# remove : supprime le fichier en paramètre, équivalent du "rm".
# rename : renommer un fichier, équivalent du "mv" en moins puissant.
# copy : copie un fichier et ses données dans un second fichier, équivalent du "cp".
[[Fichier:Demo fileSystem.webm|center|500px|vignette|demo_fileSystem]]
<p style="clear: both;" />
==== Notre carte mère ====
===== Test led =====
Afin de vérifier que notre PCB reçu fonctionne, on teste notre carte en faisant clignoter une led.<syntaxhighlight lang="c">
#define LED_PIN 9
int main(void) {
  // Activer horloge GPIOC
  RCC->AHB1ENR |= (1 << RCC_AHB1ENR_GPIOCEN_Pos);
  GPIOC->MODER &= ~(0x3 << (LED_PIN * 2)); // Clear
  GPIOC->MODER |= 0x1 << (LED_PIN * 2);    // Output
  GPIOC->OTYPER &= ~(1 << LED_PIN);        // Push-pull
  GPIOC->PUPDR &= ~(0x3 << (LED_PIN * 2)); // No pull
  while (1) {
    GPIOC->ODR ^= (1 << LED_PIN);
    for (volatile uint32_t i = 0; i < 1000000; i++)
      ;
  }
}
</syntaxhighlight>
===== Test écran =====
L'idée est d'afficher un compteur sur un afficheur 7 segments, notre "carte écran" : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/03-Code/Carte_mere/Notre_PCB/02-Compteur.
Notre test écran est structuré de la manière suivante :
* un main.c
* un dossier pour la carte écran
* un dossier hardware_setup
* un dossier pour le spi
Description des fichiers et fonctions implémentées :
====== hardware_setup ======
On reprend notre fonction setupPin que l'on adapte à arm afin d'initialiser les pins de notre carte :<syntaxhighlight lang="c">
#define OFFSET_BSRR_OFF 16
#define MODER_CLEAR 0x3
#define MODER_NumberBitParPin 0x2
#define PUPDR_CLEAR 0x3
#define PUPDR_NumberBitParPin 0x2
#define OTYPER_CLEAR 0x1
typedef enum {
  INPUT,
  OUTPUT,
  ALTERNATE_FUNCTION,
  ANALOG,
} portModeRegister;
typedef enum {
  NO_PULL,
  PULL_UP,
  PULL_DOWN,
} portPullUpPullDownRegister;
void setupPin(GPIO_TypeDef *GPIOx, uint8_t PINx, portModeRegister portMode) {
  // ACTIVATION DE L'HORLOGE GPIO SPECIFIQUE
  uint32_t RCC_AHB1ENR_GPIOxEN_Pos;
  if (GPIOx == GPIOA)
    RCC_AHB1ENR_GPIOxEN_Pos = RCC_AHB1ENR_GPIOAEN_Pos;
  else if (GPIOx == GPIOB)
    RCC_AHB1ENR_GPIOxEN_Pos = RCC_AHB1ENR_GPIOBEN_Pos;
  else if (GPIOx == GPIOC)
    RCC_AHB1ENR_GPIOxEN_Pos = RCC_AHB1ENR_GPIOCEN_Pos;
  else
    return; // GPIO non existant sur ce microcontroleur
  if (!(RCC->AHB1ENR & (1 << RCC_AHB1ENR_GPIOxEN_Pos)))
    RCC->AHB1ENR |= (1 << RCC_AHB1ENR_GPIOxEN_Pos);
  // CLEAR AVANT MODIFICATION
  // On clear après l'activation d'horloge !
  GPIOx->MODER &= ~(MODER_CLEAR << (PINx * MODER_NumberBitParPin));
  GPIOx->PUPDR &= ~(PUPDR_CLEAR << (PINx * PUPDR_NumberBitParPin));
  GPIOx->OTYPER &= ~(OTYPER_CLEAR << PINx); // Push-pull
  // TYPE DE PORT (Input, Output, Alternative, Analogique)
  portPullUpPullDownRegister typePull = NO_PULL;
  uint8_t optionPort = 0x00;
  switch (portMode) {
  case INPUT:
    optionPort = 0x00;
    typePull = PULL_UP;
    break;
  case OUTPUT:
    optionPort = 0x01;
    typePull = NO_PULL;
    if (1)
      GPIOx->OTYPER &= ~(1 << PINx); // Push-pull
    else
      GPIOx->OTYPER |= (1 << PINx); // Open-drain
    break;
  case ALTERNATE_FUNCTION:
    optionPort = 0x02;
    if (1) {
      typePull = NO_PULL;
      GPIOx->OTYPER &= ~(1 << PINx); // Push-pull
      GPIOx->OSPEEDR |= 11 << (PINx * 2);
      // Very high speed => Cf STM32F410 datasheet tableau p95/142
    }
    break;
  case ANALOG:
    optionPort = 0x03;
    break;
  default:
    optionPort = 0x00;
    typePull = NO_PULL;
    break;
  }
  // Ecriture
  GPIOx->MODER |= optionPort << (PINx * MODER_NumberBitParPin);
  // TYPE DE PULL (No pull, Pull Down, Pull Up)
  uint8_t optionPull = 0x00;
  // Ecriture type pull
  switch (typePull) {
  case NO_PULL:
    optionPull = 0x00;
    break;
  case PULL_UP:
    optionPull = 0x01;
    break;
  case PULL_DOWN:
    optionPull = 0x02;
    break;
  default:
    optionPull = 0x00;
    break;
  }
  // Ecriture
  GPIOx->PUPDR |= optionPull << (PINx * PUPDR_NumberBitParPin);
}
</syntaxhighlight>On implémente également des fonctions qui permettent d'activer, de désactiver ou de faire clignoter (une led en l'occurence) des pins.<syntaxhighlight lang="c">
void onPin(GPIO_TypeDef *GPIOx, uint8_t PINx) {
  GPIOx->BSRR = (1 << PINx); // set
}
void offPin(GPIO_TypeDef *GPIOx, uint8_t PINx) {
  GPIOx->BSRR = (1 << (PINx + OFFSET_BSRR_OFF)); // reset
}
void togglePin(GPIO_TypeDef *GPIOx, uint8_t PINx) {
  // Après sa lecture, le registre BSRR reset à 0 automatiquement
  if (GPIOx->ODR & (1 << PINx)) // Lecture pin, si 1 alors eteindre
    offPin(GPIOx, PINx);
  else // Sinon allumer
    onPin(GPIOx, PINx);
}
</syntaxhighlight>On possède également une fonction qui initialise notre carte mère, en activant les différents ports des cartes filles, le spi, les leds et les boutons.<syntaxhighlight lang="c">
void setupCarte() {
  // 1
  setupPin(GPIOC, 0, OUTPUT); // PC0, CS1
  setupPin(GPIOC, 1, OUTPUT); // PC1, RST1
  setupPin(GPIOC, 2, OUTPUT); // PC2, INT1
  // 2
  setupPin(GPIOA, 7, OUTPUT); // PA7, CS2
  setupPin(GPIOB, 1, OUTPUT); // PB1, RST2
  setupPin(GPIOC, 4, OUTPUT); // PC4, INT2
  // 3
  setupPin(GPIOC, 9, OUTPUT); // PC9, CS3
  setupPin(GPIOB, 6, OUTPUT); // PB6, RST3
  setupPin(GPIOB, 5, OUTPUT); // PB5, INT3
  // 4
  setupPin(GPIOA, 2, OUTPUT); // PA2, CS4
  setupPin(GPIOA, 1, OUTPUT); // PA1, RST4
  setupPin(GPIOA, 0, OUTPUT); // PA0, INT4
  // 6
  setupPin(GPIOA, 4, OUTPUT); // PA4, CS6
  // FPGA
  setupPin(GPIOB, 0, OUTPUT); // PB0, CS_FPGA
  // LEDs
  setupPin(GPIOB, 8, OUTPUT); // PB8, LED1
  setupPin(GPIOA, 6, OUTPUT); // PA6, LED2
  setupPin(GPIOB, 7, OUTPUT); // PB7, LED3
  // BTNs
  setupPin(GPIOC, 12, OUTPUT); // PC12, SW_1
  setupPin(GPIOB, 11, INPUT);  // PB11, SW_2
  setupPin(GPIOC, 10, INPUT);  // PC10, SW_3
  // Test LEDs allumé
  offPin(GPIOC, 0); // PC0, CS1
  offPin(GPIOB, 1); // PA7, CS2
  offPin(GPIOC, 9); // PC9, CS3
  offPin(GPIOA, 2); // PA2, CS4
  offPin(GPIOB, 0); // PB0, CS_FPGA
  onPin(GPIOC, 12); // PB0, CS_FPGA
  // Setup SPI => MOSI, MISO et SCK
  spiInit();
  // Ecran
  ecran_init();
}
</syntaxhighlight>
====== spi ======
On initialise également les fonctions pour le SPI, à retrouver ici : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/03-Code/Carte_mere/Notre_PCB/02-Compteur/lib/SPI.
On implémente les fonctions suivantes, commentées en détail dans le spi.c :<syntaxhighlight lang="c">
void spiInit();
void spi_cs_on(GPIO_TypeDef *GPIOx, uint8_t PINx);
void spi_cs_off(GPIO_TypeDef *GPIOx, uint8_t PINx);
void spi_write(uint8_t data, GPIO_TypeDef *CS_GPIOx, uint8_t CS_PINx);
</syntaxhighlight>


On y implémente des fonctions pour les leds, le minuteur, la communication série.
====== écran ======
On écrit plusieurs fonctions pour notre carte écran, qui est pour l'instant testée par le biais de l'afficheur 7 segments d'où certainess lignes commentées.  


# leds : fonctions pour allumer, éteindre et faire clignoter (exemple de tâche).
Ces fonctions servent à :
# minuteur : pour pouvoir effectuer une tâche pendant un certain temps.
 
# communication série (deuxième exemple de tâche). Commande minicom à taper dans le terminal (-o permet d'éviter que mincom envoie des commandes d'initialisation) :
* initialiser l'écran
* écrire dessus en activant le spi
* régler l'intensité de l'écran
* nettoyer l'écran
<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
sudo minicom -D /dev/ttyACM0 -b 9600 -o
// PC11, CS5
</syntaxhighlight>[[Fichier:Minicom2.png|center|500px|alt=Minicom_|vignette|Minicom_]]
#define CS5_GPIO GPIOC
<p style="clear: both;" />
#define CS5_PIN 11
 
// PB9, RST5
#define RST5_GPIO GPIOB
#define RST5_PIN 9
 
// PC13, INT5
#define INT5_GPIO GPIOC
#define INT5_PIN 13
 
void ecran_init() {
  setupPin(CS5_GPIO, CS5_PIN, OUTPUT);
  // setupPin(RST5_GPIO, RST5_PIN, OUTPUT);
  // setupPin(INT5_GPIO, INT5_PIN, OUTPUT);
 
  onPin(CS5_GPIO, CS5_PIN); // S'active à l'etat bas
  // offPin(RST5_GPIO, RST5_PIN); // S'active à l'etat bas
}
 
void ecran_spi_write(uint8_t data) {
  spi_write(data, CS5_GPIO, CS5_PIN);
}
 
void ecran_brightness(uint8_t intensite) {
  ecran_spi_write(0x7A);      // Commande "Brightness"
  ecran_spi_write(intensite); // Sécurité passive 2^8-1 = 255 qui est le maximum
}
 
void ecran_clear() {
  ecran_spi_write(0x76); // Commande "Clear"
}
 
</syntaxhighlight>On écrit ensuite le code du compteur :<syntaxhighlight lang="c">
uint8_t isChange = 0b1111;
 
int unite = 0;
int decimal = 0;
int centaine = 0;
int mil = 0;
 
void ecran_compteur() {
 
  ecran_select_digit(3);
  ecran_spi_write(unite); // Data
 
  if (isChange & 0b0010) {
    isChange &= ~0b0010;
    ecran_select_digit(2);
    ecran_spi_write(decimal); // Data
  }


== Carte fille Clavier ==
  if (isChange & 0b0100) {
=== Boutons utilisés ===
    isChange &= ~0b0100;
Nous voulions implémenter un clavier à touche mécanique. Monsieur Xavier REDON avait des switchs qui convenait donc nous n'avions pas besoin d'en recommander.
    ecran_select_digit(1);
    ecran_spi_write(centaine); // Data
  }


Les switchs sont de la marque KAILH :
  if (isChange & 0b1000) {
[[Fichier:Boite Kailh Switch.jpg|centré|vignette|Boite Kailh Switch]]
    isChange &= ~0b1000;
[[Fichier:Kailh Hot swap socket.png|gauche|vignette|Kailh Hot swap socket]]
    ecran_select_digit(0);
[[Fichier:Bouton kailh comparaison hot swap.jpg|vignette|300x300px|Bouton kailh comparaison hot swap socket]]
    ecran_spi_write(mil); // Data
  }


  if (unite < 9) {
    unite++;
  } else {
    isChange |= 0b0010;
    unite = 0;
    if (decimal < 9)
      decimal++;
    else {
      decimal = 0;
      isChange |= 0b0100;


      if (centaine < 9) {
        centaine++;
      } else {
        centaine = 0;
        isChange |= 0b1000;


        if (mil < 9) {
          mil++;
        } else {
          mil = 0;
        }
      }
    }
  }
}
</syntaxhighlight>


Petit bémol : les '''sockets hot-swap''' ne correspondaient pas à nos boutons. Comme on peut le voir sur la photo, il existe '''deux types de sockets''' pour nos modules hot-swap. Nous avons donc dû commander les modèles adaptés.
====== main ======
<syntaxhighlight lang="c">
int main(void) {
  setupCarte();
  ecran_brightness(100);
  int j = 0;


L’intérêt de ces modules est de '''pouvoir insérer ou retirer les boutons à volonté''', sans qu’ils soient soudés directement au PCB — ce sont les sockets qui, eux, sont soudés. Cette solution présente plusieurs avantages : elle '''facilite la réutilisation des boutons''' d’un projet à un autre et '''améliore la réparabilité''' du clavier.
  while (1) {
    ecran_compteur();
    j++;


    if (j > 10) {
      j = 0;
      togglePin(GPIOB, 8); // LED1
      togglePin(GPIOA, 6); // LED2
      togglePin(GPIOB, 7); // LED3
    }


    for (volatile uint32_t i = 0; i < 25000; i++)
      ;
  }
}
</syntaxhighlight>


== Carte fille Clavier ==


=== Hardware ===


==== Boutons utilisés ====
Nous voulions implémenter un clavier à touches mécaniques. Monsieur Redon avait des switchs qui convenait donc nous n'avions pas besoin d'en recommander.


Les switchs sont de la marque KAILH :
[[Fichier:Boite Kailh.jpg|alt=Boite Kailh|vignette|center|Boite Kailh Switch]]
<p style="clear: both;" />


Petit bémol : les '''sockets hot-swap''' ne correspondaient pas à nos boutons. Comme on peut le voir sur la photo de droite, il existe '''deux types de sockets''' pour nos modules hot-swap. Nous avons donc dû commander les modèles adaptés.


[[Fichier:Kailh Hot swap socket.png|left|300px|vignette|Kailh Hot swap socket|300x300px]]
[[Fichier:Bouton kailh comparaison hot swap.jpg|right|vignette|300x300px|Bouton kailh comparaison hot swap socket]]
<p style="clear: both;" />
L’intérêt de ces modules est de '''pouvoir insérer ou retirer les boutons à volonté''', sans qu’ils soient soudés directement au PCB — ce sont les sockets qui, eux, sont soudés. Cette solution présente plusieurs avantages : elle '''facilite la réutilisation des boutons''' d’un projet à un autre et '''améliore la réparabilité''' du clavier.


==== Concevons un clavier ! ====
==== Concevons un clavier ! ====
Ligne 278 : Ligne 1 084 :
[[Fichier:Site keyboard layout .png|centré|vignette|534x534px|Site keyboard layout preset ISO 60%]]
[[Fichier:Site keyboard layout .png|centré|vignette|534x534px|Site keyboard layout preset ISO 60%]]


Nous nous sommes alors inspirés des '''claviers disponibles sur le marché''' afin d’adopter un placement des touches conforme aux dispositions les plus courantes.


Nous nous sommes alors inspirés des '''claviers disponibles sur le marché''' afin d’adopter un placement des touches conforme aux dispositions les plus courantes.
[[Fichier:Keyboard-layout.jpg|centré|vignette|575x575px|Keyboard layout]]
[[Fichier:Keyboard-layout.jpg|centré|vignette|575x575px|Keyboard layout]]
<p style="clear: both;" /><p style="clear: both;" />L’utilisation de cet outil présente plusieurs intérêts : elle permet d’'''imaginer et définir la disposition du clavier''', de '''le découper en lignes et colonnes''' afin de concevoir la '''matrice de touches''', et enfin d’'''identifier les bonnes empreintes''' à utiliser sur le futur PCB grâce au '''sommaire illustré ci-dessous'''.
<p style="clear: both;" />
 
L’utilisation de cet outil présente plusieurs intérêts : elle permet d’'''imaginer et définir la disposition du clavier''', de '''le découper en lignes et colonnes''' afin de concevoir la '''matrice de touches''', et enfin d’'''identifier les bonnes empreintes''' à utiliser sur le futur PCB grâce au '''sommaire illustré ci-dessous'''.
 
[[Fichier:Summary keyboard layout.png|centré|vignette|399x399px|Summary keyboard layout]]
[[Fichier:Summary keyboard layout.png|centré|vignette|399x399px|Summary keyboard layout]]
<p style="clear: both;" /><p style="clear: both;" />Ce sommaire indique la '''taille indicative de chaque type de touche''' ainsi que '''le nombre de touches associées''' à chacune d’elles.Le "coloriage" est utile pour voir visuellement quelle touche correspond à quelle taille.


==== Kicad - Schématique Notre carte fille comporte plusieurs éléments : ====
<p style="clear: both;" />
 
Ce sommaire indique la '''taille indicative de chaque type de touche''' ainsi que '''le nombre de touches associées''' à chacune d’elles. Le "coloriage" est utile pour voir visuellement quelle touche correspond à quelle taille.
 
On peut également sauvegarder notre configuration en exportant sous format "json". Via ce format on peut utiliser une extension de kicad qui se prénomme "'''Keyboard footprints placer'''" et qui permet de placer automatiquement les boutons si on les intancie dans le bon ordre (exemple : bouton 1 => SW1 , etc...). L'outil est un peu capricieux mais fait gagner un temps précieux sur le routage.
 
<p style="clear: both;" />
 
==== Schématique ====
'''<u>Notre carte fille comporte plusieurs éléments :</u>'''
# Le microcontrôleur ATMega32U4 avec un cristal de 16 MHz, des capacités de découplage et une ferrite (Cf AVR042) ;
# Le microcontrôleur ATMega32U4 avec un cristal de 16 MHz, des capacités de découplage et une ferrite (Cf AVR042) ;
# L'USB pour la programmation et l'alimentation pendant la phase programmation du projet ;
# L'USB pour la programmation et l'alimentation pendant la phase programmation du projet ;
Ligne 296 : Ligne 1 113 :
# Des mounting holes.
# Des mounting holes.


Remarque : Pas de leds RGB, pas assez de pins et nous ne voulions pas nous éparpiller sur trop d'idées (sujet evoquer avec M Boé).
Remarque : Pas de leds RGB, pas assez de pins et nous ne voulions pas nous éparpiller sur trop d'idées (sujet evoqué avec Monsieur Boé).


[[Fichier:Clavier schematique.pdf|center|700px|alt=Clavier schematique|vignette|Clavier schematique]]
[[Fichier:Clavier schematique.pdf|center|700px|alt=Clavier schematique|vignette|Clavier schematique]]
Ligne 305 : Ligne 1 122 :
<p style="clear: both;" />
<p style="clear: both;" />


== Carte Fille FPGA ==
==== Brasure ====
On a prévu de faire une carte fille FPGA mais ceci n'est pas notre projet principal, on commande la carte et on espère avoir le temps d'avancer sur le code de cette carte, le code pour la carte mère restant prioritaire. M. Boé nous a dit qu'on pourrait continuer au s8 si besoin.
On brase les composants sur nos deux PCB. On commence par le clavier rouge, qui s'avère bien reconnu par lsusb.
 
[[Fichier:Clavier brasé.jpg|500px|alt=clavier brasé|vignette|clavier brasé|centré]]Le PCB vert en revanche n'est pas reconnu. On teste les différentes connexions au multimètre. Le 5V est bien là. On teste alors le quartz à l'oscilloscope. On se rend compte que l'on obtient que du bruit par rapport au pcb rouge. Cependant après avoir changé le quartz, le problème est toujours présent. On finit alors par se rendre compte que l'on a soudé une capacité avec une mauvaise valeur. Le clavier vert est enfin reconnu.<p style="clear: both;" />
 
[[Fichier:Test oscillo.jpg|left|400px|alt=test_oscillo|vignette|test_oscillo]]
[[Fichier:Oscillo vert.jpg|right|300px|alt=oscillo_vert|vignette|oscillo_vert]]
[[Fichier:Oscillo rouge.jpg|right|300px|alt=oscillo_rouge|vignette|oscillo_rouge]]
 
<p style="clear: both;" />
<p style="clear: both;" />


=== Hardware ===
=== Software ===
 
==== Test Led ====
Afin de vérifier que notre clavier fonctionne, on fait un test afin de faire clignoter nos deux leds : led d'alimentation et led pour Cap Lock (qui nous servira par la suite pour savoir si notre carte est en mode majuscule ou non).
 
Remarque : la fonction setupPin est la même que celle présentée dans la section ordonnanceur de la carte shield.<syntaxhighlight lang="c">
#define F_CPU 16000000UL
 
#define LEDs_PORT PORTE
#define LEDs_DDR DDRE
#define LEDs_PIN PINE
#define LED_CapsLock PE6
 
void setupHardware() {
  setupClock();
  // Leds
  setupPin(&LEDs_PORT, &LEDs_DDR, LED_CapsLock, OUTPUT);
 
  // Bouton
  //setupPin(BTNs_PORT, BTNs_DDR, BTN_Right, INPUT_PULL_UP);
 
  // Permet de liberer le portF pour utiliser les boutons !
  MCUCR |= (1 << JTD); // 1ère écriture
  MCUCR |= (1 << JTD); // Désactiver JTAG (2ème écriture obligatoire !)
}
 
int main() {
  setupHardware();
  while (1) {
      LEDs_PORT |= (1 << LED_CapsLock); // toggle LED
      _delay_ms(500);
      LEDs_PORT &= ~(1 << LED_CapsLock); // toggle LED
      _delay_ms(500);
  }
    return 0;
}
 
</syntaxhighlight>
 
==== Matrice de boutons ====
Suite à cela, on définit une matrice de boutons. L'objectif est de reconnaître quand une touche est activée (les touches étant pour le moment sans signification).
 
On définit les colonnes :<syntaxhighlight lang="c">
#define TOTAL_COL 14
 
// --- Colonnes ---
volatile uint8_t *col_ports[TOTAL_COL] = {
    [0 ... 5] = &PORTF,  // COL0 à COL5
    [6 ... 7] = &PORTC,  // COL6 à COL7
    [8 ... 10] = &PORTB,  // COL8 à COL10
    [11 ... 13] = &PORTD, // COL11 à COL13
};
 
volatile uint8_t *col_ddr[TOTAL_COL] = {
    [0 ... 5] = &DDRF,  // COL0 à COL5
    [6 ... 7] = &DDRC,  // COL6 à COL7
    [8 ... 10] = &DDRB,  // COL8 à COL10
    [11 ... 13] = &DDRD, // COL11 à COL13
};
 
uint8_t col_pins[TOTAL_COL] = {0, 1, 4, 5, 6, 7, 7, 6, 6, 5, 4, 7, 6, 4};
</syntaxhighlight>puis les lignes :<syntaxhighlight lang="c">
#define TOTAL_ROW 5
 
// --- Lignes ---
volatile uint8_t *row_ports[TOTAL_ROW] = {
    [0 ... 4] = &PORTD,
};


==== Puce FPGA ====
volatile uint8_t *row_ddr[TOTAL_ROW] = {
Puce utilisée : XC7A15T-1FTG256C
    [0 ... 4] = &DDRD,
<p style="clear: both;" />
};
volatile uint8_t *row_pins_reg[TOTAL_ROW] = {
    [0 ... 4] = &PIND,
};


==== Datasheets ====
uint8_t row_pins[TOTAL_ROW] = {5, 3, 2, 1, 0};
La carte FPGA étant complexe, nous avons beaucoup de datasheets donc plutôt que de les ajouter une par une, nous vous conseillons d'aller directement dans l'onglet concerné de notre git : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA/Datasheet .
</syntaxhighlight>Par la suite, on configure les colonnes en sortie, les lignes en entrées avec pull-up puis on procède au scan matriciel. Pour vérifier que l'appui sur un bouton est bien detecté, on allume la led CapLock.<syntaxhighlight lang="c">
#define TOTAL_KEYSWITCH 62
uint8_t key_state[TOTAL_COL][TOTAL_ROW] = {0};


Certains extraits de ces datasheets illustrent bien comment concevoir notre schématique. Les images sont ici : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA  ou décrites plus bas dans le wiki.
int main(void) {
<p style="clear: both;" />
  setupHardware();


==== Conception ====
  // Configuration colonnes en sortie
  for (uint8_t c = 0; c < TOTAL_COL; c++) {
    setupPin(col_ports[c], col_ddr[c], col_pins[c], OUTPUT);
    onPin(col_ports[c], col_pins[c]); // mettre toutes les colonnes à 1 pour les desactiver
  }


===== Puce =====
  // Configuration ligne ROW0 en entrée avec pull-up
La puce FPGA étant assez complexe, elle ne peut être représentée en une seule entité sous kicad, elle est donc décomposée dans les unités suivantes :
  for (uint8_t r = 0; r < TOTAL_ROW; r++) {
    setupPin(row_ports[r], row_ddr[r], row_pins[r], INPUT_PULL_UP);
  }


* U1A dans la sheet FPGA_Banks_14-15
  while (1) {
* U1B dans la sheet FPGA_Banks_34-35
    // Scan matriciel
* U1C dans la sheet FPGA_Config
    for (uint8_t c = 0; c < TOTAL_COL; c++) {
* U1D dans la sheet FPGA_Power
      offPin(col_ports[c], col_pins[c]); // activer colonne (LOW)
<p style="clear: both;" />


===== Liste des sheets =====
      for (uint8_t r = 0; r < TOTAL_ROW; r++)
        key_state[c][r] = !(*row_pins_reg[r] & (1 << row_pins[r]));


# Sheet principale FPGA
      onPin(col_ports[c], col_pins[c]); // désactiver colonne (HIGH)
# FPGA_Banks_14-15
    }
# FPGA_Banks_34-35
# FPGA_Config
# FPGA_Power
# LEDs&7seg
# Switch&Button
# VGA
# HDMI
# Ethernet
<p style="clear: both;" />


===== Description des sheets =====
    int isPressed = 0;
    for (uint8_t c = 0; c < TOTAL_COL; c++) {
      for (uint8_t r = 0; r < TOTAL_ROW; r++) {
        if (key_state[c][r] == 1) {
          isPressed = 1;
        }
      }
    }


====== Sheet principale FPGA ======
    if (isPressed)
      onPin(LEDs_PORT, LED_CapsLock);
    else {
      offPin(LEDs_PORT, LED_CapsLock);
    }
  }


* Ajout de 4 mounting holes si l'on veut fixer la carte quelque part ou la protéger de la poussière avec une plaque en verre.
  return 0;
<p style="clear: both;" />
}
</syntaxhighlight>


==== Lufa ====
Afin que nos touches soient reconnus comme des lettres de l'alphabet que l'on peut voir sur notre écran, on utilise la LUFA.


====== FPGA_Banks_14-15 ======
== Carte Fille FPGA ==
On a deux sources de tension PWR_BANK_14 et PWR_BANK_15. Pour chacune de ces sources, on ajoute :


* 1 capacité de découplage de 100 uF
=== Objectif ===
* 2 capacités de découplage de 4,7 uF
Une '''carte FPGA''' est actuellement en développement en parallèle. Il s’agit d’un '''défi technique majeur''' visant à faire évoluer le projet de '''pico-ordinateur''' vers une nouvelle étape.
* 4 capacités de découplage de 0,47 uF
Ces valeurs proviennent de la page 15 de la datasheet UG483 (voir capture DecouplingInfo ci-dessous) :[[Fichier:DecouplingInfo.png|left|500px|alt=DecouplingInfo|vignette|DecouplingInfo]]
<p style="clear: both;" />


====== FPGA_Banks_34-35 ======
L’objectif, à terme, est de concevoir un '''pico-ordinateur complet''' capable de '''gérer des flux vidéo et audio''', ainsi que différents '''protocoles HID''', notamment en intégrant un '''microcontrôleur''' (comme sur la carte '''Nexys A7''', par exemple). Dans cette optique, le développement de la carte FPGA doit '''progresser au mieux''', mais il est '''possible que le travail se poursuive sur le semestre S8''', comme convenu avec '''M. Boé'''.
On a deux sources de tension PWR_BANK_34 et PWR_BANK_35. Pour chacune de ces sources, tout comme pour la sheet  FPGA_Banks_14-15, on ajoute :
=== Schématique ===
* 1 capacité de découplage de 100 uF
Les notes liées à la conception de la schématique se trouvent dans ce répertoire : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA .
* 2 capacités de découplage de 4,7 uF
* 4 capacités de découplage de 0,47 uF
Ces données proviennent également de la capture DecouplingInfo.


====== FPGA_Config ======
La schématique comporte elle même toutes les explications, il est donc inutile de revenir sur chacun de ces points ici.
Différents blocs :


# Sur l'unité U1C, on ajoute une capacité de 47 uF (cf image DecouplingInfo de la section FPGA_Banks_14-15).
==== Puce ====
# Bloc JTAG : un connecteur 2*6 pins pour la programmation (pins TMS, TCK, TDO, TDI). Se reférer à JTAG de la carte mère pour explications.
La référence de la puce FPGA à router est celle ci : XC7A15T-1FTG256C. C'est une puce de la famille Artix-7.
# USB Programming : FT32H : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA/Datasheet/FT232H.pdf. On ajoute différentes capacités de découplage, ferrites et un quartz (regarder pages 43/61).
La puce FPGA étant assez complexe, elle ne peut être représentée en une seule entité sous kicad, elle est donc décomposée dans les unités suivantes :
# USB C : diode de protection PGB1010603MR et résistance pour protéger de l'ESD (ElectroStatic Discharge) - https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA/Datasheet/ESDPROTECTION_PGB1010603MR.pdf
# FPGA Configuration Modes : 3 connecteurs de 1*3 pins pour les 3 modes de configuration du FPGA (cf image ci-dessous de la page 17 de la datasheet UG470 : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA/Datasheet/FPGA_ug470_7Series_Config.pdf).
[[Fichier:ConfigMode.png|left|500px|alt=ConfigMode|vignette|ConfigMode]]<p style="clear: both;" />


====== FPGA_Power ======
* U1A dans la sheet FPGA_Banks_14-15
On a 4 sources d'alimentation :
* U1B dans la sheet FPGA_Banks_34-35
* U1C dans la sheet FPGA_Config
* U1D dans la sheet FPGA_Power


==== Liste et Description des sheets ====
'''''FPGA_Power :'''''
Feuille regroupant l'alimentation critique de notre puce :
# VCCAUX : Auxiliary voltage (tension auxiliaire), alimente circuits internes non critiques en puissance.
# VCCAUX : Auxiliary voltage (tension auxiliaire), alimente circuits internes non critiques en puissance.
# VCCINT : Internal core voltage (tension interne du cœur logique), alimente la logique principale du FPGA (LUTs, Flip-flops).
# VCCINT : Internal core voltage (tension interne du cœur logique), alimente la logique principale du FPGA (LUTs, Flip-flops).
Ligne 389 : Ligne 1 292 :
#* VBATT : alimenter registres de configuration non volatiles ou horloge temps réel.
#* VBATT : alimenter registres de configuration non volatiles ou horloge temps réel.
# VCCBRAM : Block RAM, alimente les blocs mémoire. Cela permet de séparer l’alimentation de la mémoire afin de réduire le bruit.
# VCCBRAM : Block RAM, alimente les blocs mémoire. Cela permet de séparer l’alimentation de la mémoire afin de réduire le bruit.
Valeurs issues de :
'''''FPGA_Banks_14-15''''' et '''''FPGA_Banks_34-35''''' ''':''' Feuille disposant de l'ensemble des entrées et sorties du FPGA n'ayant pas de fonction prédisposée donc libre pour ajouter nos composants.
 
* Pour toutes les sources, se réferer à DecouplingInfo pour les valeurs des capacités de découplage (datasheet UG483 p15) sauf pour VCCADC/VBATT (regarder XADC_DecouplingCapa page 65 de la datasheet UG480 : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA/Datasheet/FPGA_ug480_7Series_XADC.pdf). Capture présente ci-dessous.
* Et pour les valeurs des alimentations, regarder AlimRecommande (extrait de la datasheet DS181 pages 2 et 3) ci-dessous.


[[Fichier:AlimRecommande.png|left|500px|alt=AlimRecommande|vignette|AlimRecommande]]
'''''FPGA_Config :''''' Feuille regroupant les pins de programmation de la puce en fonction du mode choisi au préalable ainsi que la logique data USB-C (2.0 ici) .
[[Fichier:XADC DecouplingCapa.png|right|600px|alt=XADC DecouplingCapa|vignette|XADC DecouplingCapa]]
<p style="clear: both;" />


====== LEDs&7seg ======
'''''Switch&Button :''''' ''Feuille contenant les boutons et les switchs.'' Joue le rôle de la carte matrice de boutons''.''


* Leds : on ajoute 4 leds avec des résistances de 330 Ω.
'''''LEDs&7seg :''''' ''Feuille contenant les LEDs et le 7 segments.''
* Afficheur 7 segments : TDCG1050M (https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA/Datasheet/7SEG_VISH-S-A0004391181-1.pdf).
*# Cathodes :  reliés à des résistances de 60 Ω.
*#* Cathodes A à G et DP pour les 7 segments et le point.
*#* Cathodes L1L2 et L3.
*#Anodes : reliées à des transistors PNP BC807 (https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA/Datasheet/TRANSISTOR-PNP_SBC807-25LT1G.pdf) par le biais d'une résistance de 5,1 kΩ.
*#*Anodes D1 à D4 pour allumer les 4 chiffres.
*#*Anodes L2L2 et L3
[[Fichier:Afficheur fpga.png|left|500px|alt=afficheur_fpga|vignette|afficheur_fpga]]
<p style="clear: both;" />


====== Switch&Button ======
'''''Ethernet :''''' ''Feuille contenant la logique Ethernet de notre carte.'' Joue le rôle de la carte réseau.


* Switchs : 4 switchs SPDT - Single Pole Double Throw pour choisir 3V3 ou la masse par le biais de l'entrée, reliée à une résistance de 10 kΩ.
'''''VGA :''''' ''Feuille contenant toute la logique du VGA.'' Joue le rôle de la carte écran.
* 5 boutons avec 2 résistances de 10 kΩ pour protéger si un court-circuit venait à arriver.


====== VGA ======
'''''HDMI :''''' ''Feuille abandonnée car non supportée par notre puce FPGA.''
Depuis le TP avec M. Wichmann, on comprend un peu mieux la norme vidéo VGA (Video Graphics Array).  On sait donc que le VGA possède 5 sorties :


* 3 sorties pour les leds RVB de l'écran : VGA_RED, VGA_BLUE, VGA_GREEN. Chacune de ces sorties est sur 4 bits et la conversion DAC s'effectue à l'aide d'une échelle de résistance de 500Ω, 1k, 2k puis 4k (cf cours CNSPS).
'''''Power :''''' Feuille sur la gestion de l'alimentation avec son séquençage.
* 2 sorties pour les signaux de synchronisations horizontal et vertical : VGA_H-SYNC et VGA_V-SYNC reliés à des résistances de 100Ω.


On rajoute également une capcité de découplage de 1nF en parallèle avec une résistance de 1M pour le shield.
'''''Memory :''''' Feuille contenant la mémoire SRAM de notre carte. Joue le rôle de la carte mémoire.
 
====== HDMI ======
 
<p style="clear: both;" />Connecteur HDMI (High-Definition Multimedia Interface) si on a le temps pour programmer sur plus moderne que le VGA.


<p style="clear: both;" />
<p style="clear: both;" />
====== Ethernet ======
Utilisation d'un port RJ45<p style="clear: both;" />

Version actuelle datée du 15 décembre 2025 à 10:19

Cahier des charges

L'objectif pour notre groupe est de réaliser une carte mère.

Lien GIT

Lien du git : https://gitea.plil.fr/ahouduss/SE4-Pico-B6

Carte Shield

La première étape est de réaliser un shield au cas où notre carte mère s'avérerait non fonctionnelle, afin de ne pas bloquer l'avancée des groupes des cartes filles.

Hardware

Composants

Afin de réaliser notre bouclier qui combiné à un arduino uno fera guise de carte mère, nous utilisons les composants suivants :

- Puce ATMega328-A en tant que microprocesseur

- 5 connecteurs 2*4 pour les cartes filles (clavier, écran, réseau, son) et un connecteur 2*4 pour connecter la carte mémoire.

- Des convertisseurs de niveaux logiques 5V vers 3,3V pour l'utilisation de la carte mémoire (même si il aurait été préférable de mettre la partie conversion directement sur la carte mémoire).

Schématique et vue 3D

Pico-shield_schematique
Pico-shield_schematique
CarteShield 3D
CarteShield 3D

Carte mémoire

En extension de notre shield ou de notre future carte mère, on ajoute la gestion de la mémoire avec la carte SD sur une carte mémoire distincte.

Memoire_schematic
Memoire_schematic
Memoire 3D
Memoire 3D

Brasage

On procède au brasage des cartes shield et mémoire.

cartes shield et memoire brasées
cartes shield et memoire brasées
cartes shield et memoire brasées 2
cartes shield et memoire brasées 2

Tests

Test leds

On teste les leds et on constate que notre carte shield est fonctionelle.

carte shield test leds
carte shield test leds

Test carte SD
correction_pcb
correction_pcb

On teste ensuite un programme arduino simple au préalable pour voir si la carte SD est détectée. La carte n'étant pas détectée, on regarde alors :

  1. que la connexion série est bien établie pour voir si le problème ne vient pas de l'IDE Arduino -> ce n'est pas le cas.
  2. si la carte SD est défaillante en testant le programme avec une autre carte SD. On teste aussi sur un lecteur de carte SD pour voir si elle est détectée sur le pc ce qui est le cas -> le pb ne vient pas de là non plus.
  3. on s'intéresse maintenant à l'aspect matériel, on vérifie les soudures -> toujours pas de souci particulier.
  4. schématique et routage de la carte : on s'aperçoit alors que l'on a inversé le sens du 74LVC125 de l'unité U1A pour la conversion de niveau logique du MOSI en appuyant par erreur sur le raccourci clavier x qui inverse en "miroir" le sens du composant. Remarque : on aurait aussi vraiment dû mettre le convertisseur de niveau logique sur la carte mémoire pour voir le problème plus vite. Conséquence : le routage n'est pas correct, on rajoute donc des fils pour faire les bonnes connexions et on coupe au cutter les mauvaises.

Software

Programmation carte SD

On ne programme pas la carte SD ici, on le fait directement sur la nucleo.

Ordonnanceur

Maintenant que notre shield est fonctionnel, nous pouvons réaliser notre ordonnanceur. A voir ici : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/03-Code/Carte_shield/ordonnanceur

Notre ordonnanceur est structuré de la manière suivante :

  • un main.c
  • lib qui contient les fichiers nécéssaires pour le main.c
    1. Un dossier Hardware
    2. Un dossier ordonnanceur
    3. Un dossier Task
    4. Un dossier USART
  • build : un dossier qui stocke les exécutables à part

Description des fichiers et fonctions implémentées :

HARWARE

La première étape est de faire clignoter les leds qui serviront de futures tâches. On implémente donc les fonctions suivantes :

#define LEDs_PORT &PORTD

#define LED_CS1 3
#define LED_CS2 2
#define LED_CS3 1
#define LED_CS4 0

void toggleLedCS1(){
  *LEDs_PORT ^= (1 << LED_CS1);
}

void toggleLedCS2(){
  *LEDs_PORT ^= (1 << LED_CS2);
}

void toggleLedCS3(){
  *LEDs_PORT ^= (1 << LED_CS3);
}

void toggleLedCS4(){
  *LEDs_PORT ^= (1 << LED_CS4);
}

On réutilise ensuite la fonction setupPin qui permet d'initialiser nos leds.

typedef enum {
  INPUT,
  INPUT_PULL_UP,
  OUTPUT,
} pinmode;

#define LEDs_DDR &DDRD

void setupPin(volatile uint8_t *PORTx, volatile uint8_t *DDRx, uint8_t pin, pinmode mode) {
  switch (mode) {
  case INPUT: // Forcage pin à 0
    *DDRx &= ~(1 << pin);
    break;
  case INPUT_PULL_UP: // Forcage pin à 0
    *DDRx &= ~(1 << pin);
    *PORTx |= (1 << pin);
    break;
  case OUTPUT: // Forcage pin à 1
    *DDRx |= (1 << pin);
    break;
  }
}

On initialise ensuite l'horloge :

void setupClock() {
    // Activer possibilité de changer le prescaler
    CLKPR = (1 << CLKPCE);
    // Choix diviseur
    CLKPR = 0;  
}

Puis l'hardware complet :

void setupHardware(){
  setupClock();

  setupPin(LEDs_PORT, LEDs_DDR, LED_CS1, OUTPUT);
  setupPin(LEDs_PORT, LEDs_DDR, LED_CS2, OUTPUT);
  setupPin(LEDs_PORT, LEDs_DDR, LED_CS3, OUTPUT);
  setupPin(LEDs_PORT, LEDs_DDR, LED_CS4, OUTPUT);

  init_usart();
}
USART

Dans le dossier USART, on définit les fonctions basiques pour initialiser, envoyer et recevoir depuis l'usart. L'usart nous servira par la suite pour certaines tâches.

#define BAUD_RATE 9600
#define F_CPU 16000000UL
#define UBRR_VALUE ((F_CPU/16/BAUD_RATE)-1)

void init_usart() {
  // Serial Initialization
  /*Set baud rate 9600 */
  UBRR0H = (unsigned char)(UBRR_VALUE >> 8);
  UBRR0L = (unsigned char)UBRR_VALUE;

  /* Enable receiver and transmitter */
  UCSR0B = (1 << RXEN0) | (1 << TXEN0);

  /* Frame format: 8data, No parity, 1stop bit */
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

void usart_send(unsigned char data) {
  while (!(UCSR0A & (1 << UDRE0)));
  UDR0 = data;
}

unsigned char usart_receive() {
  while (!(UCSR0A & (1 << RXC0)));
  return UDR0;
}
TASK

On pense ensuite aux tâches que l'on va implémenter dans notre ordonnanceur.

On implémente des tâches pour les leds :

void taskCS1() {
    while(1){   
        toggleLedCS1();
        _delay_ms(1000);
    } 
}

void taskCS2() {     
    while(1){   
        toggleLedCS2();
        _delay_ms(500);
    }  
}

void taskCS3() { 
    while(1){   
        toggleLedCS3();
        _delay_ms(500);
    }  
}

void taskCS4() {
    while(1){   
        toggleLedCS4();
        _delay_ms(250);
    }  
}

et pour le série :

void taskSendSerialA() {
  while (1) {
    usart_send('A');
    usart_send('\n');
    usart_send('\r');
    _delay_ms(500);
  }
}

void taskSendSerialB() {
  while (1) {
    usart_send('B');
    usart_send('\n');
    usart_send('\r');
    _delay_ms(250);
  }
}

On définit ensuite la structure générale pour les tâches.

Une tâche possède :

  • un pointeur de fonction avec le nom de la tâche
  • un pointeur pour l'adresse mémoire afin de savoir où est située la tâche dans la pile
  • l'état de la tâche : actif ou non (endormi)
  • le temps pour lequel la tâche reste endormie
typedef enum{
    AWAKE,
    SLEEP,
} state_task;

typedef struct {
    void (*taskAddress)(void);  // fonction de la tache
    uint16_t stackPointer;      // pointeur de pile
    state_task state;              // AWAKE ou SLEEP
    uint16_t sleepTime;         // temps restant en ms
} process;

On définit donc un tableau de tâches et une constante pour savoir le nombre de tâches que l'on a.

process task[] = {
    // {taskSendSerialA, 0x0780, AWAKE, 0},
    {taskCS1, 0x0730, AWAKE, 0},
    // {taskSendSerialB, 0x06E0 , AWAKE, 0},
    // {taskCS2, 0x0690, AWAKE, 0},
    // {taskCS3, 0x06E0, AWAKE, 0},
    // {taskCS4, 0x0620, AWAKE, 0},
};

const uint8_t nbTasks = sizeof(task)/sizeof(task[0]);
ORDONNANCEUR

Pour débuter notre ordonnanceur, on commence par créér un minuteur afin de faire clignoter deux leds à des périodes différentes.

void init_timer1(int diviseur, long periode_ms){
    tick_ms = periode_ms;  

    TCCR1A = 0;
    TCCR1B = (1 << WGM12); // CTC mode

    switch(diviseur){
        case 64:  TCCR1B |= (1<<CS11)|(1<<CS10); break;
        case 256: TCCR1B |= (1<<CS12); break;
    }

    OCR1A = (F_CPU / diviseur) * periode_ms / 1000;
    TCNT1 = 0;
    TIMSK1 = (1 << OCIE1A);
}

Une fois que le minuteur a atteint son nombre de "ticks", il appelle l'ISR : Interrupt Service Routine, qui prend le relai. L'ISR a besoin de la pile, on implémente donc une fonction afin d'initialiser la pile.

void init_pile(int n){
    uint16_t savedSP = SP;
    uint16_t addr = (uint16_t)task[n].taskAddress;

    SP = task[n].stackPointer;

    // PC (low puis high)
    asm volatile("push %A0" :: "r"(addr));
    asm volatile("push %B0" :: "r"(addr));

    // r0-r31
    for(int i=0; i<32; i++)
        asm volatile("push __zero_reg__");

    // SREG = I=1
    uint8_t s = 0x80;
    asm volatile("push %0" :: "r"(s));

    task[n].stackPointer = SP;
    SP = savedSP;
}

L'ISR procède ainsi :

  • Elle commence par sauvegarder les registres grâce à la fonction assembleur SAVE_REGISTERS définie dans ordonnanceur.h, qui permet de sauvegarder les registres de la tâche interrompue
  • On appelle ensuite l'ordonnanceur qui définit la manière dont les tâches sont exécutées.
  • On restore ensuite le contexte, et tous les registres de la tâche que l'on va exécuter.
ISR(TIMER1_COMPA_vect, ISR_NAKED){
    
    /* Sauvegarde du contexte de la tâche interrompue */
    SAVE_REGISTERS();
    task[currentTask].stackPointer = SP;

    /* Appel à l'ordonnanceur qui choisi la prochaine tache */
    scheduler();

    /* Récupération du contexte de la tâche ré-activée */
    SP = task[currentTask].stackPointer;
    RESTORE_REGISTERS();

    asm volatile("reti");
}

L'ordonnanceur est ici round-robin, il exécute donc les tâches les unes après les autres sans priorité pour certaines tâches.

void scheduler(void){
    currentTask = (currentTask + 1) % nbTasks;
}

Fonction afin de mettre une tâche en sommeil :

void sleep_ms(uint16_t t) {
    task[currentTask].state = SLEEP;
    task[currentTask].sleepTime = t / tick_ms;
}
MAIN

Dans le fichier main.c, on appelle diverses fonctions notamment le timer, qui appellera donc l'ISR puis le scheduler. On commence également par charger la première tâche sur la pile.

int main(void) {
  cli(); // désactiver interruptions
  setupHardware();
  // initialisation des piles
  init_tasks();
  // TIMER1 config à 20 ms
  init_timer1(64, 20);
  // charger la pile de la premi??re t??che
  SP = task[0].stackPointer;
  RESTORE_REGISTERS();
  asm volatile("reti");
  return 0;
}

Carte mère

La deuxième carte à réaliser est la carte mère avec une spécificité cependant, à savoir une puce STM32F410R8T6 en tant que microprocesseur.

Remarque : M. Redon a également fait une carte mère mais basée sur un ATSAMD21G8A-A. Celle-ci pourra nous être utile si on rencontre des soucis pour le code avec notre stm32.

Hardware

Microprocesseur

On utilise la puce STM32F410R8T6.

Signification du nom de la puce

Signification (cf p 134 de la short datasheet) :

Arm based 32-bit microcontroller

  • F = General-purpose
  • R = 64 pins
  • 8 = 64 Kbytes of Flash memory
  • T = package LQFP
  • 6 = Industrial temperature range, - 40 to 85 °C
Datasheets

Datasheet de la puce STM32F410R8T6 :

STM32_datasheet
STM32_datasheet

On peut également retrouver des pages supplémentaires afin de bien dimensionner notre quartz et les capacités aux alentours. Document AN2867- Guidelines for oscillator design on STM8AF/AL/S and STM32 MCUs/MPUs à retrouver sur le lien suivant : https://www.st.com/en/microcontrollers-microprocessors/stm32f410/documentation.html

Schématique

Notre schématique est composée de quatres sous feuilles, respectivement pour l'alimentation, le microcontrôleur, la carte mémoire et les cartes filles.

Alimentation

L'alimentation se fait via du 5V et est ensuite directement convertie en 3,3V par le biais du régulateur afin d'alimenter le microcontrôleur. C'est pourquoi les bus de données USB ne sont pas utilisées car l'USB servira ici uniquement à l'alimentation et pas à la transmission de données. On ajouté également des mounting holes pour fixer la carte.

Carte mémoire

La carte mémoire ou carte fille SD est sensiblement la même que celle pour le shield. On a juste rajouté une capacité de découplage car la carte SD va recevoir et envoyer beaucoup de données rapidement.

Microcontrôleur

Le microcontrôleur est composé de beaucoup de broches dédiées à l'alimentation, aux horloges, aux boots, à la communication, aux cartes filles, aux switchs, aux leds et au JTAG (voir sections suivantes).

Alimentation

Les broches VDD servent à l'alimentation numérique et VDDA à l'alimentation analogique, ici séparée pour filtrer de manière plus précise car plus sensible que le numérique. En effet, pour filtrer les hautes fréquences en numérique, les capacités de découplage suffisent alors qu'en analogique le signal d'entrée nécessite une gestion plus précise avec une ferrite.

Horloges

On a ici deux horloges :

  • Première horloge : on peut soit choisir l'oscillateur RC de 16 MHz ou une horloge externe comprise entre 4 et 26 MHz (p18/142)
  • Deuxième horloge : horloge pour le temps réel de 32kHZ (donc pas une application qu'on vise) (p22/142)
Boot et configuration

Les boot 0 et 1 permettent de choisir la partie ou le bloc mémoire que l'on souhaite réinitialiser. En sélectionnant 0 bit, un bit de l'un ou de l'autre ou les deux, on choisit ce que l'on souhaite réinitialiser. On peut réinitialiser :

- la flash (boot0 à 0) : mémoire non volatile pour le code principal. Adresse : 0x0800 0000 - 0x0801 FFFF d'après le memory mapping (p43/142).

- la ROM - Read Only Memory (boot0 à 1 et boot1 à 0) : mémoire non volatile que l'on change rarement sauf si besoin de changer mode communication par exemple (passage en spi, uart ...). Adresse : 0x1FFF 0000 - 0x1FFF 77FF.

- la SRAM - Static Random Access Memory (boot0 à 1 et boot1 à 1) : mémoire volatile pour le débogage. Adresse : 0x2000 0000 - 0x2000 7FFF.

On a aussi le pin NRST (Not Reset car actif à l'état bas) pour réinitialiser le microcontrôleur.

Switchs

On a 3 switchs qui peuvent servir pour choisir les modes de boot ?. [PRECISER UTILITE]

Communication

On a prévu différents types de communications selon les utilisations : SPI pour les cartes filles mais aussi UART et I2C amélioré si besoin pour une potentielle carte FPGA.

Cartes filles

On a prévu de la place pour 5 cartes filles, sans compter la carte mémoire et la carte FPGA potentielle.

Leds

3 leds supplémentaires ont étés ajoutées pour différents tests, utile pour l'ordonnanceur potentiellement par exemple.

JTAG et SWD

Le bloc JTAG sert pour le débogage de la carte. Ici sur le stm32, c'est combiné à un deuxième mode de débogage spécifique aux stm32 : le SWD, une version simplifiée de JTAG.

JTAG (Joint Test Action Group) :

  • TCK : Test Clock, l'horloge du JTAG
  • TMS : Test Mode Select
  • TDI : Test Data In pour envoyer des données depuis le JTAG au microcontrôleur.
  • TDO : Test Data Out pour envoyer des données depuis le microcontrôleur au JTAG.
  • RTCK : Return Test Clock

SWD (Serial Wire Debug) :

  • SWCLK : comme TCK
  • SWDIO : comme TMS
  • SWO : comme TDO
Cartes filles

Notre carte mère peut acceuillir 5 cartes filles communicantes en SPI parmi lesquelles :

  • carte clavier
  • carte écran
  • carte réseau
  • carte son
  • une autre carte

Et en plus de cela, on a aussi la carte "fille" pour la gestion de la mémoire = le boîtier SD (en SPI également) ainsi que la carte fille FPGA ou d'autre cartes qui peuvent communiquer en UART ou I2C amélioré (car SMBA).

Mere schematique
Mere schematique

Remarque : pour voir les différentes pages de la schématique, cliquer sur le fichier.

Vue 3D

Carte mere 3D
Carte mere 3D
Carte mere 3D backside
Carte mere 3D backside

Brasure

INSERER PHOTO

Software

NUCLEO-F410RB

En attendant de recevoir notre carte mère et afin de prendre en main la programmation quelque peu spécifique des arm, on s'entraîne sur la carte NUCLEO-F410RB qui possède le même microcontrôleur STM32F410R8T6 que celui de notre carte mère.

Spécifications

La carte NUCLEO-F410RB est "séparée" en deux parties :

  • la partie haute de la carte : c'est le programmateur spécifique à STM32 nommé ST-LINK qui permet de programmer :
  • la partie basse de la carte : qui est elle dédiée à l'utilisateur.
STM32CubeIDE
Test led

Pour commencer à comprendre, on utilise dans un premier temps l'IDE STM32CubeIDE dont l'on se passera ensuite afin d'être proche du matériel et de ne pas passer par plein de librairies.

Dans notre fichier nucleo.ioc, on a toutes les spécifications de notre carte dont le pinout.

Premier programme test : on fait clignoter la led LD2. On voit sur le fichier nucleo.ioc que LD2 est sur le pin PA5 du microcontrôleur.

En allant dans l'arborescence à gauche, on va dans Core -> Src -> main.c.

On ouvre le main et dans la boucle while, on va faire clignoter notre led D2 grâce à la librairie HAL (Hardware Abstraction Layer) dont l'on se passera par la suite (c'est juste pour les premiers tests).

 while (1)
  {
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); //change d'état
	  HAL_Delay(500); //attend 500ms
  }

Sans IDE
Test led

Après avoir testé le code dans l'IDE, on va maintenant coder avec moins de librairies subsidiaires avec juste les fichiers utiles pour la puce STM32F410RB que l'on va chercher sur le site de STMicroelectronics .

On va dans la catégorie Tools et software : https://www.st.com/en/microcontrollers-microprocessors/stm32f410rb.html#tools-software.

On pourrait sélectionner directement l'evaluation tools de la nucleo mais ce n'est pas l'objectif ici puisque la nucleo nous permet juste de s'entraîner sur le microcontrôleur F410 le temps que l'on recoive notre carte. Donc on va plutôt télécharger STM32CubeF4.

Suite à cela, on ajoutera différents fichiers :

  1. stm32cubef4-v1-28-3 : le firmware de STMicroelectronics
  2. main.c : code principal
  3. main.h
  4. ordonnanceur.c : uniquement pour l'ordonnanceur, le main.c gère le reste, les différents fichiers et variables à inclure
  5. ordonnanceur.h
  6. un makefile  : pour compiler notre projet et les fichiers du firmware. On compile avec arm-none-eabi-gcc. On rajoute les chemins d'accès nécéssaires à la compilation du main.h qui a besoin du fichier stm32f410rx.h. On a également besoin du fichier core_cm4.h, d'où le second chemin : INCLUDES = -Istm32cubef4-v1-28-3/STM32Cube_FW_F4_V1.28.0/Drivers/CMSIS/Device/ST/STM32F4xx/Include \ -Istm32cubef4-v1-28-3/STM32Cube_FW_F4_V1.28.0/Drivers/CMSIS/Include
  7. un fichier nommé linker.ld : à la différence des atmega, notre microcontrôleur a une organisation mémoire plus complexe donc ce fichier nous permet de choisir où placer chaque type de données (cf les sections elf) dans la mémoire du microcontrôleur. On y précise la taille de la flash : 128ko et la taille de la RAM : 32 ko ainsi que leurs adresses trouvées page 40/763 de la datasheet. On y initialise également les sections .text (le code), .data (variables initialisées) et .bss (variables non initialisées).
  8. un fichier startup_stm32f410rx.s : un fichier assembleur qui gère les interruptions, les resets, l'initialisation des données que l'on copie colle depuis /stm32/stm32cubef4-v1-28-3/STM32Cube_FW_F4_V1.28.0/Drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc. Remarque sur ce qu'est thumb : sur notre stm32, on utilise ARM et thumb 2 dont le rôle est d'avoir un code plus compact en mélangeant des instructions à la fois de 16 et 32 bits.

Remarque : commande à taper quand la carte "fail to connect" :

st-flash --connect-under-reset erase

Ordonnanceur

Il faut également faire l'ordonnanceur que l'on doit maintenant adapter non plus au shield et aux avr mais à la carte mère et aux arm. Notre ordonnanceur était originellement composé uniquement des fichiers ordonnanceur.c et ordonnanceur.h mais on a implémenté pas mal de fonctions donc on place celles-ci dans un nouveau fichier processus.c accompagné de son processus.h. Ci-dessous la description du contenu de ces 4 fichiers.

  • processus.h :

Il inclut les librairies nécéssaires et les prototypes des fonctions implémentées dans processus.c

  • processus.c :
  1. Fonction Delay : la fonction delay n'est pas présente sous arm donc on l'implémente ici.
    void delay(volatile uint32_t t){
        while (t--){
            __asm__("nop");
        }
    }
    
  2. Tâches pour la led : ci-dessous des fonctions permettant d'allumer, éteindre et faire clignoter la led LD2 de la nucleo.
    void init_led2(void){
        //active l’horloge pour GPIOA car ds les stm32, les peripheriques sont eteints pr economiser de l energie et on doit appeler la clock pr réveiller les gpio
        RCC->AHB1ENR |= (1 << 0);
        //on veut écrire 01 (état haut) et pas 11 (analogique) pour les broches 10 et 11 donc on doit procéder en deux étapes : d'abord effacer puis mettre en sortie
        GPIOA->MODER &= ~(0x3 << (5*2));
        GPIOA->MODER |=  (0x1 << (5*2));
    }
    
    void led2_on(void){
        GPIOA->ODR |= (1 << 5);
    }
    
    void led2_off(void){
        GPIOA->ODR &= ~(1 << 5);
    }
    
    void led2_blink(void){
        while(1){
            GPIOA->ODR ^= (1 << 5);
            delay(1000000);
        }
    }
    
    void led2_blink_task(void){
        static uint32_t counter = 0;
        counter++;
        if(counter >= 10){ // clignote toutes les 10 interruptions
            GPIOA->ODR ^= (1 << 5);
            counter = 0;
        }
    }
    
  3. Tâches pour le série : envoyer un caractère, une chaîne de caractère, recevoir et allumer une led quand l'utilisateur écrit la lettre 'a' dans le minicom.
    void init_usart2(void){
        RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
        //PA2 pour tx et PA3 pour rx
        GPIOA->MODER &= ~((3 << (2*2)) | (3 << (3*2)));
        GPIOA->MODER |=  (2 << (2*2)) | (2 << (3*2));
        GPIOA->AFR[0] &= ~((0xF << (4*2)) | (0xF << (4*3))); //efface registre alternate function
        GPIOA->AFR[0] |=  (7 << (4*2)) | (7 << (4*3)); //attribue AF7 dédié à usart2
        USART2->BRR = 0x0683; //baudrate à 9600
        USART2->CR1 = USART_CR1_TE | USART_CR1_RE; //transmit et receive enable pour tx et rx
        USART2->CR1 |= USART_CR1_UE; //usart enable
    }
    
    void usart2_send_char(char c){
        while (!(USART2->SR & USART_SR_TXE)); //qd le bit transmit data register empty est à 0 (donc data register possède des données)
        USART2->DR = c; //écrire le caractère
    }
    
    void usart2_send_string(char *s){
        while (*s){
            usart2_send_char(*s++);
        }
    }
    
    char usart2_receive(void){
        while (!(USART2->SR & USART_SR_RXNE)); //qd caractère
        return (char)(USART2->DR & 0xFF); //char sur 16bits on garde que bits de données
    }
    
    void led2_blink_serial(void){
        for (int i=0; i<10; i++){
            GPIOA->ODR ^= (1 << 5);
            delay(1000000);
        }
    }
    
    void led_serial(void){
        while(1){
            char c = usart2_receive();
            usart2_send_char(c); //echo vers Minicom
            if(c == 'a')
                led2_blink_serial();
        }
    }
    
    Commande minicom à taper dans le terminal (-o permet d'éviter que minicom envoie des commandes d'initialisation) :
    sudo minicom -D /dev/ttyACM0 -b 9600 -o
    
    Exemple de tâche (non ordonnancée pour le moment) où l'on affiche un message voulu, ici "heyyy" sur minicom
    Minicom_
    Minicom_
  4. Tâche avec le bouton : allumer la led LD2 quand on appuie sur le bouton user (en bleu, le noir étant dédié au reset).
    void init_user_button(void){
        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
        GPIOC->MODER &= ~(0x3 << (13 * 2)); //PC13 en entrée
        //pull up
        GPIOC->PUPDR &= ~(0x3 << (13 * 2));
        GPIOC->PUPDR |=  (0x1 << (13 * 2)); //01 : pull up
    }
    
    uint8_t user_button_pressed(void){
        return (GPIOC->IDR & (1 << 13)) == 0; //bouton actif à 0
    }
    
    void task_button_led(void){
        while(1){
            if(user_button_pressed()){
                led2_on();
            } else {
                led2_off();
            }
        }
    }
    

  • ordonnanceur.h :

On y ajoute les librairies, processus.h et la liste des fonctions implémentées dans ordonnanceur.c.

  • ordonnanceur.c :

Ce fichier gère le minuteur, les interruptions, la gestion des tâches.

  1. minuteur : pour pouvoir effectuer une tâche pendant un certain temps.
    extern uint32_t SystemCoreClock; //freq horloge
    
    void init_minuteur(uint16_t prescaler, uint16_t periode_ms){
      RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; //active l'horloge de TIM6
      TIM6->PSC = prescaler - 1; //prescaler
      uint32_t tick_freq = SystemCoreClock/prescaler; //période
      //TIM6->ARR = (tick_freq * periode_ms)/1000-1; //Auto-Reload Register est la période avant que le timer fasse une interruption. (tick_freq * periode_ms)/1000 correspond au nb de ticks durant cette période.
      TIM6->ARR = (uint32_t)(((uint64_t)tick_freq * periode_ms)/1000 - 1);
      TIM6->CNT = 0; //reset compteur
      TIM6->DIER |= TIM_DIER_UIE; //interruption activée quand compteur atteint nb de ticks de ARR
      TIM6->CR1 |= TIM_CR1_CEN; //compteur
      NVIC_SetPriority(TIM6_DAC_IRQn, 0x1); //prio haute
      NVIC_EnableIRQ(TIM6_DAC_IRQn); //active l'interruption dans le NVIC (Nested Vector Interrupt Controller)
    }
    

On ajoute ensuite le shield sur la nucleo afin de pouvoir tester l'ordonnanceur avec plus de tâches puisque la nucleo seule possède très peu de leds.

nucleo_ordonnanceur
nucleo_ordonnanceur
Système de fichier

Afin de débuter le système de fichiers, on connecte notre mémoire (la carte SD) à notre nucleo.

nucleo_sd
nucleo_sd

Test d'initialisation On commence par initialiser notre carte SD grâce à fichier.c et en premier lieu cela ne fonctionnait pas on avait status=1 et erreur=8 (et en conséquent une taille nulle puisqu'il n'arrive pas à initialiser la carte). La carte n'était pas de bonne qualité et ne communiquait pas en SPI mais sans doute avec un autre protocole. Mais par la suite avec la carte SD donnée par M. Redon, on a réussi à la phase d'initialisation. On obtient alors :

  • son type, ici 2 (l'autre type étant 1 pour les micro cartes sd moins performantes)
  • ainsi que sa taille : 3 911 860 secteurs. Un secteur étant de 512 octets, on retrouve bien la taille écrite sur notre carte à savoir 2Gb.
Sd init
Sd init

Suite à cela, on teste l'écriture, qui s'avère opérationelle (status = 1) puis l'écriture, elle aussi fonctionnelle puisque l'on affiche bien les 3 "blocs" voulus.

Sd read test
Sd read test
Sd write test
Sd write test

Commandes pour le système de fichier La prochaine étape est de coder les commandes nécéssaires telles que append, read, remove, rename, copy.

  1. format : efface l'entièreté du système de fichier.
  2. list : liste les noms des fichiers contenus dans le système de fichier, équivalent du "ls" sous linux.
  3. append : créé un fichier si non existant et ajoute du texte si le fichier existe déjà. Commande de la forme append fichier/données. Combine le "touch" (create dans l'énoncé) et l'écriture de données.
  4. read : permet de lire le contenu d'un fichier, équivalent du "cat".
  5. remove : supprime le fichier en paramètre, équivalent du "rm".
  6. rename : renommer un fichier, équivalent du "mv" en moins puissant.
  7. copy : copie un fichier et ses données dans un second fichier, équivalent du "cp".

Notre carte mère

Test led

Afin de vérifier que notre PCB reçu fonctionne, on teste notre carte en faisant clignoter une led.

#define LED_PIN 9

int main(void) {
  // Activer horloge GPIOC
  RCC->AHB1ENR |= (1 << RCC_AHB1ENR_GPIOCEN_Pos);

  GPIOC->MODER &= ~(0x3 << (LED_PIN * 2)); // Clear
  GPIOC->MODER |= 0x1 << (LED_PIN * 2);    // Output
  GPIOC->OTYPER &= ~(1 << LED_PIN);        // Push-pull
  GPIOC->PUPDR &= ~(0x3 << (LED_PIN * 2)); // No pull

  while (1) {
    GPIOC->ODR ^= (1 << LED_PIN);
    for (volatile uint32_t i = 0; i < 1000000; i++)
      ;
  }
}
Test écran

L'idée est d'afficher un compteur sur un afficheur 7 segments, notre "carte écran" : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/03-Code/Carte_mere/Notre_PCB/02-Compteur.

Notre test écran est structuré de la manière suivante :

  • un main.c
  • un dossier pour la carte écran
  • un dossier hardware_setup
  • un dossier pour le spi

Description des fichiers et fonctions implémentées :

hardware_setup

On reprend notre fonction setupPin que l'on adapte à arm afin d'initialiser les pins de notre carte :

#define OFFSET_BSRR_OFF 16

#define MODER_CLEAR 0x3
#define MODER_NumberBitParPin 0x2

#define PUPDR_CLEAR 0x3
#define PUPDR_NumberBitParPin 0x2

#define OTYPER_CLEAR 0x1

typedef enum {
  INPUT,
  OUTPUT,
  ALTERNATE_FUNCTION,
  ANALOG,
} portModeRegister;

typedef enum {
  NO_PULL,
  PULL_UP,
  PULL_DOWN,
} portPullUpPullDownRegister;

void setupPin(GPIO_TypeDef *GPIOx, uint8_t PINx, portModeRegister portMode) {
  // ACTIVATION DE L'HORLOGE GPIO SPECIFIQUE
  uint32_t RCC_AHB1ENR_GPIOxEN_Pos;

  if (GPIOx == GPIOA)
    RCC_AHB1ENR_GPIOxEN_Pos = RCC_AHB1ENR_GPIOAEN_Pos;
  else if (GPIOx == GPIOB)
    RCC_AHB1ENR_GPIOxEN_Pos = RCC_AHB1ENR_GPIOBEN_Pos;
  else if (GPIOx == GPIOC)
    RCC_AHB1ENR_GPIOxEN_Pos = RCC_AHB1ENR_GPIOCEN_Pos;
  else
    return; // GPIO non existant sur ce microcontroleur

  if (!(RCC->AHB1ENR & (1 << RCC_AHB1ENR_GPIOxEN_Pos)))
    RCC->AHB1ENR |= (1 << RCC_AHB1ENR_GPIOxEN_Pos);

  // CLEAR AVANT MODIFICATION
  // On clear après l'activation d'horloge !
  GPIOx->MODER &= ~(MODER_CLEAR << (PINx * MODER_NumberBitParPin));
  GPIOx->PUPDR &= ~(PUPDR_CLEAR << (PINx * PUPDR_NumberBitParPin));
  GPIOx->OTYPER &= ~(OTYPER_CLEAR << PINx); // Push-pull

  // TYPE DE PORT (Input, Output, Alternative, Analogique)
  portPullUpPullDownRegister typePull = NO_PULL;
  uint8_t optionPort = 0x00;

  switch (portMode) {
  case INPUT:
    optionPort = 0x00;
    typePull = PULL_UP;
    break;

  case OUTPUT:
    optionPort = 0x01;
    typePull = NO_PULL;

    if (1)
      GPIOx->OTYPER &= ~(1 << PINx); // Push-pull
    else
      GPIOx->OTYPER |= (1 << PINx); // Open-drain

    break;

  case ALTERNATE_FUNCTION:
    optionPort = 0x02;

    if (1) {
      typePull = NO_PULL;
      GPIOx->OTYPER &= ~(1 << PINx); // Push-pull
      GPIOx->OSPEEDR |= 11 << (PINx * 2);
      // Very high speed => Cf STM32F410 datasheet tableau p95/142
    }

    break;

  case ANALOG:
    optionPort = 0x03;
    break;

  default:
    optionPort = 0x00;
    typePull = NO_PULL;
    break;
  }

  // Ecriture
  GPIOx->MODER |= optionPort << (PINx * MODER_NumberBitParPin);

  // TYPE DE PULL (No pull, Pull Down, Pull Up)
  uint8_t optionPull = 0x00;

  // Ecriture type pull
  switch (typePull) {
  case NO_PULL:
    optionPull = 0x00;
    break;
  case PULL_UP:
    optionPull = 0x01;
    break;
  case PULL_DOWN:
    optionPull = 0x02;
    break;
  default:
    optionPull = 0x00;
    break;
  }

  // Ecriture
  GPIOx->PUPDR |= optionPull << (PINx * PUPDR_NumberBitParPin);
}

On implémente également des fonctions qui permettent d'activer, de désactiver ou de faire clignoter (une led en l'occurence) des pins.

void onPin(GPIO_TypeDef *GPIOx, uint8_t PINx) {
  GPIOx->BSRR = (1 << PINx); // set
}

void offPin(GPIO_TypeDef *GPIOx, uint8_t PINx) {
  GPIOx->BSRR = (1 << (PINx + OFFSET_BSRR_OFF)); // reset
}

void togglePin(GPIO_TypeDef *GPIOx, uint8_t PINx) {
  // Après sa lecture, le registre BSRR reset à 0 automatiquement
  if (GPIOx->ODR & (1 << PINx)) // Lecture pin, si 1 alors eteindre
    offPin(GPIOx, PINx);
  else // Sinon allumer
    onPin(GPIOx, PINx);
}

On possède également une fonction qui initialise notre carte mère, en activant les différents ports des cartes filles, le spi, les leds et les boutons.

void setupCarte() {
  // 1
  setupPin(GPIOC, 0, OUTPUT); // PC0, CS1
  setupPin(GPIOC, 1, OUTPUT); // PC1, RST1
  setupPin(GPIOC, 2, OUTPUT); // PC2, INT1

  // 2
  setupPin(GPIOA, 7, OUTPUT); // PA7, CS2
  setupPin(GPIOB, 1, OUTPUT); // PB1, RST2
  setupPin(GPIOC, 4, OUTPUT); // PC4, INT2

  // 3
  setupPin(GPIOC, 9, OUTPUT); // PC9, CS3
  setupPin(GPIOB, 6, OUTPUT); // PB6, RST3
  setupPin(GPIOB, 5, OUTPUT); // PB5, INT3

  // 4
  setupPin(GPIOA, 2, OUTPUT); // PA2, CS4
  setupPin(GPIOA, 1, OUTPUT); // PA1, RST4
  setupPin(GPIOA, 0, OUTPUT); // PA0, INT4

  // 6
  setupPin(GPIOA, 4, OUTPUT); // PA4, CS6

  // FPGA
  setupPin(GPIOB, 0, OUTPUT); // PB0, CS_FPGA

  // LEDs
  setupPin(GPIOB, 8, OUTPUT); // PB8, LED1
  setupPin(GPIOA, 6, OUTPUT); // PA6, LED2
  setupPin(GPIOB, 7, OUTPUT); // PB7, LED3

  // BTNs
  setupPin(GPIOC, 12, OUTPUT); // PC12, SW_1
  setupPin(GPIOB, 11, INPUT);  // PB11, SW_2
  setupPin(GPIOC, 10, INPUT);  // PC10, SW_3

  // Test LEDs allumé
  offPin(GPIOC, 0); // PC0, CS1
  offPin(GPIOB, 1); // PA7, CS2
  offPin(GPIOC, 9); // PC9, CS3
  offPin(GPIOA, 2); // PA2, CS4
  offPin(GPIOB, 0); // PB0, CS_FPGA

  onPin(GPIOC, 12); // PB0, CS_FPGA
  // Setup SPI => MOSI, MISO et SCK
  spiInit();

  // Ecran
  ecran_init();
}
spi

On initialise également les fonctions pour le SPI, à retrouver ici : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/03-Code/Carte_mere/Notre_PCB/02-Compteur/lib/SPI.

On implémente les fonctions suivantes, commentées en détail dans le spi.c :

void spiInit();
void spi_cs_on(GPIO_TypeDef *GPIOx, uint8_t PINx);
void spi_cs_off(GPIO_TypeDef *GPIOx, uint8_t PINx);
void spi_write(uint8_t data, GPIO_TypeDef *CS_GPIOx, uint8_t CS_PINx);
écran

On écrit plusieurs fonctions pour notre carte écran, qui est pour l'instant testée par le biais de l'afficheur 7 segments d'où certainess lignes commentées.

Ces fonctions servent à :

  • initialiser l'écran
  • écrire dessus en activant le spi
  • régler l'intensité de l'écran
  • nettoyer l'écran
// PC11, CS5
#define CS5_GPIO GPIOC
#define CS5_PIN 11

// PB9, RST5
#define RST5_GPIO GPIOB
#define RST5_PIN 9

// PC13, INT5
#define INT5_GPIO GPIOC
#define INT5_PIN 13

void ecran_init() {
  setupPin(CS5_GPIO, CS5_PIN, OUTPUT);
  // setupPin(RST5_GPIO, RST5_PIN, OUTPUT);
  // setupPin(INT5_GPIO, INT5_PIN, OUTPUT);

  onPin(CS5_GPIO, CS5_PIN); // S'active à l'etat bas
  // offPin(RST5_GPIO, RST5_PIN); // S'active à l'etat bas
}

void ecran_spi_write(uint8_t data) {
  spi_write(data, CS5_GPIO, CS5_PIN);
}

void ecran_brightness(uint8_t intensite) {
  ecran_spi_write(0x7A);      // Commande "Brightness"
  ecran_spi_write(intensite); // Sécurité passive 2^8-1 = 255 qui est le maximum
}

void ecran_clear() {
  ecran_spi_write(0x76); // Commande "Clear"
}

On écrit ensuite le code du compteur :

uint8_t isChange = 0b1111;

int unite = 0;
int decimal = 0;
int centaine = 0;
int mil = 0;

void ecran_compteur() {

  ecran_select_digit(3);
  ecran_spi_write(unite); // Data

  if (isChange & 0b0010) {
    isChange &= ~0b0010;
    ecran_select_digit(2);
    ecran_spi_write(decimal); // Data
  }

  if (isChange & 0b0100) {
    isChange &= ~0b0100;
    ecran_select_digit(1);
    ecran_spi_write(centaine); // Data
  }

  if (isChange & 0b1000) {
    isChange &= ~0b1000;
    ecran_select_digit(0);
    ecran_spi_write(mil); // Data
  }

  if (unite < 9) {
    unite++;
  } else {
    isChange |= 0b0010;
    unite = 0;
    if (decimal < 9)
      decimal++;
    else {
      decimal = 0;
      isChange |= 0b0100;

      if (centaine < 9) {
        centaine++;
      } else {
        centaine = 0;
        isChange |= 0b1000;

        if (mil < 9) {
          mil++;
        } else {
          mil = 0;
        }
      }
    }
  }
}
main
int main(void) {
  setupCarte();
  ecran_brightness(100);
  int j = 0;

  while (1) {
    ecran_compteur();
    j++;

    if (j > 10) {
      j = 0;
      togglePin(GPIOB, 8); // LED1
      togglePin(GPIOA, 6); // LED2
      togglePin(GPIOB, 7); // LED3
    }

    for (volatile uint32_t i = 0; i < 25000; i++)
      ;
  }
}

Carte fille Clavier

Hardware

Boutons utilisés

Nous voulions implémenter un clavier à touches mécaniques. Monsieur Redon avait des switchs qui convenait donc nous n'avions pas besoin d'en recommander.

Les switchs sont de la marque KAILH :

Boite Kailh
Boite Kailh Switch

Petit bémol : les sockets hot-swap ne correspondaient pas à nos boutons. Comme on peut le voir sur la photo de droite, il existe deux types de sockets pour nos modules hot-swap. Nous avons donc dû commander les modèles adaptés.

Kailh Hot swap socket
Bouton kailh comparaison hot swap socket

L’intérêt de ces modules est de pouvoir insérer ou retirer les boutons à volonté, sans qu’ils soient soudés directement au PCB — ce sont les sockets qui, eux, sont soudés. Cette solution présente plusieurs avantages : elle facilite la réutilisation des boutons d’un projet à un autre et améliore la réparabilité du clavier.

Concevons un clavier !

Notre clavier doit comporter 62 touches, conformément au format standard ISO 60 %, et sera capable d’assurer l’ensemble des combinaisons de touches attendues pour un clavier moderne en 2025.

Afin de customiser notre clavier, on se rend sur le site keyboard-layout-editor .

Nous pouvons partir d'un modèle de base ou alors d'un preset :

Site keyboard layout preset ISO 60%

Nous nous sommes alors inspirés des claviers disponibles sur le marché afin d’adopter un placement des touches conforme aux dispositions les plus courantes.

Keyboard layout

L’utilisation de cet outil présente plusieurs intérêts : elle permet d’imaginer et définir la disposition du clavier, de le découper en lignes et colonnes afin de concevoir la matrice de touches, et enfin d’identifier les bonnes empreintes à utiliser sur le futur PCB grâce au sommaire illustré ci-dessous.

Summary keyboard layout

Ce sommaire indique la taille indicative de chaque type de touche ainsi que le nombre de touches associées à chacune d’elles. Le "coloriage" est utile pour voir visuellement quelle touche correspond à quelle taille. On peut également sauvegarder notre configuration en exportant sous format "json". Via ce format on peut utiliser une extension de kicad qui se prénomme "Keyboard footprints placer" et qui permet de placer automatiquement les boutons si on les intancie dans le bon ordre (exemple : bouton 1 => SW1 , etc...). L'outil est un peu capricieux mais fait gagner un temps précieux sur le routage.

Schématique

Notre carte fille comporte plusieurs éléments :

  1. Le microcontrôleur ATMega32U4 avec un cristal de 16 MHz, des capacités de découplage et une ferrite (Cf AVR042) ;
  2. L'USB pour la programmation et l'alimentation pendant la phase programmation du projet ;
  3. Le connecteur ISP ;
  4. Les boutons RST et HWB ;
  5. Le connecteur SPI pour la communication avec la carte mère ;
  6. La led pour l'alimentation de la carte ;
  7. La led pour l'état du clavier (rôle ?) ;
  8. La matrice de touches évidemment ;
  9. Des mounting holes.

Remarque : Pas de leds RGB, pas assez de pins et nous ne voulions pas nous éparpiller sur trop d'idées (sujet evoqué avec Monsieur Boé).

Clavier schematique
Clavier schematique

Vue 3D

Keyboard 3D up
Keyboard 3D back

Brasure

On brase les composants sur nos deux PCB. On commence par le clavier rouge, qui s'avère bien reconnu par lsusb.

clavier brasé
clavier brasé

Le PCB vert en revanche n'est pas reconnu. On teste les différentes connexions au multimètre. Le 5V est bien là. On teste alors le quartz à l'oscilloscope. On se rend compte que l'on obtient que du bruit par rapport au pcb rouge. Cependant après avoir changé le quartz, le problème est toujours présent. On finit alors par se rendre compte que l'on a soudé une capacité avec une mauvaise valeur. Le clavier vert est enfin reconnu.

test_oscillo
test_oscillo
oscillo_vert
oscillo_vert
oscillo_rouge
oscillo_rouge

Software

Test Led

Afin de vérifier que notre clavier fonctionne, on fait un test afin de faire clignoter nos deux leds : led d'alimentation et led pour Cap Lock (qui nous servira par la suite pour savoir si notre carte est en mode majuscule ou non).

Remarque : la fonction setupPin est la même que celle présentée dans la section ordonnanceur de la carte shield.

#define F_CPU 16000000UL

#define LEDs_PORT PORTE
#define LEDs_DDR DDRE
#define LEDs_PIN PINE
#define LED_CapsLock PE6

void setupHardware() {
  setupClock();
  // Leds
  setupPin(&LEDs_PORT, &LEDs_DDR, LED_CapsLock, OUTPUT);

  // Bouton
  //setupPin(BTNs_PORT, BTNs_DDR, BTN_Right, INPUT_PULL_UP);

  // Permet de liberer le portF pour utiliser les boutons !
  MCUCR |= (1 << JTD); // 1ère écriture
  MCUCR |= (1 << JTD); // Désactiver JTAG (2ème écriture obligatoire !)
}

int main() {
  setupHardware();
  while (1) {
      LEDs_PORT |= (1 << LED_CapsLock); // toggle LED
      _delay_ms(500);
      LEDs_PORT &= ~(1 << LED_CapsLock); // toggle LED
      _delay_ms(500);
  }
    return 0;
}

Matrice de boutons

Suite à cela, on définit une matrice de boutons. L'objectif est de reconnaître quand une touche est activée (les touches étant pour le moment sans signification).

On définit les colonnes :

#define TOTAL_COL 14

// --- Colonnes ---
volatile uint8_t *col_ports[TOTAL_COL] = {
    [0 ... 5] = &PORTF,   // COL0 à COL5
    [6 ... 7] = &PORTC,   // COL6 à COL7
    [8 ... 10] = &PORTB,  // COL8 à COL10
    [11 ... 13] = &PORTD, // COL11 à COL13
};

volatile uint8_t *col_ddr[TOTAL_COL] = {
    [0 ... 5] = &DDRF,   // COL0 à COL5
    [6 ... 7] = &DDRC,   // COL6 à COL7
    [8 ... 10] = &DDRB,  // COL8 à COL10
    [11 ... 13] = &DDRD, // COL11 à COL13
};

uint8_t col_pins[TOTAL_COL] = {0, 1, 4, 5, 6, 7, 7, 6, 6, 5, 4, 7, 6, 4};

puis les lignes :

#define TOTAL_ROW 5

// --- Lignes ---
volatile uint8_t *row_ports[TOTAL_ROW] = {
    [0 ... 4] = &PORTD,
};

volatile uint8_t *row_ddr[TOTAL_ROW] = {
    [0 ... 4] = &DDRD,
};
volatile uint8_t *row_pins_reg[TOTAL_ROW] = {
    [0 ... 4] = &PIND,
};

uint8_t row_pins[TOTAL_ROW] = {5, 3, 2, 1, 0};

Par la suite, on configure les colonnes en sortie, les lignes en entrées avec pull-up puis on procède au scan matriciel. Pour vérifier que l'appui sur un bouton est bien detecté, on allume la led CapLock.

#define TOTAL_KEYSWITCH 62
uint8_t key_state[TOTAL_COL][TOTAL_ROW] = {0};

int main(void) {
  setupHardware();

  // Configuration colonnes en sortie
  for (uint8_t c = 0; c < TOTAL_COL; c++) {
    setupPin(col_ports[c], col_ddr[c], col_pins[c], OUTPUT);
    onPin(col_ports[c], col_pins[c]); // mettre toutes les colonnes à 1 pour les desactiver
  }

  // Configuration ligne ROW0 en entrée avec pull-up
  for (uint8_t r = 0; r < TOTAL_ROW; r++) {
    setupPin(row_ports[r], row_ddr[r], row_pins[r], INPUT_PULL_UP);
  }

  while (1) {
    // Scan matriciel
    for (uint8_t c = 0; c < TOTAL_COL; c++) {
      offPin(col_ports[c], col_pins[c]); // activer colonne (LOW)

      for (uint8_t r = 0; r < TOTAL_ROW; r++)
        key_state[c][r] = !(*row_pins_reg[r] & (1 << row_pins[r]));

      onPin(col_ports[c], col_pins[c]); // désactiver colonne (HIGH)
    }

    int isPressed = 0;
    for (uint8_t c = 0; c < TOTAL_COL; c++) {
      for (uint8_t r = 0; r < TOTAL_ROW; r++) {
        if (key_state[c][r] == 1) {
          isPressed = 1;
        }
      }
    }

    if (isPressed)
      onPin(LEDs_PORT, LED_CapsLock);
    else {
      offPin(LEDs_PORT, LED_CapsLock);
    }
  }

  return 0;
}

Lufa

Afin que nos touches soient reconnus comme des lettres de l'alphabet que l'on peut voir sur notre écran, on utilise la LUFA.

Carte Fille FPGA

Objectif

Une carte FPGA est actuellement en développement en parallèle. Il s’agit d’un défi technique majeur visant à faire évoluer le projet de pico-ordinateur vers une nouvelle étape.

L’objectif, à terme, est de concevoir un pico-ordinateur complet capable de gérer des flux vidéo et audio, ainsi que différents protocoles HID, notamment en intégrant un microcontrôleur (comme sur la carte Nexys A7, par exemple). Dans cette optique, le développement de la carte FPGA doit progresser au mieux, mais il est possible que le travail se poursuive sur le semestre S8, comme convenu avec M. Boé.

Schématique

Les notes liées à la conception de la schématique se trouvent dans ce répertoire : https://gitea.plil.fr/ahouduss/SE4-Pico-B6/src/branch/master/01-Kicad/Recherche%20FGPA .

La schématique comporte elle même toutes les explications, il est donc inutile de revenir sur chacun de ces points ici.

Puce

La référence de la puce FPGA à router est celle ci : XC7A15T-1FTG256C. C'est une puce de la famille Artix-7. La puce FPGA étant assez complexe, elle ne peut être représentée en une seule entité sous kicad, elle est donc décomposée dans les unités suivantes :

  • U1A dans la sheet FPGA_Banks_14-15
  • U1B dans la sheet FPGA_Banks_34-35
  • U1C dans la sheet FPGA_Config
  • U1D dans la sheet FPGA_Power

Liste et Description des sheets

FPGA_Power :

Feuille regroupant l'alimentation critique de notre puce :

  1. VCCAUX : Auxiliary voltage (tension auxiliaire), alimente circuits internes non critiques en puissance.
  2. VCCINT : Internal core voltage (tension interne du cœur logique), alimente la logique principale du FPGA (LUTs, Flip-flops).
  3. VCCADC/BATT :
    • VCCADC : tension pour le module ADC si le FPGA en a un.
    • VBATT : alimenter registres de configuration non volatiles ou horloge temps réel.
  4. VCCBRAM : Block RAM, alimente les blocs mémoire. Cela permet de séparer l’alimentation de la mémoire afin de réduire le bruit.

FPGA_Banks_14-15 et FPGA_Banks_34-35 : Feuille disposant de l'ensemble des entrées et sorties du FPGA n'ayant pas de fonction prédisposée donc libre pour ajouter nos composants.

FPGA_Config : Feuille regroupant les pins de programmation de la puce en fonction du mode choisi au préalable ainsi que la logique data USB-C (2.0 ici) .

Switch&Button : Feuille contenant les boutons et les switchs. Joue le rôle de la carte matrice de boutons.

LEDs&7seg : Feuille contenant les LEDs et le 7 segments.

Ethernet : Feuille contenant la logique Ethernet de notre carte. Joue le rôle de la carte réseau.

VGA : Feuille contenant toute la logique du VGA. Joue le rôle de la carte écran.

HDMI : Feuille abandonnée car non supportée par notre puce FPGA.

Power : Feuille sur la gestion de l'alimentation avec son séquençage.

Memory : Feuille contenant la mémoire SRAM de notre carte. Joue le rôle de la carte mémoire.