Processeur numérique d'effets guitare programmé en Rust

De Wiki LOGre
Aller à la navigation Aller à la recherche


Projet en cour, réalisé par Yruama_Lairba

Présentation

Le but de ce projet et de réaliser un processeur numérique d'effets guitare à partir d'un microcontrôleur programmé en Rust. La version actuelle est composée d'une carte Nucleo-F411 (stm32) et d'un shield que j'ai conçu spécifiquement, intégrant préampli + ADC + DAC.

Sources

Sources du projets

Sources en lien avec le projet

Il s'agit de Bibliothèques Rust que j'ai créé ou auxquels j'ai participé et dont ce projet dépend. Normalement, ces Bibliothèques sont automatiquement téléchargé lors de la première compilation du projet.

  • stm32_i2s, bibliothèque d'abstraction pour contrôler le bus i2s de plusieurs famille de stm32. J'y ai apporté les contribution suivante:
    • v0.3: re conception de la bibliothèque, la v0.2 était inutilisable.
    • v0.4: hack pour correctement réinitialiser le périphérique, correction et support du mode "PCM". Heureusement que j'ai trouvé des explications ailleurs que chez ST pour le "PCM".
    • v0.5: ajout d'une abstraction pour supporter le mode full duplex.
  • stm32f4xx, bibliothèque d'abstraction hardware pour les puce stm32f4:
    • intégration de stm32_i2s
    • modification de l'abstraction gérant les dma Stream: ajout de fonctionnalités manquantes, correction du "coding style", parfois juste incohérent, parfois non-conforme à certaines lignes de conduites fondamentale.
  • wm8731-another-hal, bibliothèque d’abstraction alternative pour contrôler un codec wm8731
    • créé depuis zéro. Un projet existait déjà, mais la bibliothèque n’était pas fonctionnelle et le projet semblais mort.

Historique

Origine du projet

Cela commence au mois de septembre 2020, où je vois Daan suivre un tutorial pour s'initier au Rust avec une carte stm32f3discovery. J'ai eu envie de faire pareil, et j'ai emprunté la carte stm32f3discovery du log pour m'y mettre à mon tour. Et comme j'aime bien bidouiller autour des effets audios, j'ai l'idée d'utiliser ma carte nucleo F411 pour faire un multi-effets guitare.

Premières réflexions

Pour démarrer le projet, j'ai essayé de trouver des projets similaire. Le plus proche que j'ai trouvé est un projet sur instructable utilisant une carte nucleo F303. Ce projet utilise l'ADC et le DAC embarqué dans le microcontrolleur, je n'ai pas suivie cette voie pour 2 raison principale: ma carte Nucleo F411 n'as pas de DAC, et l'ADC embarqué ne que de 12 bits, ce qui m'a semblé faible par rapport au 16 bits généralement utilisée en audio grand publique (et 24 bits en audio professionnel). En parallèle, j'ai découvert qu'il y avais dans les stm32 des bus "I2S" dédié à la communication avec des ADC et DAC audio, je suis donc partie sur cette piste.


Décembre 2020 - Février 2021 : Premiers essais

Au niveau matériel:

  • module chinois PCM1808: ADC stéréo 24 bits 96khz
  • module chinois PCM5202: DAC stéreo 32 bits 384khz
  • AOP MCP602: pour faire le préampli/buffer du signal guitare
  • module chinois TPA6132: ampli casque:
  • potentiomètre double: pour le contrôle du volume en dosant la quantité de signal entre le DAC et l'ampli casque

résultats:

  • Premier montage avec breadboards et plein de connecteur dupont: fonctionne, mais faux contact, parasite, et des connections se défont à chaque transport
  • tentative de montage sur stripboard pour fiabiliser l'assemblage: le montage est dégueulasse et ne fonctionne pas, abandon.
  • Finalement, j'ai réussis à assembler module et breadboard de sorte que ca ne fasse qu'un bloc afin de réduire les connexions foireuses

=> Ca serais bien d'avoir un shield pour avoir quelque chose de fiable.

Au niveau du code: À ce moment la, il n'y avais pas la gestion de l'I2S dans la bibliothèque stm32f4xx. L’objectif principal étant de vérifier le fonctionnement du hardware, toute la partie I2S et DMA à été piloté en utilisant le "Package Access Crate", le code obtenue:

  • est difficile à maintenir et à faire évoluer.
  • contient beaucoup de bloc "unsafe"
  • ne tiens pas compte de certaine règle à respecter. En particulier, l'utilisation du contrôleur DMA requiert des précautions particulières pour éviter de provoquer un "Undefined Behavior"

=> il faudra probablement écrire des abstractions pour rendre ça plus propre.

  • Module PCM1808
  • Module PCM5102
  • Module TPA6132

Avril 2021 - Juillet 2021 : Conception et fabrication d'un shield

