Voir les contributions

Cette section vous permet de consulter les contributions (messages, sujets et fichiers joints) d'un utilisateur. Vous ne pourrez voir que les contributions des zones auxquelles vous avez accès.


Sujets - bricoleau

Pages: [1]
1
Bus CAN / Réception CAN optimale
« le: décembre 22, 2017, 01:30:56 pm »
Un petit sujet de discussion pour partager mon expérimentation de ce matin.
Rien de fracassant, mais quelques infos quand même.

La question de départ était : Depuis le programme arduino, comment vérifier la présence de message reçu dans le MCP2515, de la manière la plus rapide possible ?

En effet, quel que soit le programme, l'arduino va devoir se poser cette question très régulièrement, disons plusieurs milliers de fois par seconde.
Et dans la plupart des cas, la réponse sera négative.
Il serait donc dommage que la question coûte cher en CPU, surtout sur les cas de réponse négative.

Le MCP2515 dispose d'une sortie INT. Celle-ci donne l'état de ses buffers de réception internes.
Elle est à GND tant qu'il y a un message disponible à récupérer.

Par ailleurs, l'arduino peut dialoguer avec le MCP2515 via la liaison SPI, au travers des méthodes CAN.checkReceive() et CAN.readMsgBuf().
Il est donc possible de ne pas utiliser la sortie INT et d'utiliser seulement ces méthodes d'accès.

De plus, comme la méthode CAN.readMsgBuf() retourne la valeur CAN_NOMSG lorsqu'il n'y a rien à lire,  on peut s'interroger sur l'utilité d'appeler systématiquement CAN.checkReceive() avant CAN.readMsgBuf().
Cet appel systématique pourrait être justifié si le checkReceive() répondait significativement plus vite en cas d'absence de message à récupérer.

J'ai donc testé plusieurs cas :
  • sortie INT utilisée via une interruption
  • sortie INT lue par un digitalRead
  • sortie INT lue par accès direct aux registres de l'arduino
  • utilisation de la méthode checkReceive()
  • utilisation directe de la méthode readMsgBuf()

Le test a été effectué à vide, c'est-à-dire avec un bus CAN sur lequel aucun message ne circule, car l'objectif était de vérifier l'absence de message disponible dans le MCP2515, le plus vite possible

Le programme de test
#include <mcp_can.h>

const char* pgm =
"Programme d'evaluation des diverses façons de verifier la presence d'un message CAN a lire";

/* Cablage Arduino <-> MCP2515
 *   D3  <-> INT
 *   D13 <-> SCK
 *   D11 <-> SI
 *   D12 <-> SO
 *   D10 <-> CS
 *   GND <-> GND
 *   5V  <-> VCC
 */
const uint8_t pin_INT = 3;
const uint8_t pin_CS = 10;

MCP_CAN CAN(pin_CS);

const uint32_t nb_tests = 100000UL;

uint32_t bidon;//variable globale bidon pour tromper le compilo

char* reference()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    //Si on ne met rien de consistant dans la boucle for,
    //le compilo va optimiser tout ça et virer le code inutile
    if (bidon < 100) bidon++;
    //A cet endroit on va mettre les differentes versions à tester
  }
  return __func__;
}

volatile bool message_a_lire = false; //mise à jour par interruption, non implémentée pour le test
char* version_interruption()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (message_a_lire)
    {
      bidon++;//on n'arrive jamais ici car aucun message echangé lors du test
    }
  }
  return __func__;
}

char* version_lecture_pin_INT_digitalRead()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (!digitalRead(pin_INT))
    {
      bidon++;
    }
  }
  return __func__;
}

char* version_lecture_pin_INT_directe()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (!(PIND & (1 << pin_INT)))
    {
      bidon++;
    }
  }
  return __func__;
}

char* version_CAN_checkReceive()
{
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (CAN.checkReceive() == CAN_MSGAVAIL)
    {
      bidon++;
    }
  }
  return __func__;
}

char* version_CAN_readMsgBuf()
{
  uint32_t rxId;
  uint8_t rxTaille;
  uint8_t rxData[8];
  for (uint32_t i=0; i<nb_tests; i++)
  {
    if (bidon < 100) bidon++;
    if (CAN.readMsgBuf(&rxId, &rxTaille, rxData) == CAN_OK)
    {
      bidon++;
    }
  }
  return __func__;
}

