Sommaire
- Présentation de l'architecture du RISC-V
- Gigadevice et Nuclei
- Un peu d'assembleur !
- Présentation de la carte électronique
- Arsenal de développement
- Le Hello World de l'embarqué : Blinky
- Hello World plus poussé : L'UART
- Les différentes fréquences et le timer
- NMSIS et le SysTick
- Solutions de débogage
- Conclusion
Nous avons déjà parlé du RISC-V dans un article précédent avec la carte HiFive dotée d'un microcontrôleur pas si intéressant. Celui présenté cette fois-ci semble beaucoup plus standard : il fait le plein de périphériques et de mémoire Flash embarquée.
Présentation de l'architecture du RISC-V
Nous avons déjà un peu présenté l'historique du RISC V dans l'article précédent. Essayons maintenant de découvrir le modèle de programmation ; cela nous servira plus tard lors de débogages et développement bas niveau.
La fondation RISC a rédigé deux spécifications de cette nouvelle ISA (Instruction Set Architecture) :
- Un document général à l'accès non-privilégié
- Les spécifications des accès privilégiés
Qu'est ce que c'est que l'accès privilégié ? Il faut savoir que les processeurs de type "PC", qui sont voués à être utilisés avec un gros système d'exploitation type Linux/Windows/Mac disposent de protection d'accès physique. En gros, c'est le processeur qui s'assure qu'une instruction assembleur peut être exécutée. Cela permet d'offrir une séparation entre les processus systèmes et utilisateur. Sous Linux par exemple, un utilisateur privilégié est 'root'.
Intéressons-nous à cette première documentation qui décrit le coeur RISC-V minimal, le RV32I, ainsi que toutes les extensions optionnelles. En effet, l'architecture décrite permet de créer des processeurs de 32-bits, 64-bits ou 128-bits, c'est à dire du petit microcontrôleur au processeur dopé pour serveurs ou calculs scientifiques (c'est à l'origine le but de cette architecture créée à l'université de Berkley).
Cette première documentation nous montre donc le modèle de programmation, la liste des registres :
L'architecture est donc assez simple, j'aime bien. Nous avons 32 registres à usage générique, tout du moins si on est seul au monde et que l'on ne souhaite pas développer quelque chose de "portable". Typiquement un logiciel développé en assembleur. Bien entendu, il s'agit ici des registres entiers. L'architecture RISC-V dispose de beaucoup d'extensions optionnelles dont par exemple l'extension offrant un jeu de registres flottants (IEEE 754).
Rappel de la codification des extensions :
- Coeur RV32 'I' (A load-store ISA with 32, 32-bit general-purpose integer registers) avec les options :
- M : Integer Multiplication and Division
- A : Atomics
- C : 16-bit Compressed Instructions
- F : Single-Precision Floating-Point
- D : Double-Precision Floating-Point
- Q : Quad-Precision Floating-Point
Au moment de l'écriture de cet article, plusieurs extensions supplémentaires sont prévues mais non encore décites (Bit manipulation, Dynamically Translated Languages, Vector Operations …). On le voit, cette architecture est vouée à évoluer dans les prochaines années.
Enfin, la spécification décrit également le langage assembleur et spécifie une convention d'appel et de comportement (ABI, Application Binary Interface) des registres, ceci étant nécessaires pour préciser quels registres sont utilisés pour les arguments des fonctions, les divers pointeurs, adresses de retour etc.
Chaque cœur RISC-V dispose de son propre contrôleur d'interruption appelé CLIC (Core Local Interrupt Controller), il est dit logical car rattaché à un seul coeur ce qui peut avoir sens dans un composant multi-coeurs.
Gigadevice et Nuclei
D'après mes recherches et ce que j'en ai compris, l'IP du coeur RISC-V est pris chez la société Nuclei qui dispose d'un catalogue complet de coeurs (https://www.nucleisys.com/product/rvipes/n200/). Le notre, c'est celui-ci :
Ce qui va nous intéresser ici est la présence d'un timer dans le coeur même. Typiquement, l'usage sera pour séquencer un système ; chez ARM, ils l'ont standardisé et l'ont appelé SystTick. Je suis un peu déçu d'ailleurs sur ce point, car avoir un timer standardisé permet de passer du code d'un microcontrôleur à l'autre sans ce soucier de ce point.
Pour la performance, le coeur s'annonce plus véloce et moins gourmand que son principal concurrent, la famille Cortex-M :
Bon c'est une documentation constructeur, il faut en prendre et en laisser car on ne connaît pas les conditions de test. En réel, cela dépendera beaucoup de la performance de la Flash embarquée au MCU et de bien d'autres paramètres.
Un peu d'assembleur !
Essayons de tâter un peu plus profondément l'architecture. Pour s'amuser, nous allons utiliser le simulateur en ligne BRISC. On copie le programme ci-dessous qui réalise une petite boucle d'incrémentation d'un registre. Retirez les commentaires, ils ne passent pas sur le simulateur :
.file "example.c"
.option nopic
.text
.align 2
.globl example
main:
li a0, 3 # chargement immédiat d'une valeur
li a1, 10 # a1 contiendra notre valeur finale de sortie
loop:
addi a0, a0, 1 # a0 = a0 + 1
bne a0, a1, end # Branch if Not Equal
j loop # sinon on saute 'Jump'
end:
Il est possible d'avancer pas à pas et de voir graphiquement le changement des différents registres.
Si vous voulez aller plus loin, Western Digital diffuse un tutorial complet d'assembleur sur Youtube en utilisant la carte HiFive : https://www.youtube.com/watch?v=KLybwrpfQ3I&list=PL6noQ0vZDAdh_aGvqKvxd0brXImHXMuLY.
N'oubliez pas le petit émulateur développé par le génie Fabrice Bellard : https://bellard.org/tinyemu/ qui fournit en plus une image Linux prête à l'emploi !
Présentation de la carte électronique
La société GigaDevice a donc conçu ce microcontrôleur à base du coeur libre RISC-V dans une version RV32IMAC. La version que nous allons utiliser ici est la référence GD32VF103CBT6 qui embarque 128Ko de Flash et 32Ko de RAM. La fréquence du processeur monte à 108 Mhz et les périphériques sont légions : USART, I2C, SPI, CAN, USB, I2S, ADC 12 bits. Bref, le minimum syndical ! Les GPIO des périphériques sont remappables, attention toutefois, dans une certaine mesure (la liste est dans la datasheet).
Que ce soit dans le code source des drivers et le nommage, l'inspiration STM32 est totale et c'est tant mieux car j'adore les STM32. J'ai lu quelque part que le composant était même totalement compatible broche à broche avec certaines versions de STM32.
La carte que nous allons utiliser est la Longan Nano et coûte 5 Euros, avec en option un écran LCD. Ce qui rend le tout assez sympa.
Sur une des extrémités, vous trouverez un port USB-C et à l'autre bout un connecteur JTAG : merci d'y avoir pensé, c'est assez rare pour le souligner. À côté du JTAG, sur le même connecteur, on y trouve l'UART0 qui va nous servir comme organe de débogage (même si a priori on peut s'en servir pour programmer la carte à la manière d'Arduino).
Point d'entrée pour vos documents :
* https://longan.sipeed.com/en/
* https://dl.sipeed.com/LONGAN/Nano/DOC/
Arsenal de développement
Pour commencer, nous allons utiliser Visual Studio Code avec l'extension PlatformIO : à la manière d'Arduino, il vous simplifie le démarrage rapide sur une nouvelle carte en permettant de coder et programmer votre premier bout de code très facilement, en quelques clics ! Ce genre d'outils est idéal pour essayer une carte, même si je pense qu'il faut s'en éloigner si on veut produire du code industriel qui se vend, notamment pour des problématiques de maintenance.
Quoiqu'il en soit, une connexion USB-C suffit pour envoyer votre code dans le micro. La manipulation est la suivante : maintenez le bouton reset et le bouton boot0 situés sur la carte, puis relachez le reset : le bootloader intégré sera exécuté ce qui créera un dispositif de type DFU sous Linux. Cliquez ensuite sur "upload" et c'est parti !!
Comme éditeur de code : restons sur Visual Studio Code, l'intégration avec Segger est possible. Nous tenterons également QtCreator qui fournit une interface C/C++ exemplaire tant au niveau de l'édition de code que du débogage. Il est réactif !
Au niveau de la librairie fournie par GigaDevice : c'est un sans fautes, pour le moment. Là encore on sent l'inspiration ST, ici vous êtes en terrain connu. La librairie est toute simple, légère, de fines fonctions d'abstraction des registres des périphériques. Messieurs les autres fondeurs, merci de vous en inspirer et arrêtez avec vos interfaces graphiques de génération de code, c'est horrible. Un dossier d'exemples est fourni, bref normalement nous avons tout ce qu'il faut pour commencer facilement.
Point d'entrée pour le tutorial PlatformIO : https://docs.platformio.org/en/latest/boards/index.html#gigadevice-gd32v
Si vous voulez obtenir un compilateur déjà pré-compilé pour vos développements, Nuclei en fournit un ici : https://www.nucleisys.com/download.php.
Le Hello World de l'embarqué : Blinky
Le but est ici de faire clignoter une LED. La carte dispose d'une LED RGB tricolore câblée comme ceci :
Le PlateformIO dispose d'un exemple tout fait permettant de faire clignoter la LED embarquée. Pour réaliser un délai entre l'extinction et l'allumage de la LED, l'exemple utilise une lecture bloquante du timer embarqué dans chaque coeur RISC. Attention donc, c'est utile mais cela bloque tout et dans un contexte multi-tâches on utilisera d'autres moyens non bloquants.
#include "gd32vf103.h"
#include "systick.h"
#include <stdio.h>
/* BUILTIN LED GREEN*/
#define LED_PIN BIT(1)
#define LED_GPIO_PORT GPIOA
#define LED_GPIO_CLK RCU_GPIOA
void longan_led_init()
{
/* enable the led clock */
rcu_periph_clock_enable(LED_GPIO_CLK);
/* configure led GPIO port */
gpio_init(LED_GPIO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, LED_PIN);
GPIO_BC(LED_GPIO_PORT) = LED_PIN;
}
void longan_led_on()
{
GPIO_BC(LED_GPIO_PORT) = LED_PIN;
}
void longan_led_off()
{
GPIO_BOP(LED_GPIO_PORT) = LED_PIN;
}
int main(void)
{
longan_led_init();
init_uart0();
while(1){
/* turn on builtin led */
longan_led_on();
delay_1ms(1000);
/* turn off uiltin led */
longan_led_off();
delay_1ms(1000);
}
}
Comme le montre la documentation, il va falloir créer une règle Linux pour pouvoir programmer le composant :
sudo nano /etc/udev/rules.d/90-longan-nano.rules
ATTRS{idVendor}=="28e9", ATTRS{idProduct}=="0189", MODE="0666"
udevadm control --reload-rules && udevadm trigger
Hello World plus poussé : L'UART
Cette fonction sera autrement plus pratique : lors de vos développements embarqués, avoir une console série qui affiche le bon déroulement de votre programme est indispensable. Cela doit être une des premières choses à prévoir dans votre développement car cela vous servira tout le temps !
Donc là rien de bien compliqué : il faut d'abord repérer quelle broche nous allons utiliser. Sur le longan, le connecteur d'extrémité contient le brochage Tx/Rx typiquement mis là pour cet usage.
Le code d'initialisation est en deux parties :
- D'une part nous allons utiliser le mode alternatif de la broche PA9, c'est-à-dire non pas en GPIO mais en UART
- D'autre part la configuration du module UART proprement dit à la fréquence voulue :
static void init_uart0(void)
{
// enable GPIO clock
rcu_periph_clock_enable(RCU_GPIOA);
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
// enable USART0 clock
rcu_periph_clock_enable(RCU_USART0);
// configure USART0
usart_deinit(USART0);
usart_baudrate_set(USART0, 115200U);
usart_word_length_set(USART0, USART_WL_8BIT);
usart_stop_bit_set(USART0, USART_STB_1BIT);
usart_parity_config(USART0, USART_PM_NONE);
usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
}
// retarget the C library printf function to USART0
int _put_char(int ch) // used by printf
{
usart_data_transmit(USART0, (uint8_t) ch );
while (usart_flag_get(USART0, USART_FLAG_TBE) == RESET){
}
return ch;
}
Notez ici que la fonction _put_char est re-définie : c'est cette fonction qui est au bout d'un printf et se charge d'envoyer un caractère uniquement quelque part : par défaut je pense qu'elle ne fait rien, car nous sommes dans un environnement embarqué. Nous allons envoyer tout caractère reçu vers l'UART.
Et enfin la fonction principale est modifiée en ajoutant notre initialisation et l'appelle du printf();
int main(void)
{
longan_led_init();
init_uart0();
while(1){
printf("ON\n");
/* turn on builtin led */
longan_led_on();
delay_1ms(1000);
/* turn off uiltin led */
printf("OFF\n");
longan_led_off();
delay_1ms(1000);
}
}
Maintenant munissez-vous d'un contrôleur USB-série quelconque pour relier le port série du Longan au PC :
Ouvrez un terminal série sur votre port série créé par la puce FTDI et observez !
Les différentes fréquences et le timer
Ok alors jusqu'à maintenant, nous avons copié-collé un peu de code pris à droite et à gauche. Sauf que nous ne maîtrisons pas grand chose sur la fréquence de fonctionnement. A priori, le délai bloquant fourni par la librairie standard fonctionne vu la fréquence de clignotement de la LED, mais on ne sait pas à quelle fréquence tourne le CPU.
On affiche la fréquence en récupérant la valeur de la variable globale SystemCoreClock qui est initialisée au démarrage, avant le main().
printf("[OST] Starting with CPU=%d\n", (int)SystemCoreClock);
Voici le schéma de la PLL, le CPU est donc bien cadencé à la fréquence maximale ici, soit 108 MHz.
Notons que le timer SysTick offert par le coeur Nuclei est lui divisé par 4 en entrée. Le meilleur moyen de vérifier si la fréquence CPU est correcte est de faire bagoter un GPIO à l'aide d'un timer. Si notre calcul de timer est bon et la fréquence CPU correcte, alors nous devrions voir la bonne fréquene à l'oscilloscope.
NMSIS et le SysTick
Nous l'avons vu, le coeur Nuclei nous offre un timer "bonus" en plus des Timers 0 à 6 que le fondeur GigaDevices propose. Ces derniers sont assez complexes et servent généralement à sortir des PWM, compter des impulsions ou commander des moteurs. Si on peut éviter de s'en servir, profitons-en.
Comment y accéder ? Eh bien, c'est un peu obscure. En fait, la société Nuclei a pondu un ensemble de "standards" d'appellations exactement comme … ARM, avec son CMSIS et Nuclei l'a donc fort logiquement appelé NMSIS.
On y trouve donc dedans ce qui a trait au coeur, dont notre fameux SysTick.
Malheureusement, au moment de l'écriture de cet article, la librairie NMSIS s'intègre très mal à PlatformIO. Il faut donc mieux commencer par l'ensemble cohérent fournit par Nuclei, sur le Github : https://github.com/Nuclei-Software/nuclei-sdk qui contient également la librairie du GD32 adaptée pour l'occasion.
Ce qu'il est possible de faire pour le moment est de copier coller le code se rapportant au SysTick en provenance du NMSIS. Il suffit alors de récupérer l'exemple fournit par Nuclei :
static volatile uint32_t msTicks = 0;
static volatile bool tick_1s = false;
static volatile uint32_t tick_1s_counter = 0;
#define CONFIG_TICKS (TIMER_FREQ / 1000)
#define SysTick_Handler eclic_mtip_handler
void SysTick_Handler(void)
{ /* SysTick interrupt Handler. */
SysTick_Reload(CONFIG_TICKS); /* Call SysTick_Reload to reload timer. */
msTicks++; /* See startup file startup_gd32vf103.S for SysTick vector */
tick_1s_counter++;
if (tick_1s_counter >= 1000)
{
tick_1s_counter = 0;
tick_1s = true;
}
}
Et le main :
int main(void)
{
longan_led_init();
init_uart0();
longan_bp1_init();
uint32_t returnCode = SysTick_Config(CONFIG_TICKS);
while(1)
{
if (tick_1s)
{
tick_1s = false;
printf("[OST] SysTick=%d\r\n", (int)msTicks);
}
}
}
Et voilà, sur l'UART vous devriez observer le tick qui s'incrémente. N'oubliez pas le mot clé 'volatile' pour les variables incrémentées dans l'interruption : sans cela, le compilateur effectuera une optimisation ce qui rendra le code non fonctionnel ; en effet, selon son analyse la fonction SysTick_Handler n'est jamais appelée donc il va la supprimer. En ajoutant le mot clé volatile, on lui dit simplement "t'inquiète, cette variable sert bien quelque part, t'occupe et ne touche à rien".
La table des vecteurs d'interruptions est localisée dans le fichier start.S :
Solutions de débogage
Le coeur RISC-V étant assez nouveau, il existe peu de solutions. Je suggère notamment :
- L'incontournable JLink, attention toutefois à la version matérielle de votre sonde ; un tableau résume quelle version est supportée : https://wiki.segger.com/Software_and_Hardware_Features_Overview
- Sipeed USB-JTAG/TTL RISC-V Debugger, un espèce de clone de ST-Link mais qui a l'air de supporter le RISC-V, je l'ai commandé je testerai
- BlackMagic Probe : l'intégration du RISC-V est en cours, non encore fonctionnelle a priori
- OpenOCD via un moniteur série : je ne l'ai pas testé !
Conclusion
Voilà une carte bien sympatique. Il est maintenant temps de réaliser un petit projet avec, et si possible doté d'une sone JTAG !
Le code source du tutorial est ici : https://github.com/arabine/risc-v-tutorial
# Corrections
Posté par AnthonyRabine (site web personnel) . Évalué à 1.
Bon ça commence bien, le titre n'est pas bon :
"Prise en main de la carte Longan Nano RISC-V de Sipeed"
GigaDevice étant le fondeur de la puce GD32VF103 uniquement, Sipeed a conçu la carte.
[^] # Re: Corrections
Posté par ǝpɐןƃu∀ nǝıɥʇʇɐW-ǝɹɹǝıԀ (site web personnel) . Évalué à 2.
Superbe dépêche. Avec un bel effort de vulgarisation. Même si ça reste un peu trop pointu pour certains. Du coup, ma contribution se bornera à relever trois petites coquilles :
« IRAFURORBREVISESTANIMUMREGEQUINISIPARETIMPERAT » — Odes — Horace
[^] # Re: Corrections
Posté par gUI (Mastodon) . Évalué à 2.
Corrigé, merci.
En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
[^] # Re: Corrections
Posté par gUI (Mastodon) . Évalué à 2. Dernière modification le 23 décembre 2020 à 18:48.
Corrigé, merci.
En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
[^] # Re: Corrections
Posté par Gil Cot ✔ (site web personnel, Mastodon) . Évalué à 2.
Aussi
au lieu de
“It is seldom that liberty of any kind is lost all at once.” ― David Hume
[^] # Re: Corrections
Posté par gUI (Mastodon) . Évalué à 2.
Corrigé, merci.
En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
[^] # Re: Corrections
Posté par kiriarat (site web personnel) . Évalué à 1. Dernière modification le 26 décembre 2020 à 23:30.
mes 2 cents…
au lieu de
(orthographe + apostrophe typographique française)
Ce message ne contient aucun degré. (sérieusement…! non, mais en vrai !! ok, peut-être un peu parfois mais pas là ☻ )
[^] # Re: Corrections
Posté par Benoît Sibaud (site web personnel) . Évalué à 3.
Corrigé pour la typo. Je laisse l'apostrophe et les anglicismes vu qu'il s'agit d'un journal, sauf contre-avis de l'auteur.
# "accès privilégié" et "root"?
Posté par Tom D . Évalué à 3.
Je n'y connais pas grand chose, mais est-ce que quand on parle de "accès privilégié" au niveau de l'ISA et Linux, on ne parle pas plutôt de la différence entre le kernel et les modules d'un côté et les process "utilisateurs" (systemd ou init et tous leurs enfants) de l'autre?
"root" étant privilégié par le système d'exploitation (gestion de fichiers, process, …) mais pas par le CPU lui-même.
[^] # Re: "accès privilégié" et "root"?
Posté par AnthonyRabine (site web personnel) . Évalué à 2.
En fait si, c'est lié ; tu ne peux pas créer une protection logicielle, au sens des processus, sans l'aide du CPU pour interdire physiquement l'accès. Si tout était logiciel, alors cela serait facile à hacker.
[^] # Re: "accès privilégié" et "root"?
Posté par AnthonyRabine (site web personnel) . Évalué à 3.
Bon en fait je viens de comprendre ta remarque, tu as probablement raison j'ai confondu l'aspect utilisateur root des deux espaces "kernel/user".
[^] # Re: "accès privilégié" et "root"?
Posté par Guillaume Knispel . Évalué à 2.
Juste pour pinailler, tu peux en implémentant une VM, cf. l'approche de Singularity.
# DirtyJTAG
Posté par Benjamin Henrion (site web personnel) . Évalué à 2.
"Il est maintenant tant de réaliser un petit projet avec, et si possible doté d'une sone JTAG !"
J'ai la même carte, je vais essayer de porter le code de DirtyJTAG dessus:
https://github.com/jeanthom/DirtyJTAG
# EOL bug
Posté par Thomas Capricelli (site web personnel) . Évalué à 2.
Je note sur ta photo d'écran de cutecom que tu sembles avoir le même problème que moi: les \n ne passent pas. Ils sont bien dans ton code, mais le terminal ne passe pas à la ligne.
De mon coté, je n'y arrive pas, même en tripotant les paramètres des différents terminaux que j'ai essayés (minicom, screen, platformio/python).
Bref, quelqu'un a une idée ? J'ai l'impression qu'ils ne passent même pas "on the wire".
# La vérité sur le SysTick
Posté par martoni (site web personnel, Mastodon) . Évalué à 2.
Merci pour cette belle introduction à la longan nano. Juste pour préciser, avec 5€ (en fait $4.90) on a aussi l'écran (RGB), c'est pas en option !
Et l'usb est dans le microcontrôleur contrairement à beaucoup de kit de ce type, il n'y a pas de «ftdi» à côté pour faire de la conversion usb-uart ou usb-gpio.
L'histoire du SysTick est visiblement une histoire de copier/coller de ARM. En fait le GD32VF103CBT6 de Gigadevice est un copier-coller de leur microcontrôleur GD32E103CBT6 dont ils ont juste changé le cœur pour mettre un processeur de type Risc-V (Bumblebee).
Et ils ont fait pareil pour leurs base de code !
Tout le code fourni initialement en tant que librairie était en fait le code de leur microcontrôleur ARM. À la lecture de l'article il semble que ce code se soit amélioré, mais à l'époque faire fonctionner l'USB (pour en faire un périphérique afficheur de stats pour mon pc) n'était pas une mince affaire.
À l'origine, le SysTick est du pur standard ARM, qui ne peut être présent dans un processeur Risc-V. Comme la doc était du copié collé, c'est resté (du moins au début).
En fait, le standard Risc-V décrit des registres
mstatus
,mtimecmp
, … qui sont accessibles via le CSR (Control Status Register). On ne peut pas lire/écrire ces registres via les instruction classiquesload/store
. Il faut utiliser les instructions spécifiques riscvcsrrx
.Il est donc impossible que le code ARM soit le même que le code RISC-V puisque se sont des instructions assembleur différentes, et il à donc fallu retoucher leur base de code.
J'ai plus qu'une balle
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.