La conception du shield s'est faite avec Kicad 5 en ciblant JLCPCB pour la fabrication du circuit imprimé et la soudure de composant CMS. C'est ma première expérience réel de conception de carte. Durant cette période:

  • J'ai découvert l’existence des "codec" audio, il s'agit de composant avec ADC + DAC intégrant des fonctionnalités supplémentaires selon les modèles, par exemple:
    • contrôle numérique du volume,
    • sortie supplémentaire avec ampli casque,
    • sortie supplémentaire avec ampli haut parleur,
    • entrée avec préampli micro,
    • génération d'horloge pour le bus i2s...
  • J'ai découvert que j'avais mal compris le concept de "rail to rail" pour les AOP, cela peu s’appliquer à l'entrée et/ou à la sortie. Du coup j'ai sélectionné un AOP MCP6002, rail to rail entrée et sortie pour maximiser la dynamique de mon signal.
  • J'ai été confronté à des problèmes d'approvisionnement sur les codec audio, j'ai du recommencer plusieurs fois la carte, parce que le codec initialement retenu était passé en rupture de stock. J'ai finalement décidé d'acheter des codecs wm8731 sur aliexpress et de le souder moi même.
  • J'ai essayé de souder le codec moi même, gros echec, une carte de bousillé à cause d'une pastille arraché. Oliv' m'a dépanné en soudant lui même les codec restant avec un fer à soudé de bien meilleur qualité. J'en ai profité pour souder les composants traversant de certaines cartes.

=> J'ai réussi à faire fonctionner une carte, mais il n'y a pas d’abstraction fonctionnel pour le codec wm8731, le code de test envoie juste des bits en brut par SPI pour configurer le codec.

Juillet 2021 - Mars 2022 : Abstraction pour le wm8731

À ce moment là il y avais bien un package sur crates.io pour contrôler le wm8731, mais celui-ci n’était pas fonctionnel et le projet semblais inactif. J'ai donc développé et publié mon propre package : "wm8731-another-hal". Il a fallu pas mal de réflexion pour présenter une API qui empêche les configurations invalide tout en ayant du sens pour l'utilisateur.

Mars 2022 - Mai 2023 : Abstraction pour l'I2S

Entre temps je me suis rendu compte qu'une abstraction "stm32_i2s" pour l'I2S avais été intégré dans le package stm32f4xx. Malheureusement celle-ci était mal conçu, il était quasiment impossible d'utiliser correctement le périphérique I2S avec. Il a fallu tout re-concevoir, ce qui à donné lieu à 3 release supplémentaire:

  • v0.3, ma réécriture initiale. Je découvre au passage des problèmes de fonctionnement limitant l'utilisation.
  • v0.4, contournement permettant de réinitialiser correctement le bus i2s pour lever les limitation précédente. Correction du mode PCM grâce à une doc NXP, les explications de ST sont foireuse à ce sujet.
  • v0.5, ajout d'une abstraction pour supporter le mode full-duplex.

Mai 2023 - Juillet 2023 : Correction DMA Stream

La bibliothèque stm32f4xx contient des abstractions pour le contrôleur DMA:

  • "Stream": abstraction proche du matériel, représente un "stream" du DMA, facile à comprendre mais requiert des blocs "unsafe" pour être utilisé.
  • "Transfer": Je ne comprend pas comment c'est censé être utilisé, il n'y a pas d'exemple, je comprend juste que ça n'est pas adapté à ce que je souhaite faire.

Du j'essaye d'utiliser les "Stream", mais je me rend compte que ça n'a pas été bien codé et qu'il y des choses à corriger:

  • il manque des fonctionnalités et c'est précisément celles dont j'ai besoin
  • non-respect d'un pattern communément utilisé en rust embarqué: un périphérique doit être manipulé au travers d'une variable le représentant.
  • mauvais coding style: incohérence, nommage confus, fonction "unsafe" pouvant facilement être réécrite pour être "safe"...

=> et bien sur, il a fallu aussi corriger dans la bibliothèque tout le code qui dépendait des Stream.

Septembre 2023 - octobre 2023 : Buffer pour le contrôleur DMA ?

Dans mon utilisation, je souhaite avoir un buffer qui soit accédé simultanément: - En écriture par le Stream DMA qui lit les données depuis l'ADC et les ecris dans le buffer DMA - En lecture par le Stream DMA qui lit les données du buffer DMA et les écris vers le DAC - En lecture/écriture par le programme pour appliquer un traitement Ce genre de situation requiert une attention particulière, En rust, c'est généralement résolu au travers de wrapper "safe" qui gère les détails.

  • Premiere approche : créer un wrapper en gérant soit même les problèmes d'accès concurrent et d'optimisation et de synchro mémoire. Abandonnées, cela touche de trop près à des comportement du compilateur pour ne pas faire une connerie.
  • Deuxième approche : conceptuellement, on a quelque chose d'équivalent à avoir plusieurs threads et des données partagé entre eux en lecture/écriture. En Rust, cette situation est géré par des types particulier:
    • Système par verrous (Mutex,Rwlock): chaque thread doit acquérir un verrou avant de pouvoir utiliser la donnée. Inapplicable au Stream DMA.
    • Types "Atomic": types dont chaque méthode s’exécute de manière atomique sans nécessité de verrou, c'est à dire qu'on a pas besoin de recourir à un signal pour avoir la garanti que l’opération s’exécute sans interruption. C'est d'ailleurs sur eux que repose les systèmes pas verrou. Avec les Stm32F4 (cortex M4), les types "Atomic" sont limité par des opérations "load" et "store" et sont de 8, 16 ou 32 bits, mais c'est tout ce dont on a besoin pour le buffer DMA:
      • le "load" permet de lire une donné dans le buffer
      • le "store" permet d'ecrire une donné dans le buffer
      • Un stream DMA ne peu copier que des mots de 8,16 ou 32 bits

=> un buffer DMA sera représenté par des tableaux (array) contenant un type atomic.