Sylvain Mahé Le site Web Retour à l'accueil Principes Partager mes idées et mes projets librement et gratuitement. Thématiques Mécatronique du système embarqué, artisanat pluridisciplinaire, bricolage commun, esthétique logicielle et matérielle, minimalisme fonctionnel, conceptualisation alternative, rédaction technique et littéraire, partage pédagogique documenté. Contact ✆ Téléphone : 06.45.49.96.98
✉ E-mail : contact@sylvainmahe.site
✎ Site Web : sylvainmahe.site
Écriture de la page : Sylvain Mahé
Une radiocommande avec la classe SpiNrf24l01p La classe SpiNrf24l01p permet de transmettre de manière multidirectionnelle (applications multiceivers, soit plusieurs transceivers) des informations (sur 32 bits quelles qu'elles soient) sur la bande fréquence des 2.4GHz entre plusieurs périphériques (deux ou plus). Le composant utilisé est le nRF24L01+, ce matériel est un transceiver pour émetteur/récepteur, la classe que je propose ici pilote ce composant et permet des applications multiceivers (réseau d'émetteurs/récepteurs), autorisant les projets les plus ambitieux notamment en robotique. Le composant nRF24L01+ communique en SPI avec le microcontrôleur. Ports des automates programmables concernés par le SPI : Automate programmable MODULABLE 20 :
- Port GPIO 11 (PB2) = SS (slave select)
- Port GPIO 12 (PB3) = MOSI (master output slave input)
- Port GPIO 13 (PB4) = MISO (master input slave output)
- Port GPIO 14 (PB5) = SCK (serial clock)