void evaluer(char* fonction())
{
  static uint32_t chrono_reference;
 
  uint32_t chrono = micros();
  char* nom = fonction();
  chrono = micros() - chrono;

  if (fonction == reference)
  {
    chrono_reference = chrono;
  }

  Serial.print("\nEvaluation ");
  Serial.print(nom);
  Serial.println(" :");
  Serial.print("  chrono_global_brut=");
  Serial.print(chrono);
  Serial.println(" µs");
  Serial.print("  chrono_unitaire_brut=");
  Serial.print(chrono / (nb_tests / 1000));
  Serial.println(" ns");
  chrono -= chrono_reference;
  Serial.print("  chrono_global_net=");
  Serial.print(chrono);
  Serial.println(" µs");
  Serial.print("  chrono_unitaire_net=");
  Serial.print(chrono / (nb_tests / 1000));
  Serial.println(" ns");
}

void setup()
{
  pinMode(pin_INT, INPUT);
  Serial.begin(115200);
  Serial.println(pgm);
  Serial.print("\nNombre de tests de lecture par tir : ");
  Serial.println(nb_tests);
  evaluer(reference);
  evaluer(version_interruption);
  evaluer(version_lecture_pin_INT_digitalRead);
  evaluer(version_lecture_pin_INT_directe);
  evaluer(version_CAN_checkReceive);
  evaluer(version_CAN_readMsgBuf);
}

void loop()
{
}

Résultat sur le terminal :
Programme d'evaluation des diverses façons de verifier la presence d'un message CAN a lire

Nombre de tests de lecture par tir : 100000

Evaluation reference :
  chrono_global_brut=75968 µs
  chrono_unitaire_brut=759 ns
  chrono_global_net=0 µs
  chrono_unitaire_net=0 ns

Evaluation version_interruption :
  chrono_global_brut=107392 µs
  chrono_unitaire_brut=1073 ns
  chrono_global_net=31424 µs
  chrono_unitaire_net=314 ns

Evaluation version_lecture_pin_INT_digitalRead :
  chrono_global_brut=478392 µs
  chrono_unitaire_brut=4783 ns
  chrono_global_net=402424 µs
  chrono_unitaire_net=4024 ns

Evaluation version_lecture_pin_INT_directe :
  chrono_global_brut=145120 µs
  chrono_unitaire_brut=1451 ns
  chrono_global_net=69152 µs
  chrono_unitaire_net=691 ns

Evaluation version_CAN_checkReceive :
  chrono_global_brut=1974784 µs
  chrono_unitaire_brut=19747 ns
  chrono_global_net=1898816 µs
  chrono_unitaire_net=18988 ns

Evaluation version_CAN_readMsgBuf :
  chrono_global_brut=2006252 µs
  chrono_unitaire_brut=20062 ns
  chrono_global_net=1930284 µs
  chrono_unitaire_net=19302 ns

Il y a une fonction de référence dont on mesure le temps d'exécution, puis les diverses versions testées.
Afin d'évaluer le temps d'exécution spécifique à chacune d'elles, je prends son temps d'exécution brut, duquel je déduis le temps d'exécution de la fonction de référence, pour avoir un résultat net qui correspond exactement aux quelques lignes de différence dans le code.

Evidemment, sans surprise, le test sur un booléen d'état mis à jour par une interruption est le plus rapide. Il ne coûte que 314 nanosecondes.
Encore que, cela me semble vraiment très peu. Je me demande si le compilo n'a pas optimisé ce petit bout de programme trop simple, pour laisser cette variable dans un registre de l'atmega.

On voit bien la différence entre un bon gros digitalRead et une lecture directe du registre PIND. En termes de rapidité, celui-ci n'est d'ailleurs pas loin du cas avec interruption.

Enfin, le checkReceive et le readMsgBuf ont des temps d'exécution voisins à 19 microsecondes (toujours dans le cas où il n'y a rien à lire), avec un petit avantage pour le checkReceive.


Au vu de ces résultats, je pars sur le modèle de réception CAN ci-dessous
#include <mcp_can.h>
/* Cablage Arduino <-> MCP2515
 *   D3  <-> INT
 *   D13 <-> SCK
 *   D11 <-> SI
 *   D12 <-> SO
 *   D10 <-> CS
 *   GND <-> GND
 *   5V  <-> VCC
 */
