SE4Binome2025-6
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
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.
Brasage
On procède au brasage des cartes shield et mémoire.
Tests
Test leds
On teste les leds et on constate que notre carte shield est fonctionelle.
Test carte SD
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 :
- 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.
- 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.
- 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. Le routage à été modifié sur kicad par la suite et l'erreur réparé comme on peut le voir sur la photo.
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
Faire un ordonnanceur sur une architecture AVR est nécessaire si l'on souhaite ensuite le faire sur une architecture ARM Cortex M4 qui est bien plus complexe.
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 :
HARDWARE
#include "hardware.h"
#include <avr/io.h>
#include <util/delay.h>
#include "../USART/usart.h"
void setupClock() {
// Activer possibilité de changer le prescaler
CLKPR = (1 << CLKPCE);
// Choix diviseur
CLKPR = 0;
}
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;
}
}
int readPin(volatile uint8_t *PINx, uint8_t pin) {
return (*PINx & (1 << pin));
}
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();
}
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);
}
Et son .h :
#ifndef HARDWARE_H
#define HARDWARE_H
#include <stdint.h>
// ------------------ Enum ------------------ //
typedef enum {
INPUT,
INPUT_PULL_UP,
OUTPUT,
} pinmode;
// ------------------ LEDs ------------------ //
#define LEDs_PORT &PORTD
#define LEDs_DDR &DDRD
#define LEDs_PIN PIND
#define LED_CS1 3
#define LED_CS2 2
#define LED_CS3 1
#define LED_CS4 0
// ------------------ Prototypes ------------------ //
void setupClock();
void setupPin(volatile uint8_t* PORTx, volatile uint8_t* DDRx, uint8_t pin, pinmode mode);
int readPin(volatile uint8_t* PINx, uint8_t pin);
void setupHardware();
void toggleLedCS1();
void toggleLedCS2();
void toggleLedCS3();
void toggleLedCS4();
void taskToggleCS1();
void taskToggleCS2();
void taskToggleCS3();
void taskToggleCS4();
#endif
Dans la librairie hardware.c nous avons es ensembles de fonctions lié à notre matérielle pour son initialisation et son contrôle ou sa lecture (de pin).
USART
Dans la librarie 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.
#include "usart.h"
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
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;
}
Et usart.h :
#ifndef USART_H
#define USART_H
// Baud rate variable que l'on peut modifier
#define BAUD_RATE 9600
#define F_CPU 16000000UL
#define UBRR_VALUE ((F_CPU / 16 / BAUD_RATE) - 1)
void init_usart();
void usart_send(unsigned char data);
unsigned char usart_receive();
#endif
TASK
Task.c :
#include "../HARDWARE/hardware.h"
#include "../ORDONNANCEUR/ordonnanceur.h"
#include "../USART/usart.h"
#include "task.h"
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]);
void init_tasks() {
for (int i = 0; i < nbTasks; i++) {
init_pile(i);
}
}
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);
}
}
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);
}
}
Task.h:
#ifndef TASK_H
#define TASK_H
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;
extern process task[];
extern const uint8_t nbTasks;
void init_tasks();
void task_led(void (*toggleFunc)(void), uint16_t ms);
void taskCS1();
void taskCS2();
void taskCS3();
void taskCS4();
void taskSendSerialA();
void taskSendSerialB();
#endif
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
On définit un tableau de tâches ainsi :
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]);
Le nombre de tâche est calculé automatique pour ne pas s'embêter à le faire à chaque fois que l'on modifie de le tableau de tâche.
Si certaines tâches sont commentés c'est parceque sur cette 1ere version d'ordonnanceur, nous avons fais au plus simple sans contrôle de tache endormi, nous ne pourrons donc pas avoir un résultat propre avec plusieurs tâches simultanément.
ORDONNANCEUR
ordonnanceur.c :
#include "ordonnanceur.h"
#include <avr/interrupt.h>
#include "../HARDWARE/hardware.h"
int currentTask = 0;
uint16_t tick_ms = 0;
// ------------------ TIMER ------------------ //
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);
}
// ------------------ 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;
}
// ------------------ SCHEDULER ------------------ //
void scheduler(void) {
currentTask = (currentTask + 1) % nbTasks;
}
// ------------------ ISR TIMER1 ------------------ //
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");
}
Nous avons ici un ordonnanceur préemptif : une fois que le minuteur a atteint son nombre de "ticks", il appelle l'ISR : Interrupt Service Routine qui va :
- 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
- Appeller l'ordonnanceur qui va faire la bascule des tâches.
- Restorer 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é sous sa forme la plus minimaliste.
MAIN
Dans le fichier main.c, voici comment les librairies vu ensemble precedemment sont appelés :
#include <avr/interrupt.h>
#include <util/delay.h>
#include "./lib/HARDWARE/hardware.h"
#include "./lib/ORDONNANCEUR/ordonnanceur.h"
#include "./lib/TASK/task.h"
#include "./lib/USART/usart.h"
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 microcontrolleur.
Remarque : M. Redon a également fait une carte mère mais basée sur un ATSAMD21G8A-A sur notre Git.
Hardware
Microprocesseur
On utilise la puce STM32F410R8T6 basé sur un Cortex-M4.
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 :
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
Toutes les datasheets ayant servi pour la creation du Kicad de la carte se trouve dans le dossier Datasheet.
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 le bloc mémoire :
- 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.
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 tester en premier lieu le microcontrolleur puis l'ordonnanceur.
JTAG et SWD
Le bloc JTAG sert pour la programmation de la carte :
- 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).
Vue 3D
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
}
C'est un peu l'équivalent arduino, on code avec l'abstraction matérielle. Nous allons par la suite proceder au niveau bare metal.
Bare metal
Ce chapitre présente la programmation Bare Metal sur microcontrôleurs ARM, en utilisant un STM32 NUCLEO-F410RB comme exemple. Nous aborderons le startup, le linker, les exemples de clignotement LED, l'utilisation de CMSIS, ainsi que la gestion de périphériques comme le SPI, l'UART, etc...
A FAIRE PROPREMENT
Système de fichier
Afin de débuter le système de fichiers, on connecte notre mémoire (la carte SD) à notre nucleo.
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.
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.
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".
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 :
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.
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 :
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.
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.
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 :
- 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 ;
- Le connecteur ISP ;
- Les boutons RST et HWB ;
- Le connecteur SPI pour la communication avec la carte mère ;
- La led pour l'alimentation de la carte ;
- La led pour l'état du clavier (rôle ?) ;
- La matrice de touches évidemment ;
- 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é).
Vue 3D
Brasure
Nous avons soudé le stricte minimum sur notre carte pour le faire fonctioner avant tout puisque que nous souhaitons trouvé les éventuelles anomalies de soudure ou de conception avant chaque grosse étape. Sur la PCB rouge et la 1ère pcb verte tout est ok.
La seconde PCB verte 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 qui semble donner du bruit dans le cas de notre pcb dysfonctionnelle. 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 inversé une capacité avec une resistance, le problème est donc résolu rapidement.
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);
// 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;
}
Détection de notre matrice de boutons
On créer un fichier qui pourra être facilement importer dans nos différents projets afin d'avoir une détection de touche facilement importer dans la suite : la LUFA.
Voici alors clavier.c :
#include "clavier.h"
#include "../lib/HARDWARE/hardware.h"
#include <avr/io.h>
// --------- 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
};
volatile uint8_t *col_pins_reg[TOTAL_COL] = {
[0 ... 5] = &PINF, // COL0 à COL5
[6 ... 7] = &PINC, // COL6 à COL7
[8 ... 10] = &PINB, // COL8 à COL10
[11 ... 13] = &PIND, // COL11 à COL13
};
uint8_t col_pins[TOTAL_COL] = {0, 1, 4, 5, 6, 7, 7, 6, 6, 5, 4, 7, 6, 4};
// --------- Lignes ---------
volatile uint8_t *row_ports[TOTAL_ROW] = {
[0 ... 4] = &PORTD,
};
volatile uint8_t *row_ddr[TOTAL_ROW] = {
[0 ... 4] = &DDRD,
};
uint8_t row_pins[TOTAL_ROW] = {5, 3, 2, 1, 0};
uint8_t key_state[TOTAL_COL][TOTAL_ROW] = {0};
void init_matrix_button(void) {
// Configuration colonnes en entrée avec pull-up
for (uint8_t c = 0; c < TOTAL_COL; c++) {
setupPin(col_ports[c], col_ddr[c], col_pins[c], INPUT_PULL_UP);
}
// Configuration ligne en sortie
for (uint8_t r = 0; r < TOTAL_ROW; r++) {
setupPin(row_ports[r], row_ddr[r], row_pins[r], OUTPUT);
onPin(row_ports[r], row_pins[r]); // mettre toutes les lignes à 1 pour les desactiver
}
}
void scan() {
for (uint8_t r = 0; r < TOTAL_ROW; r++) {
offPin(row_ports[r], row_pins[r]); // activer ligne (LOW)
for (uint8_t c = 0; c < TOTAL_COL; c++)
key_state[c][r] = !(*col_pins_reg[c] & (1 << col_pins[c]));
onPin(row_ports[r], row_pins[r]); // désactiver ligne (HIGH)
}
}
Le scan se fait facilement une fois la logique assimilé... Et clavier.h :
#ifndef CLAVIER_H
#define CLAVIER_H
#include "keyswitch.h"
#include <stdint.h>
#include "clavier_conversion.h"
extern uint8_t key_state[TOTAL_COL][TOTAL_ROW];
void init_matrix_button(void);
void scan(void);
#endif
Et ensuite, nous avons pleins de define afin de lire chaque bouton individuellement au lieu d'appeler un tableau (pas intuitif pour l'utilisateur) dans un fichier keyswitch.h :
#ifndef KEYSWITCH_H
#define KEYSWITCH_H
#include <stdint.h>
#define TOTAL_KEYSWITCH 62
#define TOTAL_COL 14
#define TOTAL_ROW 5
// COL0 PF0 | COL1 PF1 | COL2 PF4 | COL3 PF5 | COL4 PF6 | COL5 PF7 | COL6 PC7 |
// COL7 PC6 | COL8 PB6 | COL9 PB5 | COL10 PB4 | COL11 PD7 | COL12 PD6 | COL13 PD4
typedef enum{
COL0 = 0,
COL1 = 1,
COL2 = 2,
COL3 = 3,
COL4 = 4,
COL5 = 5,
COL6 = 6,
COL7 = 7,
COL8 = 8,
COL9 = 9,
COL10 = 10,
COL11 = 11,
COL12 = 12,
COL13 = 13,
} COLs;
// ROW0 PD5 | ROW1 PD3 | ROW2 PD2 | ROW3 PD1 | ROW4 PD0
typedef enum{
ROW0 = 0,
ROW1 = 1,
ROW2 = 2,
ROW3 = 3,
ROW4 = 4,
} ROWs;
extern uint8_t key_state[TOTAL_COL][TOTAL_ROW];
#define btn1 key_state[COL0][ROW0]
#define btn2 key_state[COL1][ROW0]
#define btn3 key_state[COL2][ROW0]
#define btn4 key_state[COL3][ROW0]
#define btn5 key_state[COL4][ROW0]
#define btn6 key_state[COL5][ROW0]
#define btn7 key_state[COL6][ROW0]
#define btn8 key_state[COL7][ROW0]
#define btn9 key_state[COL8][ROW0]
#define btn10 key_state[COL9][ROW0]
#define btn11 key_state[COL10][ROW0]
#define btn12 key_state[COL11][ROW0]
#define btn13 key_state[COL12][ROW0]
#define btn14 key_state[COL13][ROW0]
#define btn15 key_state[COL0][ROW1]
#define btn16 key_state[COL1][ROW1]
#define btn17 key_state[COL2][ROW1]
#define btn18 key_state[COL3][ROW1]
#define btn19 key_state[COL4][ROW1]
#define btn20 key_state[COL5][ROW1]
#define btn21 key_state[COL6][ROW1]
#define btn22 key_state[COL7][ROW1]
#define btn23 key_state[COL8][ROW1]
#define btn24 key_state[COL9][ROW1]
#define btn25 key_state[COL10][ROW1]
#define btn26 key_state[COL11][ROW1]
#define btn27 key_state[COL12][ROW1]
#define btn28 key_state[COL13][ROW1]
#define btn29 key_state[COL0][ROW2]
#define btn30 key_state[COL1][ROW2]
#define btn31 key_state[COL2][ROW2]
#define btn32 key_state[COL3][ROW2]
#define btn33 key_state[COL4][ROW2]
#define btn34 key_state[COL5][ROW2]
#define btn35 key_state[COL6][ROW2]
#define btn36 key_state[COL7][ROW2]
#define btn37 key_state[COL8][ROW2]
#define btn38 key_state[COL9][ROW2]
#define btn39 key_state[COL10][ROW2]
#define btn40 key_state[COL11][ROW2]
#define btn41 key_state[COL12][ROW2]
#define btn42 key_state[COL0][ROW3]
#define btn43 key_state[COL1][ROW3]
#define btn44 key_state[COL2][ROW3]
#define btn45 key_state[COL3][ROW3]
#define btn46 key_state[COL4][ROW3]
#define btn47 key_state[COL5][ROW3]
#define btn48 key_state[COL6][ROW3]
#define btn49 key_state[COL7][ROW3]
#define btn50 key_state[COL8][ROW3]
#define btn51 key_state[COL9][ROW3]
#define btn52 key_state[COL10][ROW3]
#define btn53 key_state[COL11][ROW3]
#define btn54 key_state[COL13][ROW3]
#define btn55 key_state[COL0][ROW4]
#define btn56 key_state[COL1][ROW4]
#define btn57 key_state[COL2][ROW4]
#define btn58 key_state[COL6][ROW4]
#define btn59 key_state[COL10][ROW4]
#define btn60 key_state[COL11][ROW4]
#define btn61 key_state[COL12][ROW4]
#define btn62 key_state[COL13][ROW4]
#endif
La bibliothèque à été pensé pour être facilement réadaptable et lisible pour un utilisateur souhaitant re-coder un clavier mais également pour quelqu'un ne souhaitant pas refaire l'hardware et juste coder/utiliser le clavier existant.
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. Rien de bien compliqué, on retrouve le projet LUFA sur ce lien github : https://github.com/abcminiuser/lufa
On télécharge le projet et on vient extraire la librairie LUFA et l'exemple Keyboard présent dans le dossier Démo. On modifie le Makefile présent ainsi :
MES_LIBS = lib/HARDWARE/hardware.c lib/CLAVIER/clavier.c
MCU = atmega32u4
ARCH = AVR8
BOARD = NONE
F_CPU = 16000000
F_USB = $(F_CPU)
OPTIMIZATION = s
TARGET = Keyboard
SRC = $(TARGET).c Descriptors.c $(MES_LIBS) $(LUFA_SRC_USB) $(LUFA_SRC_USBCLASS)
LUFA_PATH = ../LUFA
CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER -IConfig/
LD_FLAGS =
# Default target
all:
# Include LUFA-specific DMBS extension modules
DMBS_LUFA_PATH ?= $(LUFA_PATH)/Build/LUFA
include $(DMBS_LUFA_PATH)/lufa-sources.mk
include $(DMBS_LUFA_PATH)/lufa-gcc.mk
# Include common DMBS build system modules
DMBS_PATH ?= $(LUFA_PATH)/Build/DMBS/DMBS
include $(DMBS_PATH)/core.mk
include $(DMBS_PATH)/cppcheck.mk
include $(DMBS_PATH)/doxygen.mk
include $(DMBS_PATH)/dfu.mk
include $(DMBS_PATH)/gcc.mk
include $(DMBS_PATH)/hid.mk
include $(DMBS_PATH)/avrdude.mk
include $(DMBS_PATH)/atprogram.mk
PROGRAMMER = avrdude
AVRDUDE_PORT = /dev/ttyACM0
AVRDUDE_BAUD = 115200
AVRDUDE_PROGRAMMER = avr109
upload: $(TARGET).hex
$(PROGRAMMER) -v -p $(MCU) -c $(AVRDUDE_PROGRAMMER) \
-P $(AVRDUDE_PORT) -b $(AVRDUDE_BAUD) -D \
-U flash:w:$(TARGET).hex:i
La ligne upload est necessaire seulement si vous avez un bootloader personnalisé, auquel cas faite un simple make dfu pour téléverser. Ensuite on vient ajouter nos libraries et notre code de logique aux fichiers Keyboard.c :
#include "./lib/CLAVIER/clavier.h"
#include "./lib/HARDWARE/hardware.h"
// ... je passe les détails basique du fichier
void SetupHardware() {
/* Disable watchdog if enabled by bootloader/fuses */
MCUSR &= ~(1 << WDRF);
wdt_disable();
/* Disable clock division */
clock_prescale_set(clock_div_1);
/* Hardware Initialization */
setupHardware();
init_matrix_button();
// Initialisation USB obligatoire
USB_Init();
}
// ... je passe les détails basique du fichier
bool CALLBACK_HID_Device_CreateHIDReport(USB_ClassInfo_HID_Device_t *const HIDInterfaceInfo,
uint8_t *const ReportID,
const uint8_t ReportType,
void *ReportData,
uint16_t *const ReportSize) {
USB_KeyboardReport_Data_t *KeyboardReport = (USB_KeyboardReport_Data_t *)ReportData;
uint8_t UsedKeyCodes = 0;
scan();
// ROW0
if (btn1) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_ESCAPE);
if (btn2 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_1_AND_EXCLAMATION);
if (btn3 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_2_AND_AT);
if (btn4 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_3_AND_HASHMARK);
if (btn5 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_4_AND_DOLLAR);
if (btn6 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_5_AND_PERCENTAGE);
if (btn7 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_6_AND_CARET);
if (btn8 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_7_AND_AMPERSAND);
if (btn9 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_8_AND_ASTERISK);
if (btn10 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_9_AND_OPENING_PARENTHESIS);
if (btn11 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_0_AND_CLOSING_PARENTHESIS);
if (btn12 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_MINUS_AND_UNDERSCORE); // ) ° ]
if (btn13 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_EQUAL_AND_PLUS); // = + }
if (btn14) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_BACKSPACE);
// ROW1
if (btn15) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_TAB);
if (btn16) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_A);
if (btn17) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_Z);
if (btn18) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_E);
if (btn19) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_R);
if (btn20) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_T);
if (btn21) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_Y);
if (btn22) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_U);
if (btn23) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_I);
if (btn24) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_O);
if (btn25) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_P);
if (btn26) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_OPENING_BRACKET_AND_OPENING_BRACE); // ^¨
if (btn27) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_CLOSING_BRACKET_AND_CLOSING_BRACE); // $£¤
if (btn28) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_ENTER);
// ROW2
if (btn29) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_CAPS_LOCK);
if (btn30) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_Q);
if (btn31) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_S);
if (btn32) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_D);
if (btn33) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F);
if (btn34) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_G);
if (btn35) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_H);
if (btn36) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_J);
if (btn37) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_K);
if (btn38) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_L);
if (btn39) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_M);
if (btn40) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_APOSTROPHE_AND_QUOTE); // % ù
if (btn41) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_NON_US_HASHMARK_AND_TILDE); // µ *
// ROW3
if (btn42) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_LEFT_SHIFT);
if (btn43) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_NON_US_BACKSLASH_AND_PIPE);
if (btn44) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_W);
if (btn45) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_X);
if (btn46) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_C);
if (btn47) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_V);
if (btn48) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_B);
if (btn49) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_N);
if (btn50) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_COMMA_AND_LESS_THAN_SIGN); // ,?
// if (btn51) KeyboardReport->KeyCode[UsedKeyCodes++] = ; // ;.
if (btn52) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_DOT_AND_GREATER_THAN_SIGN); // :/
if (btn53 && !btn62) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_SLASH_AND_QUESTION_MARK); // !§
if (btn54) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_RIGHT_SHIFT);
// ROW4
if (btn55) KeyboardReport->Modifier |= QWERTY_to_AZERTY(HID_KEYBOARD_MODIFIER_LEFTCTRL);
if (btn56) KeyboardReport->Modifier |= QWERTY_to_AZERTY(HID_KEYBOARD_MODIFIER_LEFTGUI);
if (btn57) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_LEFT_ALT);
if (btn58) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_SPACE);
if (btn59 && !btn62) KeyboardReport->Modifier |= QWERTY_to_AZERTY(HID_KEYBOARD_MODIFIER_RIGHTALT);
// if (btn60 && !btn62) KeyboardReport->Keyboard |= QWERTY_to_AZERTY(); // Trouver une fonction a celui ci
if (btn61 && !btn62) KeyboardReport->Modifier |= QWERTY_to_AZERTY(HID_KEYBOARD_MODIFIER_RIGHTCTRL);
// Fonction spéciale du clavier côté HARDWARE
if(btn62){
// Fonction F1 à F12
if (btn2) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F1);
if (btn3) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F2);
if (btn4) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F3);
if (btn5) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F4);
if (btn6) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F5);
if (btn7) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F6);
if (btn8) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F7);
if (btn9) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F8);
if (btn10) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F9);
if (btn11) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F10);
if (btn12) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F11);
if (btn13) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_F12);
// Déplacement
if (btn60) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_DOWN_ARROW);
if (btn59) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_LEFT_ARROW);
if (btn61) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_RIGHT_ARROW);
if (btn53) KeyboardReport->KeyCode[UsedKeyCodes++] = QWERTY_to_AZERTY(HID_KEYBOARD_SC_UP_ARROW);
}
*ReportSize = sizeof(USB_KeyboardReport_Data_t);
return false;
}
void CALLBACK_HID_Device_ProcessHIDReport(USB_ClassInfo_HID_Device_t *const HIDInterfaceInfo,
const uint8_t ReportID,
const uint8_t ReportType,
const void *ReportData,
const uint16_t ReportSize) {
uint8_t *LEDReport = (uint8_t *)ReportData;
if (*LEDReport & HID_KEYBOARD_LED_CAPSLOCK)
onPin(LEDs_PORT,LED_CapsLock);
else
offPin(LEDs_PORT,LED_CapsLock);
}
J'ai fais une petite macro (reprise de l'année dernière, projet manette) pour convertir les caractères sur un clavier AZERTY dans le fichier clavier_conversion.h :
#ifndef CLAVIER_CONVERSION_H
#define CLAVIER_CONVERSION_H
#include <../../LUFA/Drivers/USB/USB.h>
static inline uint8_t QWERTY_to_AZERTY(uint8_t qwerty_code) {
switch (qwerty_code) {
// Lettres
case HID_KEYBOARD_SC_Q: return HID_KEYBOARD_SC_A;
case HID_KEYBOARD_SC_W: return HID_KEYBOARD_SC_Z;
case HID_KEYBOARD_SC_A: return HID_KEYBOARD_SC_Q;
case HID_KEYBOARD_SC_Z: return HID_KEYBOARD_SC_W;
case HID_KEYBOARD_SC_M: return HID_KEYBOARD_SC_SEMICOLON_AND_COLON;
case HID_KEYBOARD_SC_COMMA_AND_LESS_THAN_SIGN: return HID_KEYBOARD_SC_M;
default: return qwerty_code;
}
}
static inline uint8_t AZERTY_to_QWERTY(uint8_t azerty_code) {
switch (azerty_code) {
// Lettres
case HID_KEYBOARD_SC_A: return HID_KEYBOARD_SC_Q;
case HID_KEYBOARD_SC_Z: return HID_KEYBOARD_SC_W;
case HID_KEYBOARD_SC_Q: return HID_KEYBOARD_SC_A;
case HID_KEYBOARD_SC_W: return HID_KEYBOARD_SC_Z;
case HID_KEYBOARD_SC_SEMICOLON_AND_COLON: return HID_KEYBOARD_SC_M;
case HID_KEYBOARD_SC_M: return HID_KEYBOARD_SC_COMMA_AND_LESS_THAN_SIGN;
default: return azerty_code;
}
}
#endif
J'ai eu beaucoup de mal a trouver certains caractères mais le document présent ci dessous m'as grandement aidé :
A partir de la page 89 nous avons l'ensemble des codes et certains détails pour certains d'entre eux. Malgré tout une touche n'as pas réussi à être mapper (je n'ai pas trouvé le code équivalent pour mon clavier AZERTY), cette touche correspond au point virgule / point (;.) (Bouton 51).
Cependant le code reste fonctionnelle, j'y ai ajouté la possiblité de se deplacer via la touche FN qui correspond aux fonctionnalité spéciale, comme toutes les touches F1,F2,...F10,F11,F12 qui sont mappé aux touches 1,2,...,0,°,+.
Communication carte mère
Partie a détailler plus tard.
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 :
- 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).
- 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.
- 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.