
# Programmation Bare Metal ARM

### Sommaire
- [Programmation Bare Metal ARM](#programmation-bare-metal-arm)
    - [Sommaire](#sommaire)
  - [Startup (.s)](#startup-s)
  - [Linker (.ld)](#linker-ld)
  - [Exemple Blink](#exemple-blink)
  - [CMSIS - Dépendances requises](#cmsis---dépendances-requises)
    - [Fichiers utiles](#fichiers-utiles)
  - [Blink avancer](#blink-avancer)
  - [Pin de la board NUCLEO-F410RB](#pin-de-la-board-nucleo-f410rb)
  - [Fonction primaire architecture ARM](#fonction-primaire-architecture-arm)
    - [Clignotement LED](#clignotement-led)
    - [Acceder a PA2 et PA3](#acceder-a-pa2-et-pa3)
    - [SPI](#spi)
    - [Ordonnanceur](#ordonnanceur)
  - [Faire du debug sur STM](#faire-du-debug-sur-stm)
    - [Primitive sur terminal](#primitive-sur-terminal)
    - [Avec interface graphique](#avec-interface-graphique)
  - [Mes ressources](#mes-ressources)
  

Ce projet 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.
Le but final est d'avoir un code pour la carte mère.

## Startup (.s)
Le Startup file est crucial en Bare Metal. Une erreur dans ce fichier peut empêcher le microcontrôleur de démarrer ou provoquer des crashs.

**Rôles principaux :**
- Définir l'environnement nécessaire à l'exécution de main().
- S'exécuter avant main() et lancer ensuite main().
- Être adapté à la target (processeur) utilisée.
- Placer correctement la table des vecteurs comme exigé par les ARM Cortex-M.
- Initialiser la pile correctement.
- Initialiser les sections .data et .bss dans la SRAM.
  
## Linker (.ld)
Le Linker Script détermine comment les sections du code sont placées en mémoire.

**Fonctionnalités :**
- Définir les adresses absolues des sections.
- Définir les zones mémoire, leurs tailles et adresses.
- Fournir les instructions au linker GNU via l'option -T.
- L'extension de fichier est .ld.
- Pour plus de détail c'est par [ici](./02-linker/).

## Exemple Blink
Dans [ce dossier](./03-blink/), nous allons téléverser notre 1er programme qui consiste à simplement faire clignoter une LED. 

**Processus de compilation :**
- Le code source .c est compilé en fichiers .o.
- Le linker assemble ces fichiers et les place correctement en mémoire, générant un fichier .elf.
- Le fichier .elf est converti en .bin et téléversé dans le microcontrôleur via ST-LINK ou OpenOCD.

Voici le processus de build afin de televerser un code sur un ARM :
![](img/buildProcess.png)

Nous retrouvons notre code principale en .c qui est compiler pour en ressortir des fichiers .o qui seront ensuite traiter dans le bloc Linker qui fera le lien entre le code et les blocs mémoires réelles. A la sortie nous aurons des .elf qui un fois dans le programmer le transformera en .bin et y sera téléverser dans le uControleur.

Le second schéma montre la chaine de compilation dans le détail :
![](img/chaineCompilation.png)

Ici on peut voir l'importance de chaque fichier tel que le linker et voir quel outil est utilisé à quel moment.

Et ici dans le détail la compilation (du .c au point .o) : 
![](img/Resume_process_1.png)

## CMSIS - Dépendances requises 
C'est à [ce lien](https://github.com/STMicroelectronics/cmsis-device-f4) qu'on vient télécharger la bibliothèque CMSIS (Cortex Microcontroller Software Interface Standard).

CMSIS (Cortex Microcontroller Software Interface Standard) simplifie l'accès au matériel :

- Définitions des registres MCU.
- Prototypes de fonctions système (SystemInit()).
- Table des vecteurs d'interruptions.
  
C'est une bibliothèque fournie par ARM et ST, qui simplifie l'accès au matériel en proposant :
- Des définitions pour tous les registres du MCU
- Les prototypes des fonctions système (comme SystemInit())
- La table des vecteurs d'interruptions (startup code)

Si on veut la bibliothèque contenant plus d'information et des examples on peut se fier a [ce repertoire.](https://github.com/STMicroelectronics/STM32CubeF4/tree/master)

### Fichiers utiles
Nous faisons un premier tri pour ne garder que les fichiers que nous utiliserons pour notre microcontrolleur. Ces fichiers peuvent être retrouvé dans le dossier [library](./01-lib/)

## Blink avancer
Récuperation des fichiers ```syscall.c``` et ```sysmem.c``` : https://github.com/STMicroelectronics/STM32CubeF4/tree/master/Projects/STM32F410xx-Nucleo/Templates/STM32CubeIDE/Example/User

Modification du Makefile pour inclure ces fichiers et corriger les erreurs de compilation.

Ainsi, nous obtenons une base solide pour programmer notre microcontrôleur.

## Pin de la board NUCLEO-F410RB
![](img/pinBoard.png)

Cf datasheet Nucleo-64 page 38/91

## Fonction primaire architecture ARM
### Clignotement LED

Afin de faire clignoter une LED il faut :

1. Activer l'horloge du GPIO correspondant.
Pour ce faire, nous modifions le registre RCC->AHB1ENR. (Cf datasheet RM0401 p.119/763)

```c
    RCC->AHB1ENR |= (1 << RCC_AHB1ENR_GPIO<PORT>EN_Pos);
```

2. Définir la direction du PIN via le registre 
GPIO<PORT>->MODER. (Cf datasheet RM0401 p.150/763)

```c
    GPIO<PORT>->MODER |= 0x1 << (<PIN_NUM> * 2);
```
"PB2" => PORT = B et PIN_NUM = 2

En gros on fait un *2 parceque comme on peut le voir sur le tableau ci dessous :
![](img/tableauRegistreGpioMODER.png)

Ici par exemple le PIN 3 correspond au 6ieme et 7ieme bit de MODER.
```c
    GPIO<PORT>->MODER |= 0x1 << (<PIN_NUM> * 2);    // Output
```

3. Définir le type de sortie (Registre OTYPER) 
   
Cf datasheet page 150/763. Le tableau est ci dessous :
![](img/TableRegistreGpioOTYPER.png)

```c
    GPIO<PORT>->OTYPER &= ~(0x1 << (<PIN_NUM>));    // Push pull
```
4. Définir le type de pull (Registre PUPDR) 
   
Cf datasheet page 151/763. Le tableau est ci dessous :
![](img/tableauRegistreGpioPUPDR.png)

```c
  GPIO<PORT>->PUPDR &= ~(0x3 << (<PIN_NUM> * 2)); // Forcage 00 : No pull
```

5. Etape finale : écriture sur le pin (Registre ODR)
Cf datasheet page 151/763. Le tableau est ci dessous :
![](img/tableauRegistreGpioODR.png)

```c
    GPIO<PORT>->ODR ^= (1 << <PIN_NUM>);
```
On peut également modifier de façon atomique (donc juste 1 bit pas tous) via le registre BSRR.

Exemple :
```c
    if (GPIO<PORT>->ODR & (1 << <PIN_NUM>))
      GPIO<PORT>->BSRR = (1 << (<PIN_NUM> + 16)); // Ici 16 est l'offset à ajouter afin d'avoir le bon registre pour eteindre
    else
      GPIO<PORT>->BSRR = (1 << <PIN_NUM>);
```
Lorsque BSRR est lu il est reset juste après et cela permet d'éviter les conflits si une interruption intervient et modifie ODR entre 2 instruction.

Après on peut également toucher a la vitesse du pin (Registre OSPEEDR) si l'on souhaite optimiser la consommation de batterie.

*Note : Remplacer <PORT> par A ou B ou C ou etc... et remplacer <PIN> par 1,2,etc.. selon ce qu'on veut activer*  

*Exemple :* Nous avons sur notre Shield arduino une LED (CS1) sur PB3.  
### Acceder a PA2 et PA3
SB62 and SB63 must be ON, while SB13 and SB14 must be OFF.
Donc dessouder les resistances bridges pour acceder ces ports. Et ensuite deplacer le jumper sur U5V.

Cf Chapitre7.10 p26/91 datasheet UM1724

### SPI
Nous allons programmer le SPI sur notre microcontrolleur et tester ce code pour la carte Sparkfun Serial 7-Segments Display.

Sur [ce git](https://github.com/sparkfun/Serial7SegmentDisplay/wiki/Serial-7-Segment-Display-Datasheet) se trouve la datasheet de cette carte que nous allons utiliser pour communiquer en SPI.

Dans l'idée, nous allons programmer une bibliothèque suffisament complète afin de pouvoir simplement d'afficher "9" via une commande dans ce style dans notre main : ``` spi_sent('G'); ```

On peut tout d'abord centraliser la logique des initialisations des pins afin d'alleger le main (Cf fichier hardware_setup.c et .h).

Voici les pins correspondant à la carte :

![](img/carte7SegmentSparkFunPinout.png)

Celle-ci contient sur sa carte un afficheur 7 segments et un microcontrolleur pour gérer une logique SPI. Ce qui serait l'équivalent d'une carte écran à laquelle on vient échanger des données.

Pour gérer une liaison SPI nous connectons les pins suivants:

| STM32 Nucleo-F410 | 7 segment Sparkfun |
| ----------------- | ------------------ |
| X                 | RX                 |
| CS5 => PBO        | SS                 |
| MOSI => PA6       | SDI                |
| RST5 => PA1       | RST                |
| SCK => PA5        | SCK                |
| MISO => PA6       | SDO                |
| 5V                | VCC                |
| GND               | GND                |
| X                 | A6                 |
| X                 | A7                 |

### Ordonnanceur 
Lien utile :
- https://www.espacetechnologue.com/wp-content/uploads/2020/04/CH1_-architectureARM.pdf
- https://documentation-service.arm.com/static/5efefb97dbdee951c1cd5aaf?token=
- https://dev.to/carolineee/how-to-implement-multitasking-scheduling-mechanism-on-a-microcontroller-2n9k
- https://s-o-c.org/saving-and-restoring-task-context-on-cortex-m/
- https://github.com/ventZl/arm-bare-rtos/tree/mast

## Faire du debug sur STM
### Primitive sur terminal

1. Mise en place du serveur ST-Link

```st-util``` met le ST-Link en mode serveur GDB.
Par défaut, le serveur écoute sur localhost:4242.
Le serveur GDB permet simplement à GDB (l'outil de debug) de communiquer avec le microcontrôleur.

2. Lancer GDB et se connecter au microcontrolleur
   
Dans un autre terminal, lancer le debug : ```arm-none-eabi-gdb <votre_fichier_elf>``` Ici mon fichier se trouver a ce path : ./build/main.elf (le fichier compilé pour le STM32)
C'est important que ce soit le même que celui qui est sur le STM32 sinon ça ne marchera pas.

Se connecter au serveur GDB créé par st-util : ```target remote <ip_instance>```.
Dans mon cas l'ip d'instance, indiqué après avoir tapé st_util est localhost:4242

3. Commandes GDB essentielles
   
**Exécution du programme**
- *continue ou c* : Reprend l'exécution jusqu'au prochain breakpoint
- *step ou s* :	Exécute une ligne de code et entre dans les fonctions
- *next ou n* :	Exécute une ligne sans entrer dans les fonctions
- *finish* : Termine l'exécution de la fonction courante
- *break main* : Place un breakpoint à la fonction main
- *break <fichier.c>:<num_ligne>* :Place un breakpoint à la ligne 42 du fichier

**Inspection mémoire et registres**
- *info registers* : Affiche tous les registres CPU (R0-R15, SP, LR, PSR...)
- *x/10x 0x20000000* : Affiche 10 mots en mémoire à une adresse donnée (ex. SRAM)
- *print <variable>* : Affiche la valeur d'une variable locale ou globale
- *set <variable>=<valeur>* : Modifie la valeur d'une variable

**Flash et contrôle du MCU**
- *load* : Flashe le binaire ELF dans le microcontrôleur
- *monitor reset init* : Réinitialise le MCU et initialise le debug

Pour plus de commande faite la commande ```help```.

### Avec interface graphique
Pour simplifier le debug et avoir toutes les informations nécessaires à portée de vue, nous allons utiliser l'extension Cortex-Debug dans VSCodium.

**1. Installer l'extension **Cortex-Debug****

Ouvrir VSCodium(ou Vscode) => aller dans Extensions => Rechercher et installer Cortex-Debug.

![](img/extensionVSCod.png)

**2. Inserer cette configuration dans le fichier launch.json :**

Ouvrir le fichier launch.json (ou créer un nouveau fichier de configuration de debug). Peut se faire en cliquant sur l'icone Play dans le menu "Run and Debug".

Ajouter la configuration suivante :
```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "stlink",
            "gdbPath": "arm-none-eabi-gdb",
            "runToEntryPoint": "main",
            "showDevDebugOutput": "raw",

            "device": "STM32F410Rx",
            "executable": "${workspaceFolder}/05-SPI/build/main.elf",
            "cwd": "${workspaceFolder}/05-SPI",
            "svdFile": "${workspaceFolder}/01-lib/Debug/STM32F410.svd",

            "preLaunchTask": "build",
        }
    ]
}
```

- **device** : Correspond au modèle ***exact*** de votre STM32. Ici STM32F410Rx.
- **executable** : Chemin vers le fichier ELF généré lors de la compilation.
C'est ce fichier que GDB va charger et exécuter.
- **svdFile** : Fichier SVD contenant la description des registres du microcontrôleur. Il permet une visualisation intuitive des registres dans l'interface graphique Cortex-Debug.correspond au modèle ***exact*** de votre STM32. Ici STM32F410Rx
- **cwd** : Répertoire dans lequel se trouve le projet à déboguer (répertoire de travail).
- **preLaunchTask** : Tâche à exécuter avant de lancer le debug, ici "build", pour recompiler automatiquement le projet. Peut être retirer si vous preferez le faire vous même. <br>
Cortex-Debug préfère que ce soit lui qui charge le programme via le debugger (ST-Link) plutôt que st-flash qui utilise un protocole séparé.

**3. Récupérer les fichiers SVD**
Le fichier .svd contient la description des registres et périphériques du microcontrôleur, nécessaire pour une visualisation graphique claire.

Voici les liens ou l'on peut récuperer le fichier SVD correspondant à notre puce : 
1. Github : https://github.com/modm-io/cmsis-svd-stm32
2. ou le site officiel : https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html#cad-resources

Je les places dans le dossier 01-lib/Debug afin de trier correctement mes dependances.

Et voici le resultat : 
![](img/visualisationDebug.png)

On voit qu'on a accès à la mémoire qu'on peut rechercher une adresse spécififique, qu'on peut acceder à la valeur de nos variable. Ici  ```y``` vaut 3 car j'ai lancer 3 fois la boucle jusqu'au breakpoint. Pour poser un breakpoint faut juste cliquer aux endroits àà coté du numéro de ligne. <br>
On peut lire et écrire directement dans les registres de périphérique (ici ODR correspond à des regitre physique donc 0 ou 3,3V).

J'espère que le tutoriel est clair, ce qui est fait en terme de debug marche sur tout type de microcontroleur pour ceux qui découvre grâce à moi ;). Ca evite les printf à tout va, pas évident de debug en plus si on fait du bare metal.

## Mes ressources
- https://kleinembedded.com/
- https://github.com/cpq/bare-metal-programming-guide/tree/main
- https://github.com/ErniW/ARM-STM32-bare-metal-examples/tree/main
- https://www.linuxembedded.fr/2021/02/bare-metal-from-zero-to-blink
Bibliothèque standard qui fournit les définitions matérielles et le startup code :

- **Stricte essentielle :** https://github.com/STMicroelectronics/cmsis-device-f4
- **Plus complet :** https://github.com/STMicroelectronics/STM32CubeF4/tree/master