Automate programmable MODULABLE 32 :
- Port GPIO 5 (PB4) = SS (slave select)
- Port GPIO 6 (PB5) = MOSI (master output slave input)
- Port GPIO 7 (PB6) = MISO (master input slave output)
- Port GPIO 8 (PB7) = SCK (serial clock)
Dans les exemples qui suivent, il s'agit de montrer le plus simplement possible (pour la compréhension du lecteur) un émetteur et un récepteur distinct (sans créer de confusion et donc sans évoquer le coté multiceiver) : Mais sachez que la programmation de multiples émetteurs/récepteurs s'effectue exactement de la même façon, avec les fonctions employées dans les exemples (transmit et receive), de manière complètement transparente pour le programmeur via l'utilisation de la classe SpiNrf24l01p. Ci-dessous, un premier périphérique nommé "émetteur" envoi des données à un deuxième montage appelé "récepteur". Exemple d'émetteur : #include <SpiNrf24l01p.h> int main() { SpiNrf24l01p myChannel = SpiNrf24l01p (1); unsigned char myIteration = 0; SpiNrf24l01p::start (5, 32, 1524003746, true); while (true) { myChannel.transmit (myIteration); if (myIteration < 255) { myIteration++; } else { myIteration = 0; } } return 0; } Dans cet exemple, un objet myChannel de type SpiNrf24l01p est déclaré, en paramètre est indiqué le canal 1 sur lequel communiquer les données (il y a 64 canaux en tout). À la ligne suivante une variable myIteration de type unsigned char (8 bits non signés) est déclarée avec une valeur de 0, elle va servir à incrémenter un nombre entier. Puis, le composant nRF24L01+ est démarré en appelant la fonction statique start prenant plusieurs paramètres :
- Le 1er paramètre 5 est le numéro du port GPIO de l'automate programmable sur lequel est connectée la broche SS (slave select) du composant nRF24L01+.
Ce paramètre est utile lorsque vous souhaitez connecter en SPI différents composants en série ou en parallèle avec le microcontrôleur. - Le 2ème paramètre 32 est le numéro du port GPIO de l'automate programmable relié en interne au convertisseur analogique/numérique (l'ADC) du microcontrôleur. Ce port doit être physiquement laissé libre (en l'air) car il sert au système anti-collisions qui lorsqu'un port approprié est sélectionné, utilise du bruit analogique pour générer de l'aléatoire. Si vous ne souhaitez pas utiliser de bruit analogique pour le système anti-collisions, indiquez 0 en paramètre, dans ce cas seul le générateur de bruit artificiel (pseudo aléatoire) sera utilisé. Le système anti-collisions de la classe SpiNrf24l01p permet d'éviter que plusieurs (deux ou plus) nRF24L01+ ne se transmettent des données exactement au même moment. Afin de conserver cette qualité opérationnelle, il est vivement conseillé dans le cadre d'une communication bidirectionnelle ou multidirectionnelle de sélectionner un port approprié (voir la liste ci-dessous). Ports des automates programmables concernés par l'ADC : Automate programmable MODULABLE 20 :
- Port GPIO 15 (PC0) = ADC0 (analog to digital converter 0)
- Port GPIO 16 (PC1) = ADC1 (analog to digital converter 1)
- Port GPIO 17 (PC2) = ADC2 (analog to digital converter 2)
- Port GPIO 18 (PC3) = ADC3 (analog to digital converter 3)
- Port GPIO 19 (PC4) = ADC4 (analog to digital converter 4)
- Port GPIO 20 (PC5) = ADC5 (analog to digital converter 5)

Automate programmable MODULABLE 32 :
- Port GPIO 25 (PA7) = ADC7 (analog to digital converter 7)
- Port GPIO 26 (PA6) = ADC6 (analog to digital converter 6)
- Port GPIO 27 (PA5) = ADC5 (analog to digital converter 5)
- Port GPIO 28 (PA4) = ADC4 (analog to digital converter 4)
- Port GPIO 29 (PA3) = ADC3 (analog to digital converter 3)
- Port GPIO 30 (PA2) = ADC2 (analog to digital converter 2)
- Port GPIO 31 (PA1) = ADC1 (analog to digital converter 1)
- Port GPIO 32 (PA0) = ADC0 (analog to digital converter 0)
- Le 3ème paramètre 1524003746 correspond au code de radio-identification (ou RFID unique par définition) qui permet de sécuriser la communication entre plusieurs (deux ou plus) nRF24L01+. C'est à vous de choisir ce code RFID, plus le nombre est complexe et plus la communication sera sécurisée et protégée contre les parasites, mais il faut reconnaître que n'importe quel nombre sur 32 bits différent de 0 avec seulement quelques chiffres, est largement suffisant. - Le 4ème paramètre true indique d'émettre à la puissance maximale (0dBm). Une valeur sur false serait la puissance minimale (-18dBm), ce qui peut être suffisant en communication courte distance (par exemple en intérieur). Puis dans la boucle while, la fonction transmit de l'objet myChannel est utilisée pour transmettre une information 32 bits à un autre nRF24L01+. Dans ce cas c'est la valeur de la variable myIteration qui est transmise, variable dont la valeur est incrémentée aux lignes suivantes. Ce petit bout de programme est simple, mais il permet déjà d'assurer une communication 2.4GHz efficace et sécurisée entre deux nRF24L01+. Envoyer la valeur d'une variable qui s'incrémente au cours du temps peut servir à programmer simplement un système à tolérance de pannes (fail-safe) sur la partie récepteur du montage. En effet, si la valeur reçue ne s'incrémente plus pendant un certain temps (1 seconde par exemple), il peut être alors intéressant de déclencher une mise au neutre des servo-moteurs, une coupure de la motorisation d'un aéronef, une procédure de vol automatisée par gyroscope, etc... Exemple de récepteur : #include <SpiNrf24l01p.h> int main() { SpiNrf24l01p myChannel = SpiNrf24l01p (1); unsigned char myIteration = 0; SpiNrf24l01p::start (5, 32, 1524003746, true); while (true) { myChannel.receive(); //myChannel.data sont les données reçues sur ce canal : myIteration = myChannel.data; } return 0; } Dans cet exemple le seul changement notable est l'appel de la fonction receive de l'objet myChannel afin de recevoir les données émises, en l'occurrence ici la valeur de la variable incrémentée dans le montage coté émetteur. La classe SpiNrf24l01p dispose d'une fonction reset ce qui permet de remettre à l'état false la variable received. Cette variable est utile pour savoir si une ou plusieurs données ont été reçues avec succès. La variable data (qui correspond aux données reçues) est également réinitialisée à 0.

Une fonction statique stop existe également et permet d'éteindre le circuit d'émission/réception du composant nRF24L01+, un nouvel appel à la fonction start permet de redémarrer le circuit.
Exemple d'un système radiocommandé 5 voies : Dans l'exemple suivant, 4 potentiomètres (2 par manche) sont connectés aux ports GPIO 25, 26, 27, et 28 de l'automate programmable. Ce sont des entrées/sorties connectées au convertisseur analogique/numérique (l'ADC) du microcontrôleur. Un interrupteur est également connecté au port GPIO 1 de l'automate programmable, ce qui va servir d'interrupteur de coupure moteur dans ce cas précis. Les 4 potentiomètres et l'interrupteur servent d'interface utilisateur entre l'homme et la machine. 5 objets de type SpiNrf24l01p (correspondants aux 5 voies) servent à transmettre les valeurs brutes des 4 potentiomètres et de l'interrupteur (respectivement des valeurs allants de 0 à 1023 pour les potentiomètres et 0 ou 1 pour l'interrupteur). L'émetteur : #include <AnalogRead.h> #include <GpioRead.h> #include <SpiNrf24l01p.h> int main() { AnalogRead myStickThrottle = AnalogRead (25); AnalogRead myStickPitch = AnalogRead (26); AnalogRead myStickRoll = AnalogRead (27); AnalogRead myStickYaw = AnalogRead (28); GpioRead myButtonCut = GpioRead (1, true, 10); SpiNrf24l01p myChannelThrottle = SpiNrf24l01p (1); SpiNrf24l01p myChannelPitch = SpiNrf24l01p (2); SpiNrf24l01p myChannelRoll = SpiNrf24l01p (3); SpiNrf24l01p myChannelYaw = SpiNrf24l01p (4); SpiNrf24l01p myChannelCut = SpiNrf24l01p (5); SpiNrf24l01p::start (5, 32, 1524003746, true); while (true) { myStickThrottle.read(); myStickPitch.read(); myStickRoll.read(); myStickYaw.read(); myButtonCut.read(); myChannelThrottle.transmit (myStickThrottle.value); myChannelPitch.transmit (myStickPitch.value); myChannelRoll.transmit (myStickRoll.value); myChannelYaw.transmit (myStickYaw.value); myChannelCut.transmit (myButtonCut.bistable); } return 0; } L'exemple suivant est la partie récepteur du montage. 5 objets de type PwmWrite sont utilisés pour générer des signaux PWM ce qui permet de faire fonctionner 4 servo-moteurs :
- Le 1er pour les gaz (throttle).
- Le 2ème pour l'axe de tangage (pitch).
- Le 3ème pour l'axe de roulis (roll).
- Le 4ème pour l'axe de lacet (yaw).
Une fois reçues avec les fonctions receive, les valeurs brutes sont transformées à l'aide de la classe Math (qui permet de créer entre autres des courbes) en valeurs adaptées PWM (modulation de la largeur d'impulsion en microsecondes) afin de les envoyer aux servo-moteurs par les ports GPIO 1, 2, 3, et 4 de l'automate programmable. Le récepteur : #include <SpiNrf24l01p.h> #include <PwmWrite.h> #include <Math.h> int main() { SpiNrf24l01p myChannelThrottle = SpiNrf24l01p (1); SpiNrf24l01p myChannelPitch = SpiNrf24l01p (2); SpiNrf24l01p myChannelRoll = SpiNrf24l01p (3); SpiNrf24l01p myChannelYaw = SpiNrf24l01p (4); SpiNrf24l01p myChannelCut = SpiNrf24l01p (5); PwmWrite myServoThrottle = PwmWrite (1); PwmWrite myServoPitch = PwmWrite (2); PwmWrite myServoRoll = PwmWrite (3); PwmWrite myServoYaw = PwmWrite (4); SpiNrf24l01p::start (5, 32, 1524003746, true); PwmWrite::start (50); while (true) { myChannelThrottle.receive(); myChannelPitch.receive(); myChannelRoll.receive(); myChannelYaw.receive(); myChannelCut.receive(); if (myChannelCut.data == true) { myServoThrottle.us (1000); } else { myServoThrottle.us (Math::curve (0, myChannelThrottle.data, 1023, 1000, 2000, 0)); myServoPitch.us (Math::curve (0, myChannelPitch.data, 1023, 1000, 2000, 0)); myServoRoll.us (Math::curve (0, myChannelRoll.data, 1023, 1000, 2000, 0)); myServoYaw.us (Math::curve (0, myChannelYaw.data, 1023, 1000, 2000, 0)); } } return 0; } La 5ème voie (cut) permet la coupure des gaz quel que soit la position du manche de gaz, c'est une des sécurités fondamentales notamment en aéromodélisme. Libre à vous d'explorer les possibilités de MODULE afin d'utiliser et de modifier ces exemples pour vos applications ! En l'occurrence je pense au retour air/sol de la pression atmosphérique et de la température à l'aide du capteur environnemental BME280 et de la classe TwiBme280, de l'utilisation des centrales inertielles MPU6050 et BNO055 à l'aide des classes TwiMpu6050 et TwiBno055, de l'ajout de trims, d'un écran de contrôle, d'une alarme batterie faible, d'une temporisation, de réglages divers (courbes, double débattements), etc... Références : Récapitulatif des fonctions et variables de cette classe : signed long data = 0; bool received = false; SpiNrf24l01p (const unsigned char ADDRESS); static void start (const unsigned char PIN_SS, const unsigned char PIN_ANALOG, const unsigned long RFID, const bool POWER); void receive(); void transmit (const signed long DATA); void reset(); static void stop();