const uint8_t pin_INT = 3; //valeur 2 ou 3
const uint8_t pin_CS = 10;

MCP_CAN CAN(pin_CS);

volatile bool message_CAN_disponible = false;

void ISR_reception_CAN()
{
  message_CAN_disponible = true;
}

void initialiser_reception_CAN()
{
  pinMode(pin_INT, INPUT);
  int8_t numero_interruption = digitalPinToInterrupt(pin_INT);
  if (numero_interruption != -1)
  {
    attachInterrupt(numero_interruption, ISR_reception_CAN, FALLING);
  }
  else
  {
    //erreur : pas d'interruption disponible pour la valeur de pin_INT
  }
}

void CAN_recup()
{
  if (message_CAN_disponible)
  {
    message_CAN_disponible = false;
    while (!(PIND & (1 << pin_INT)))// ou bien !digitalRead(pin_INT)
    {
      uint32_t rxId;
      uint8_t rxTaille;
      uint8_t rxBuffer[8];
      if (CAN.readMsgBuf(&rxId, &rxTaille, rxBuffer) == CAN_OK)
      {
        traiter_message_CAN_recu(rxId, rxTaille, rxBuffer);
      }
    }
  }
}

void traiter_message_CAN_recu(uint32_t id, uint8_t taille, uint8_t buffer[])
{
  //...ou bien chargement dans un buffer en RAM pour traitement ultérieur
}

void setup()
{
  initialiser_reception_CAN();
  //...
}

void loop()
{
  CAN_recup();
  //...
}

2
Vos projets / Premiers pas de bricoleau
« le: novembre 11, 2017, 05:55:50 pm »
Bonjour

J'ouvre ce fil de discussion pour exposer et suivre mon petit projet personnel, bénéficier de vos conseils.
Et si le résultat peut profiter à d'autres alors tant mieux.

Autant vous prévenir, on est partis pour un moment, surtout que je ne suis pas pressé et que ma disponibilité pour avancer est parfois en pointillés.
Alors voici le début :

Je pratique arduino depuis plusieurs années, dans d'autres contextes que le modélisme ferroviaire, pour lequel je n'ai quasiment aucune expérience.
A force de lorgner de manière assidue sur les excellents articles de locoduino, j'ai décidé de me lancer dans l'aventure.
Donc en gros c'est grâce (ou à cause?) de vous que je suis ici aujourd'hui, alors il va falloir m'aider  :P

Mon idée est de fabriquer une sorte de banc de test pour locos et montages de tous poils.
L'objectif est d'avoir un petit réseau prototype, pour expérimenter tout ce qui a trait à l'électronique autour de la gestion d'un réseau miniature.
D'abord en CC, je verrai plus tard le DCC.
La principale contrainte est l'espace disponible chez moi pour cette réalisation, d'où le choix d'opter pour l'échelle N.

Mon équipement de départ :
Coté modélisme : une loco (minitrix 2053), un set de rails (PECO ST-300)
Côté électronique : des cartes arduino de toutes sortes, plein d'autres cartes diverses, un stock de composants, plaques de prototypage, une station de soudage, un oscillo, ...
En dehors du matériel roulant et des voies, j'ai l'intention de tout faire en DIY.

Ma roadmap fourmille d'idées, mais la toute première étape va être de faire rouler la bestiole.

Pour cela, je pense partir sur une alim stabilisée de type industriel 12VDC 3A, avec un montage piloté par un arduino pour hacher la tension en PWM.
Les sources qui me servent de référence sont :
La PWM : Qu’est-ce que c’est ? (2)
La PWM : Qu’est-ce que c’est ? (3)
La PWM : Qu’est-ce que c’est ? (4)
Tension hachée et pertes par effet Joule (1)
Tension hachée et pertes par effet Joule (2)
Discussion sur la synchronisation des signaux en PWM

J'ai bon jusque là?  :D

3
Bibliothèques / Bibliothèque Wire I2C
« le: janvier 27, 2016, 10:36:09 pm »
Bonjour

Décidément, le site locoduino est vraiment très vivant, avec des articles publiés ou mis à jour très régulièrement.
J'y jette un oeil avec plaisir presque tous les jours.
Bravo et merci

