Outils pour utilisateurs

Outils du site


diy:projets:tl_partieemetteur

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
diy:projets:tl_partieemetteur [2018/05/25 08:21] – [Interface avec le gpio] sduranddiy:projets:tl_partieemetteur [2018/07/02 09:34] (Version actuelle) – [Le programme principal et la fonction qui gère la led] sdurand
Ligne 40: Ligne 40:
 echo V > /sys/class/gpio/gpioX/value echo V > /sys/class/gpio/gpioX/value
 </code> </code>
-avec **V** qui peut valoir **1 pour éteindre la led** ou **0 pour allumer la led** (cela vous semble peut  +avec **V** qui peut valoir **1 pour éteindre la led** ou **0 pour allumer la led** (**ceci dépend de la façon  
-être contre intuitifmais c'est comme ça) et **X** qui est toujours le **numéro du pin de la led**.+dont vous avez branché la led au raspberry**notre branchement est sans doute hasardeux) et **X** qui  
 +est toujours le **numéro du pin de la led**.
  
-Et ça va se passer de la même manière en C++ ! Vous pouvez très bien **ouvrir ces fichiers dans votre code** pour y +Quand vous aurez fini de jouer, vous devrez dé-exportez la led. Là aussi, c'est très simple: 
 +<code> 
 +echo numero_du_pin_de_la_led > /sys/class/gpio/unexport 
 +</code> 
 +Et voilà, le pin de la led est de nouveau "inactif"
 + 
 +Tout va se passer de la même manière en C++ ! En effet, vous pouvez très bien **ouvrir ces fichiers dans votre code** pour y 
 **effectuer toutes les opérations** dont je viens de vous parler, c'est comme ça que je vous recommande de procéder.  **effectuer toutes les opérations** dont je viens de vous parler, c'est comme ça que je vous recommande de procéder. 
 J'ai personnellement choisi de **créer une classe** pour encapsuler ces opérations. Le code de cette classe est  J'ai personnellement choisi de **créer une classe** pour encapsuler ces opérations. Le code de cette classe est 
-disponible sur [[https://github.com/SDurand7/LedTransmitter|mon github]] (fichiers ledcontroller.cpp/hpp) au cas où vous ne souhaiteriez pas +disponible ci-dessous au cas où vous ne souhaiteriez pas 
 tout réécrire vous même.  tout réécrire vous même. 
  
Ligne 66: Ligne 73:
 Si vous êtes arrivés jusqu'ici, vous êtes prêt à attaquer **la partie amusante** ! Si vous êtes arrivés jusqu'ici, vous êtes prêt à attaquer **la partie amusante** !
 ==== Les changements d'états de la led ==== ==== Les changements d'états de la led ====
 +
 +Dans cette partie, nous allons écrire un **main** qui va utiliser notre **API** pour allumer/éteindre la led 
 +en fonction de la valeur de **chaque bit** de **chaque caractère** d'un message. Nous avons décidé d'utiliser des 
 +**chaînes de caractères comme données à transmettre**, simplement parce qu'il nous sera plus facile (selon nous) de 
 +visualiser les **erreurs de bits** sur des caractères que sur les valeurs des pixels d'une image ou tout autre 
 +type de données qu'on aurait pu transmettre. 
 +
 +En concevant le code que je vais vous présenter, j'ai prévu **deux chaînes de caractères** particulières 
 +qui **agirait comme des commandes**. Ces deux commandes sont //**/end**// pour terminer le programme 
 +proprement et **libérer les ressources** //(dé-exporter le pin de la led)//, et //**/test**// qui envoie 
 +une séquence particulière de 0 et de 1 (qui sont en fait des **"U"**), parce que cette alternance 
 +cyclique de 0 et de 1 va nous permettre de visualiser d'éventuelles **erreurs de synchronisation**.
 +
 +Voyons à quoi ressemble **la boucle de notre main**:
 +<code C++>
 +int main() {
 +   // GPIO_PIN est définie via un define par la valeur du pin de la led
 +   LedController lc(GPIO_PIN);
 +   std::string message;
 +
 +   std::cout << "Waiting for a string to send: (\"/test\" to launch a sequence of 0 and 1, \"/end\" to end the program)" << std::endl;
 +   // On récupère le premier message à envoyer, on n'utilise pas l'opérateur **>>** de std::cin pour conserver les espaces
 +   std::getline(std::cin, message);
 +
 +   // Tant qu'on a pas saisi le message d'arrêt ("/end")
 +   while(message != "/end") {
 +      std::cout << "Waiting for a string to send: " << std::endl;
 +      
 +      // On vérifie si on a reçu la séquence de test, si c'est le cas, on envoie un certains nombre de U
 +      if(message == "/test") {
 +         // Attention, suivant la fréquence que vous sélectionnerai pour les bits, cette séquence peut être très longue
 +         transmit(lc, "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU");
 +      }
 +      else {
 +         // Si on a un message lambda, on le trasnmet
 +         transmit(lc, message);
 +      }
 +      std::cout << "Transfer complete.\n" << std::endl;
 +   }
 +
 +   return ALL_GOOD;
 +}
 +</code>
 +
 +Pour vous pouvez le voir, rien de bien compliqué là aussi. On récupère simplement des messages et on 
 +les traite en conséquence. Notez que si vous souhaitez ajouter **plus de commandes**, vous devriez 
 +utiliser un //**switch**// pour plus de clarté.
 +
 +Passons maintenant au coeur du programme, la **conversion de bits en signaux lumineux**. Nous allons commencer 
 +par créer la boucle nous permettant d'itérer sur les caractères d'un string. En **C++** et avec 
 +**la norme C++14**, vous pouvez faire ça comme suit:
 +<code C++>
 +for(const char& c: message) {
 +   // ...
 +}
 +</code>
 +
 +Nous devons ensuite itérer //"sur les bits"// des caractères. Pour cela, nous allons réaliser un **ET** 
 +bit à bit (via l'opérateur logique "**&**" en C++) avec tout les octets possibles où un seul bit serait 
 +à un (ex: 10000000, 01000000, 001 ...). J'ai donc déclaré un tableau constant en variable globale 
 +qui contiendra tout ces octets.
 +<code C++>
 +const unsigned char powers_of_two[8] = {128, 64, 32, 16, 8, 4, 2, 1};
 +</code>
 +
 +Nous pouvons maintenant tester pour tout les caractères (en utilisant une boucle) de cette façon:
 +<code C++>
 +const bool lightUp = c & powers_of_two[i];
 +</code> 
 +
 +Au final, **votre code devrait ressembler a quelque chose comme ça**:
 +<code C++>
 +void transmit(LedController& lc, const std::string& message) {
 +   // On initialise la variable timer comme le temps référence de notre transmission 
 +   auto timer = std::chrono::high_resolution_clock::now();
 +   // On allume la led pour signaler au récepteur le début de la transmission
 +   lc.turnOn();
 +
 +   // On parcourt le message
 +   for(const char& c: message) {
 +      // On parcourt les bits de chaque caractère
 +      for(unsigned int i = 0; i < 8; i++) {
 +         // On incrémente le timer d'un pas pré-calculé (dépendant le la fréquence de transmission choisie)
 +         timer += time_step;
 +         // On regarde avant la pause si on doit allumer ou non la led
 +         const bool lightUp = c & powers_of_two[i]; 
 +         // On attend
 +         std::this_thread::sleep_until(timer);
 +
 +         // On positionne la led dans le bon état
 +         lightUp ? lc.turnOn() : lc.turnOff();
 +      }
 +   }
 +
 +   std::this_thread::sleep_until(timer + time_step);
 +   lc.turnOff();
 +
 +   // On attend suffisamment pour que le récepteur reçoive un octet de zéros pour marquer la fin de transmission
 +   std::this_thread::sleep_for(2 * time_step);
 +}
 +</code>
 +
 +En ce qui concerne **les pauses**, je vous invite simplement à consulter le code ci-dessus, 
 +vous avez sans doute remarquer que le "schéma" est simple et ne nécessite pas d'explications 
 +détaillées.
  
 ---- ----
  
 ===== Sources du projet ===== ===== Sources du projet =====
 +
 +==== La classe pour contrôler le gpio ====
 +
 +<code C++>
 +#include "ledcontroller.hpp"
 +
 +
 +// Private method that export the choosen gpio
 +void LedController::export_gpio() {
 + std::ofstream exportFile("/sys/class/gpio/export");
 +
 + if(!exportFile.is_open()) {
 + std::cerr << "ERROR: Could not export gpio " << this->numero << "." << std::endl;
 + throw ERR_EXPORT;
 + }
 +
 + exportFile << this->numero;
 + exportFile.close();
 +}
 +
 +
 +// Private method that unexport the previously opened gpio
 +void LedController::unexport_gpio() {
 + std::ofstream unexportFile("/sys/class/gpio/unexport");
 +
 + if(!unexportFile.is_open()) {
 + std::cerr << "ERROR: Could not unexport gpio " << this->numero << "." << std::endl;
 + throw ERR_UNEXPORT;
 + }
 +
 + unexportFile << this->numero;
 + unexportFile.close();
 +}
 +
 +
 +// Constructor of our class:
 +// numero: numero of the gpio's pin we want to open
 +// dirOut: boolean indicating if the direction we want is out or not (as it is a LedController, it is set to true by default)
 +// litUp: boolean indicating if the led should be initially on or off
 +LedController::LedController(int numero, bool dirOut, bool litUp) {
 + this->numero = numero;
 +
 + export_gpio();
 + // Adding a little delay to make sure that the files created when exporting exists before we start working on them
 + std::this_thread::sleep_for(std::chrono::milliseconds(EXPORT_DELAY));
 +
 + std::ofstream directionFile(std::string("/sys/class/gpio/gpio" + std::to_string(numero) + "/direction").c_str());
 +
 + if(!directionFile.is_open()) {
 + unexport_gpio();
 +
 + std::cerr << "ERROR: Could not open \"direction\" for gpio " << numero << "." << std::endl;
 + throw ERR_EXPORT;
 + }
 +
 + // Setting the direction according to the boolean dirOut
 + directionFile << (dirOut ? "out" : "in"); 
 + directionFile.close();
 +
 + if(!litUp) {
 + turnOff();
 + }
 + else {
 + turnOn();
 + }
 +}
 +
 +
 +// Destructor that will unexport the gpio previously opened
 +LedController::~LedController() {
 + unexport_gpio();
 +}
 +
 +
 +// Public method lighting up the led
 +void LedController::turnOn() {
 + std::ofstream value;
 +
 + value.open(std::string("/sys/class/gpio/gpio" + std::to_string(this->numero) + "/value").c_str());
 +
 + if(!value.is_open()) {
 + unexport_gpio();
 +
 + std::cerr << "ERROR: Could not open \"value\" for gpio " << this->numero << "." << std::endl;
 + throw ERR_VALUE;
 + }
 +
 + value << "0";
 +
 + value.close();
 +}
 +
 +
 +// Public method turning off the led
 +void LedController::turnOff() {
 + std::ofstream value; 
 +
 + value.open(std::string("/sys/class/gpio/gpio" + std::to_string(this->numero) + "/value").c_str());
 +
 + if(!value.is_open()) {
 + unexport_gpio();
 +
 + std::cerr << "ERROR: Could not open \"value\" for gpio " << this->numero << "." << std::endl;
 + throw ERR_VALUE;
 + }
 +
 + value << "1";
 +
 + value.close();
 +}
 +</code>
 +
 +==== Son header ====
 +
 +<code C++>
 +#ifndef LEDCONTROLLER_HPP
 +#define LEDCONTROLLER_HPP
 +
 +#include <iostream>
 +#include <string>
 +#include <chrono>
 +#include <thread>
 +#include <fstream>
 +
 +// Refer to the constructor source code for more info on this define
 +#define EXPORT_DELAY 400
 +
 +// A few define to make errors handling clearer
 +#define ERR_EXPORT -2
 +#define ERR_UNEXPORT -3
 +#define ERR_VALUE -4
 +#define ERR_DIRECTION -5
 +
 +
 +
 +class LedController {
 + public:
 + LedController(int numero, bool dirOut = true, bool litUp = false);
 + ~LedController();
 +
 + void turnOn();
 + void turnOff();
 +
 + private:
 + void export_gpio();
 + void unexport_gpio();
 + int numero;
 +};
 +
 +#endif
 +</code>
 +
 +==== Le programme principal et la fonction qui gère la led ====
 +
 +<code C++>
 +#include <iostream>
 +#include <string>
 +#include <chrono>
 +#include <thread>
 +#include "ledcontroller.hpp"
 +
 +#define GPIO_PIN 21
 +#define BITRATE 20
 +#define BITTIME static_cast<int>(1e6/BITRATE)
 +
 +#define ALL_GOOD 0
 +
 +
 +const auto time_step = std::chrono::microseconds(BITTIME);
 +// We are asuming here that a char is always an octet, and we will assume in every part of the code
 +const unsigned char powers_of_two[8] = {128, 64, 32, 16, 8, 4, 2, 1};
 +
 +
 +// Function that iterates over message and switch on and off the led of lc according to the bits
 +void transmit(LedController& lc, const std::string& message) {
 + // Initializing timer as the reference time
 + auto timer = std::chrono::high_resolution_clock::now();
 + // Turning on the light to tell the receptor we're starting to emit
 + lc.turnOn();
 +
 + // Iterating over the message
 + for(const char& c: message) {
 + // Iterating over the bits of c (again, we're assuming that a char is an octet long)
 + for(unsigned int i = 0; i < 8; i++) {
 + // Incrementing the timer, computing wether we should lightUp the led or not, and sleeping until it's time to change the state of the led
 + timer += time_step;
 + const bool lightUp = c & powers_of_two[i]; 
 + std::this_thread::sleep_until(timer);
 +
 + lightUp ? lc.turnOn() : lc.turnOff();
 + }
 + }
 +
 + std::this_thread::sleep_until(timer + time_step);
 + lc.turnOff();
 +
 + // Sleeping long enough to make sure that the receptor received at least a full octet of zeros (it will mark the end of transmission for it)
 + std::this_thread::sleep_for(2 * time_step);
 +}
 +
 +// This main simply get the input from stdin and act in consequence
 +int main() {
 + LedController lc(GPIO_PIN);
 + std::string message;
 +
 + // Getting the first string
 + std::cout << "Waiting for a string to send: (\"/test\" to launch a sequence of 0 and 1, \"/end\" to end the program)" << std::endl;
 + std::getline(std::cin, message);
 +
 + // Sending message if it is different of "/end" and "/test"
 + // "/end" will end the loop and therefore, the execution
 + while(message != "/end") {
 + // I feel like it's ok to not use a switch because I don't plan to add more commands, but if you plan to, you should definitely use one
 + if(message == "/test") {
 + // The test sequence is a sequence of upper case "u" because in ASCII, an U is 01010101, and this makes a pretty good test sequence
 + transmit(lc, "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU");
 + }
 + else {
 + transmit(lc, message);
 + }
 +
 + std::cout << "Transfer complete.\n" << std::endl;
 +
 + std::cout << "Waiting for a string to send: " << std::endl;
 + std::getline(std::cin, message);
 + }
 +
 + return ALL_GOOD;
 +}
 +</code>
  
  
 ---- ----
-//Auteur: Sylvain Durand//+//Auteur: S. Durand//
diy/projets/tl_partieemetteur.1527236512.txt.gz · Dernière modification : 2018/05/25 08:21 de sdurand