Aujourd'hui j'ai lu l'article sur la bibliothèque Wire.
Cela m'a fait penser que j'ai du dépiauter cette lib récemment (pour mon module contrôleur d'aiguilles) et que j'ai vu quelques trucs complémentaires qui pourraient être utiles à partager. Alors les voici tels que je les ai compris

Tout d'abord, que l'on soit maître ou esclave, émetteur ou récepteur, cette lib s'appuie sur des buffer alloués de manière statique en RAM : un pour l'émission, un pour la réception. Ceux-ci ont une taille de 32 octets. Cette limite n'est pas imposée par le protocole I2C, mais à moins de modifier la bibliothèque, c'est une limite de taille de message à prendre en compte.

Côté maître émetteur :
- beginTransmission() ne fait rien d'autre qu'enregistrer en RAM l'adresse du destinataire
- write() ne fait rien d'autre qu'alimenter en RAM le buffer d'émission
- tout se passe lors du endTransmission() : c'est uniquement lors de l'exécution de cette fonction qu'a lieu la communication physique sur le bus

Côté maître récepteur :
- tout se passe lors du requestFrom(), qui retourne le nombre d'octets effectivement chargés dans le buffer de réception, une fois la communication physique terminée.
- available(), read() ou peek() ne font que piocher dans le buffer de RAM
Du coup, personnellement je trouve la fonction available() assez inutile, et préfère coder des trucs du style
if (requestFrom(esclave, 5) == 5)
{
  for (uint8_t i=0; i<5; i++) reponse[i] = Wire.read();
  ...traiter la réponse
}
else
{
  ... traiter l'erreur
}

Côté esclave : même gestion via deux buffers en RAM
Le handler onReceive() est appelé une fois la communication physique terminée, pour exploiter les octets du buffer. Là encore, l'usage de available() me semble superflu car le nombre d'octets reçus est passé directement en paramètre d'appel du handler.
(bigre au passage, je réalise que ce terme handler fait bizarre, au sein d'articles aussi attachés au respect de la langue française  :P)

Le handler onRequest() est plus subtil, car c'est le seul qui intervient entre deux étapes de la communication physique.
Le but est alimenter le buffer d'émission via la fonction write().

Côté esclave, je me suis pris la tête sur deux points :

1) d'abord ces handler sont déclenchés par des interruptions, c'est-à-dire au beau milieu de n'importe quel endroit du programme.
Et dès lors que l'information échangée fait plus d'un octet, il y a quelques précautions à prendre.
Par exemple en réception, il ne faudrait pas aller mettre à jour des variables globales pile au moment où une partie du programme était en train de les utiliser. Surtout si ces variables ont des liens de corrélation entre elles. Cela peut générer un comportement inattendu du programme.

Pareil en émission : par exemple, si le message émis contient les valeurs de 2 variables, et que l'interruption se déclenche au moment où le programme vient de mettre à jour la première variable mais pas encore la seconde, cela peut entraîner l'émission d'un message incohérent.

Du coup, je préfère passer systématiquement par des buffers de cloisonnement, entre les routines liées aux interruptions et le programme principal.

2) l'impossibilité par l'esclave de connaître le nombre d'octets attendus par le maître
Là pour le coup, la bibliothèque Wire n'y est pour rien. C'est le protocole I2C qui veut ça.
Le paramètre de quantité passé à requestFrom() reste au niveau du maître, qui interrompt la communication en provenance de l'esclave lorsqu'il a reçu la quantité d'octets attendue.
C'est bien dommage, car lorsqu'on souhaite que l'esclave puisse retourner plusieurs types de message différents, on ne peut pas utiliser le nombre d'octets attendus comme élément discriminant.
Il n'y a que deux options :
a) mettre en place un protocole logiciel. Par exemple le maître commence par envoyer une information discriminante, puis lance un requestFrom(). Les périphériques I2C industriels marchent d'ailleurs souvent comme ça.
b) partir du principe que l'esclave renvoie toutes les informations possibles à chaque sollicitation, en mettant les plus importantes en premier, charge au maître de couper quand bon lui semble.

Pour mon contrôleur d'aiguilles, je suis parti sur cette dernière option, la plus simple à première vue, mais je le regrette.
Cela oblige à faire tous les write dans le handler onRequest (avec la limite de 32 octets), car à ce moment là on ne sait pas encore combien d'octets le maître va réellement lire.
Mais au final, je me suis retrouvé dans l'embarras quand j'ai ajouté un dernier octet de CRC antiparasite.
Parce que là, même si le maître ne s'intéresse qu'aux premiers octets du message, il doit quand même tout lire afin de vérifier le CRC.
Pour bien faire, il faudrait soit segmenter le message avec un CRC à la fin de chaque segment, afin que le maître puisse interrompre la communication par endroits. Soit repartir sur l'option a) avec une petite couche de protocole logiciel.

4
Vos projets / Contrôle numérique des aiguillages
« le: janvier 12, 2016, 11:03:12 pm »
Bonjour

Voici le petit chantier qui m’occupe actuellement, sur lequel j’aimerais bien recueillir vos avis éclairés  :D

Commençons d’abord par préciser que je n’ai aucun réseau à la maison, ni aucune expérience du modélisme ferroviaire. Mais j’ai d’autres compétences et centres d’intérêt qui font que je commence à maîtriser un peu arduino et ce qui gravite autour.

Un ami m’a demandé de lui filer un coup de main pour passer son réseau (courant continu) en commande numérique. Il dispose d’une quinzaine d’aiguillages électriques de type jouef, ainsi que quelques dételeurs, qu’il souhaite commander par ordinateur, ou par une centrale qu’il se fabriquera.

On n’est clairement pas dans du modélisme haut de gamme et dispendieux.
Du coup, pour rester dans la même philosophie, mon objectif était de lui proposer une solution bon marché. Suite à la lecture de plusieurs articles très intéressants sur locoduino, je suis parti sur la création de modules de commande d’aiguillages.

Chaque module a 8 sorties, pour gérer 4 aiguillages simples.
Il est constitué d’une arduino pro mini et deux ULN2803, dont les sorties sont couplées deux à deux pour être en mesure de fournir jusqu'à 1A aux bobines de l'aiguillage, bien que j’ai quelques doutes sur la pertinence de ce montage. L’expérience dira si cela tient la route longtemps.
Le tout est assemblé à la main, soudé sur une plaque de prototypage pastillée, avec une connectique de type bornier à vis côté 12V/Aiguillages.

L’arduino pro mini fonctionne comme un périphérique escale I2C. Les modules pourront être branchés en cascade, chacun ayant sa propre adresse I2C. Le tout devra être piloté par une centrale. Une foultitude d’options est possible à ce niveau-là, à affiner plus tard.

Je suis actuellement en phase de réalisation du premier prototype. Mon objectif initial est atteint : le coût matériel d’un module est de moins de 4€ frais de port compris, soit moins de 1€ par aiguillage à commander. Evidemment, il s’agit de composants achetés en ligne auprès de vendeurs asiatiques.

Photo avec les ULN2803 pas encore montés :



Pour les soudures, j’en suis resté aux gros pâtés d’étain bien baveux. Pas très doué à ce niveau-là  ;D

Si l'approche reste minimaliste côté hardware, c’est l’inverse côté logiciel embarqué dans l’arduino pro mini : aucune économie de neurones n’est tolérée. Je suis plutôt du genre à ne figer une version de code que lorsque je ne vois absolument plus rien d’améliorable.

L’arduino mini gère en autonomie le premier niveau de sécurité des aiguillages : durée des impulsions, contrôle d’exclusion des sorties contradictoires etc.
J’ai retenu ce principe de module « intelligent » pour sécuriser les équipements contre toute défaillance logique de la centrale en amont et/ou perte de la communication avec celle-ci.

Du coup, c’est dans les fonctionnalités « ferroviaires » que j’ai quelques doutes, du fait de mon manque d’expérience dans ce domaine.

Voici ce qui est réalisé pour l’instant :
L’arduino mini reçoit des consignes de positionnement de chaque aiguillage. Il peut même recevoir simultanément plusieurs consignes pour des aiguillages différents.
Puis, de manière asynchrone, il gère l’activation des deux bobines de chaque aiguillage, sous forme d’impulsions et selon trois paramètres  communs à toutes les sorties :
  • Durée d’une impulsion vers une bobine (exemple : 500 ms)
  • Durée minimum de repos d’un aiguillage. S’applique à partir de la fin d’impulsion sur une des deux bobines de l’aiguillage (exemple : 300 ms).
  • Durée minimum entre deux débuts d’impulsion sur des aiguillages différents. Là, il s’agit de donner la possibilité de ménager la source d’alim 12V, selon sa capacité à délivrer du courant. Si cette durée est à zéro, les 4 aiguillages peuvent être basculés simultanément. Si elle est supérieure à la durée d’impulsion, un seul aiguillage à la fois peut être actif. Et entre les deux, cela permet de régler le nombre maximum de bobines activables simultanément.
Toute consigne reçue et non immédiatement exécutable est mise en attente pour être exécutée dès que possible.

De plus, le dispositif I2C Maître a la possibilité d’interroger l’arduino mini pour connaître son état instantané :
  • Positions théoriques des aiguillages (= consignes)
  • Positions réelles des aiguillages
  • Nombre de bobines en cours d’alimentation
  • Durée écoulée depuis la dernière fin d’impulsion
  • Valeurs des paramètres utilisés, que le Maître peut également modifier.
Cela donne la possibilité au Maître de gérer le niveau de sollicitation de la source d’alim 12V, de manière globale pour l’ensemble des modules présents sur le bus.

Les trames échangées en I2C comportent aussi des CRC, pour réduire les risques d’erreurs de communication liées à des parasites électriques. Une trame incorrecte est ignorée, charge au Maître de gérer la chose.

A ce stade, la question est : quelles fonctionnalités utiles pourrais-je avoir oubliées, au niveau de l’arduino mini, dans une optique de module le plus générique possible ?

Je m’interroge par exemple sur la position réelle des aiguilles.
Il n’y a aucun capteur pour donner un retour information. Ainsi, à la mise sous tension, la position de chaque aiguillage est indéterminée tant qu’une première impulsion n’a pas été envoyée. De plus, si l’aiguille est déplacée manuellement ou par un train qui circulerait en sens inverse (possible ?), il peut y avoir un déphasage logique avec l’arduino mini.
Actuellement, si l’arduino considère qu’un aiguillage est déjà dans la position indiquée par une nouvelle consigne, il ne déclenche rien.
Peut-être ajouter une fonctionnalité de remise en phase logique?

Autre interrogation : les aiguillages liés, typiques d’un changement de voie entre deux voies parallèles.
Ils seront commandés séparément, mais est-il utile ou nécessaire de gérer leur cohérence au niveau de l’arduino mini?

Autres points oubliés ?

5
Bibliothèques / Bouton poussoir
« le: janvier 05, 2016, 11:57:14 pm »
Bonjour

Pour mon premier topic, je vais essayer d'apporter ma petite contribution au microcosme locoduino.

J'ai lu avec grand intérêt la plupart des articles du site, vraiment très bien faits et instructifs.
Au début, je galérais un peu dans la navigation pour en retrouver un sur lequel je voulais revenir, mais j'ai fini par trouver une petite bidouille : depuis la page d'accueil, je fais une recherche avec un mot clé au pif, puis je clique sur "tous les articles"   :P

Le dernier qui a attiré mon attention est celui sur les boutons poussoirs, un classique à destination des débutants.
J'aime beaucoup la structure de cet article ainsi que la qualité des illustrations

Concernant le code proposé avec la librairie bounce2, il me semble voir une petite amélioration possible :
bouton.attach(bp, INPUT_PULLUP);qui permet de rendre le code encore un peu plus simple en masquant l'usage de la primitive pinMode.

Bon, il reste encore le INPUT_PULLUP ::)

De mon côté, j'avais mis en ligne ailleurs une petite librairie qui me semble encore plus à la portée des débutants, surtout les francophones anglophobes   :D.
Elle définit complètement l'objet bouton poussoir dans son cablage le plus simple, et fournit toutes les fonctions de base en encapsulant toutes les primitives arduino.
Elle est aussi un poil plus économe en RAM que la bounce2.

Je vous la soumets donc comme une alternative possible, en pièce jointe (librairie complète avec exemples directement disponibles dans l'IDE arduino)

6
Présentez vous ! / Bonjour
« le: janvier 05, 2016, 10:53:16 pm »
Hello

Alors moi c'est bricoleau.
Pas de train à la maison  :-[
Mais je m'intéresse de près au modélisme arduiferroviaire

Actuellement je prépare un contrôleur d'aiguillages à commande numérique pour un pote

Pages: [1]