LOCODUINO

Forum de discussion
Dépôt GIT Locoduino
Flux RSS

mercredi 29 mars 2017

49 visiteurs en ce moment

Un moniteur de signaux DCC

Comment espionner les commandes émises par la centrale DCC

. Par : Dominique

Dans l’article « L’Arduino et le système de commande numérique DCC », nous avons vu à quoi ressemble la norme DCC  .

Dans l’article « Comment piloter trains et accessoires en DCC avec un Arduino (2) », nous avons étudié la bibliothèque CmdrArduino qui se charge de générer les signaux DCC   pour nous en tâche de fond.

Dans cet article nous allons utiliser une autre bibliothèque pour visualiser sur notre ordinateur les trames DCC qui sont envoyées aux rails, pour vérifier le bon fonctionnement de notre projet.

Cette bibliothèque est celle de Minabay qui est aussi utilisée dans l’article Un décodeur d’accessoires universel (1).

Mais cette fois-ci, la bibliothèque va observer le signal DCC   et afficher les paquets vus toutes les 2 secondes.

La trace ci-dessous correspond à ce que ma centrale "va-et-vient" envoie en mode automatique. On peut voir l’adresse 4 de la loco (premier octet 00000100) et les 2 commandes :

  • la lumière (fonction groupe 1) : 10010000 (allumée)
  • la vitesse : advanced operation instruction 00111111 + octet de vitesse (bits 0..6) et direction (bit 7)

Le dernier octet est le code de contrôle qui ne nous intéresse pas ici.

J’ai ajouté la signification des paquets à droite de la liste.

Premières constatations :

  • On compte bien entre 130 et 140 paquets par seconde comme calculé dans la Présentation de la norme DCC. Ici on a 272 paquets en 2 secondes.
  • Environ 90% des paquets sont des Idle 11111111 00000000 11111111
  • La bibliothèque répète bien automatiquement les paquets de vitesse et lumière, environ 4 fois par seconde

Réalisation matérielle du moniteur

J’ai utilisé un simple Nano complété par un petit circuit d’interface entre les rails (tension +/- 15 volts, insupportable pour l’Arduino) et la broche 2 programmée en entrée digitale sous interruption.

Ce circuit est celui du site Minabay : il convient parfaitement.

PNG - 245.4 ko

Voici la liste des composants nécessaires :

  • un Arduino (j’ai choisi un Nano mais n’importe lequel doit convenir)
  • 2 résistances de 10kΩ
  • 1 résistance de 1kΩ
  • 1 Diode 1N4148
  • 1 Opto-coupleur 6N137 (il en faut un rapide quand même)
  • 1 plaque d’essai pour assembler les composants

Plutôt que d’utiliser un Uno avec son bornier femelle, j’ai préféré le Nano sans bornier. J’ai inséré des barrettes à souder dans les trous à la place des barrettes à picots habituels : de cette façon les fils à raccorder au Nano sont soudés sur les barrettes ce qui procure un meilleur contact qu’un ensemble de prises mâle et femelle.

De ce fait, les barrettes du Nano ne sont soudées qu’aux 4 coins ce qui permet de récupérer l’Arduino si le projet n’est plus utilisé plus tard.

A l’arrière de la plaque d’essai, le nombre de soudures est minimal et on ne prend pas de risque da court-circuits.

Le circuit terminé ne m’a pris qu’une petite heure. Le connecteur 4 points permet de sortir une liaison I2C vers un afficheur LCD 4 lignes de 20 caractères qui devrait me permettre de me passer du PC/Mac, à condition d’ajouter une petite alimentation 5V. Pour la liaison I2C, ne pas oublier les 2 resistances de rappel au 5V, de 4,7kΩ chacune sur SDA et SDL respectivement.

Le logiciel qui suit ne tient pas compte de cette possibilité.

La mise en boite est un simple sandwich entre 2 planchettes de contreplaqué.

Le logiciel

Après installation de la bibliothèque, on ouvre simplement l’exemple DCC_Monitor qui se trouve dans le dossier DCC_Decoder.

  1. // DCC_Monitor
  2. // Based on DCC_Decoder Library Version 4
  3. // Dump of DCC packets every 2 seconds
  4. // Arduino Pin 2 is the DCC input. It drives interrupt 0.
  5.  
  6. #include <DCC_Decoder.h>
  7.  
  8. ////////////////////////////////////////////////////////////////////////////////////////
  9. ////////////////////////////////////////////////////////////////////////////////////////
  10. //
  11. // Defines and structures
  12. //
  13. #define kDCC_INTERRUPT 0
  14.  
  15. typedef struct
  16. {
  17. int count;
  18. byte validBytes;
  19. byte data[6];
  20. } DCCPacket;
  21.  
  22. ////////////////////////////////////////////////////////////////////////////////////////
  23. ////////////////////////////////////////////////////////////////////////////////////////
  24. //
  25. // The dcc decoder object and global data
  26. //
  27. int gPacketCount = 0;
  28. int gIdlePacketCount = 0;
  29. int gLongestPreamble = 0;
  30.  
  31. DCCPacket gPackets[25];
  32.  
  33. static unsigned long lastMillis = millis();
  34.  
  35. ////////////////////////////////////////////////////////////////////////////////////////
  36. ////////////////////////////////////////////////////////////////////////////////////////
  37. //
  38. // Packet handlers
  39. //
  40.  
  41. // ALL packets are sent to the RawPacket handler. Returning true indicates
  42. // that packet was handled. DCC library starts watching for
  43. // next preamble. Returning false and library continue parsing packet
  44. // and finds another handler to call.
  45. boolean RawPacket_Handler(byte byteCount, byte* packetBytes)
  46. {
  47. // Bump global packet count
  48. ++gPacketCount;
  49.  
  50. int thisPreamble = DCC.LastPreambleBitCount();
  51. if( thisPreamble > gLongestPreamble )
  52. {
  53. gLongestPreamble = thisPreamble;
  54. }
  55.  
  56. // Walk table and look for a matching packet
  57. for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
  58. {
  59. if( gPackets[i].validBytes )
  60. {
  61. // Not an empty slot. Does this slot match this packet?
  62. // If so, bump count.
  63. if( gPackets[i].validBytes==byteCount )
  64. {
  65. char isPacket = true;
  66. for( int j=0; j<byteCount; j++)
  67. {
  68. if( gPackets[i].data[j] != packetBytes[j] )
  69. {
  70. isPacket = false;
  71. break;
  72. }
  73. }
  74. if( isPacket )
  75. {
  76. gPackets[i].count++;
  77. return false;
  78. }
  79. }
  80. }else{
  81. // Empty slot, just copy over data
  82. gPackets[i].count++;
  83. gPackets[i].validBytes = byteCount;
  84. for( int j=0; j<byteCount; j++)
  85. {
  86. gPackets[i].data[j] = packetBytes[j];
  87. }
  88. return false;
  89. }
  90. }
  91.  
  92. return false;
  93. }
  94.  
  95. // Idle packets are sent here (unless handled in rawpacket handler).
  96. void IdlePacket_Handler(byte byteCount, byte* packetBytes)
  97. {
  98. ++gIdlePacketCount;
  99. }
  100.  
  101. ////////////////////////////////////////////////////////////////////////////////////////
  102. ////////////////////////////////////////////////////////////////////////////////////////
  103. //
  104. // Setup
  105. //
  106. void setup()
  107. {
  108. Serial.begin(9600);
  109.  
  110. DCC.SetRawPacketHandler(RawPacket_Handler);
  111. DCC.SetIdlePacketHandler(IdlePacket_Handler);
  112.  
  113. DCC.SetupMonitor( kDCC_INTERRUPT );
  114. }
  115.  
  116. ////////////////////////////////////////////////////////////////////////////////////////
  117. ////////////////////////////////////////////////////////////////////////////////////////
  118. void DumpAndResetTable()
  119. {
  120. char buffer60Bytes[60];
  121.  
  122. Serial.print("Total Packet Count: ");
  123. Serial.println(gPacketCount, DEC);
  124.  
  125. Serial.print("Idle Packet Count: ");
  126. Serial.println(gIdlePacketCount, DEC);
  127.  
  128. Serial.print("Longest Preamble: ");
  129. Serial.println(gLongestPreamble, DEC);
  130.  

    >
  131. Serial.println("Count Packet_Data");
  132. for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
  133. {
  134. if( gPackets[i].validBytes > 0 )
  135. {
  136. Serial.print(gPackets[i].count, DEC);
  137. if( gPackets[i].count < 10 )
  138. {
  139. Serial.print(" ");
  140. }else{
  141. if( gPackets[i].count < 100 )
  142. {
  143. Serial.print(" ");
  144. }else{
  145. Serial.print(" ");
  146. }
  147. }
  148. Serial.println(
  149. DCC.MakePacketString(
  150. buffer60Bytes,
  151. gPackets[i].validBytes,
  152. &gPackets[i].data[0]
  153. )
  154. );
  155. }
  156. gPackets[i].validBytes = 0;
  157. gPackets[i].count = 0;
  158. }
  159. Serial.println("============================================");
  160.  
  161. gPacketCount = 0;
  162. gIdlePacketCount = 0;
  163. gLongestPreamble = 0;
  164. }
  165.  
  166. ////////////////////////////////////////////////////////////////////////////////////////
  167. ////////////////////////////////////////////////////////////////////////////////////////
  168. //
  169. // Main loop
  170. //
  171. void loop()
  172. {
  173. DCC.loop();
  174.  
  175. if( millis()-lastMillis > 1000 )
  176. {
  177. DumpAndResetTable();
  178. lastMillis = millis();
  179. }
  180. }

Une fois le programme compilé et installé dans l’Arduino, on relie l’interface aux rails, puis on lance le moniteur de l’environnement Arduino ou tout autre terminal de son choix.

J’ai testé le programme sur différents Arduino, y compris le Due.

Version LCD I2C

On trouve facilement, pour 10€ environ, des afficheurs 4 lignes de 20 caractères avec une interface I2C.

J’ai donc modifié l’exemple de Minabay ci-dessus pour obtenir un affichage décodé selon les besoins de mon projet (centrale va-et-vient).

Toutes les 2 secondes, l’afficheur présente :

  • Sur la ligne 1 : le nombre de paquets IDLE et le nombre de paquets de données valides ;
  • Sur les lignes suivantes : le nombre de chaque type de paquet de données et leur signification.

Par exemple :
1@4>3 signifie 1 paquet d’adresse DCC   4, direction avant et vitesse 3
4@4>5 signifie 4 paquet d’adresse DCC 4, direction avant et vitesse 5
15@4L0 signifie 15 paquets d’adresse DCC 4, lumière éteinte
4@4>7 signifie 4 paquet d’adresse DCC 4, direction avant et vitesse 7

On voit bien l’accélération de 3 à 11 crans DCC pendant l’interval de 2 secondes.

Le logiciel de cette version est ici :

  1. /* DCC_Monitor for LCD 4x20
  2.  
  3.   Based on DCC_Decoder Library Version 4 from http://www.mynabay.com/arduino
  4.   Display DCC packets counts every 2 seconds (IDLE should be around 50%)
  5.   Arduino Pin 2 is the DCC input. It drives interrupt 0.
  6.   Arduino Pin A4 is SDA, Pin A5 is SLC (even on Nano !).
  7.   To connect an LCD 20x4 display via I2C
  8.  
  9.   Don't forget I2C pull-up resistors (4,7K each) connected to + 5V
  10.   Compilation on Arduino 1.5.x necessitate a minor change in the DCC_Decoder library:
  11.   Replace "prog_char*" type by "char*"
  12.  
  13.   Display 1st line : Idle paquets count, valid data paquets count
  14.   Display next lines : for each data type :
  15.   - paquet count, DCC address,
  16.   - speed paquet : direction (< or >), speed value (0..127)
  17.   - light function paquet (FL) : L0 (off) or L1 (on)
  18.  
  19.   This program is free software; you can redistribute it and/or
  20.   modify it under the terms of the GNU General Public License
  21.   as published by the Free Software Foundation.
  22.  
  23.   Copyright (c) 2014 Dominique Bultez. */
  24.  
  25. #define VERSION "1.0b3 Oct 14"
  26. #define D_USB // comment this line to cancel display to the computer's terminal
  27.  
  28. #include <DCC_Decoder.h>
  29. #include <Wire.h>
  30. #include <LiquidCrystal_I2C.h>
  31.  
  32. LiquidCrystal_I2C lcd(0x27,20,4); // set the LCD address to 0x27 for a 20 chars and 4 line display
  33.  
  34. ////////////////////////////////////////////////////////////////////////////////////////
  35. //
  36. // Defines and structures
  37. //
  38. #define kDCC_INTERRUPT 0
  39.  
  40. typedef struct
  41. {
  42. int count;
  43. byte validBytes;
  44. byte data[6];
  45. } DCCPacket;
  46.  
  47. ////////////////////////////////////////////////////////////////////////////////////////
  48. //
  49. // The dcc decoder object and global data
  50. //
  51. int gPacketCount = 0;
  52. int gIdlePacketCount = 0;
  53. int gLongestPreamble = 0;
  54.  
  55. DCCPacket gPackets[25];
  56.  
  57. static unsigned long lastMillis = millis();
  58.  
  59. ////////////////////////////////////////////////////////////////////////////////////////
  60. //
  61. // Packet handlers
  62. //
  63.  
  64. // ALL packets are sent to the RawPacket handler. Returning true indicates
  65. // that packet was handled. DCC library starts watching for
  66. // next preamble. Returning false and library continue parsing packet
  67. // and finds another handler to call.
  68. boolean RawPacket_Handler(byte byteCount, byte* packetBytes)
  69. {
  70. // Bump global packet count
  71. ++gPacketCount;
  72.  
  73. int thisPreamble = DCC.LastPreambleBitCount();
  74. if( thisPreamble > gLongestPreamble )
  75. {
  76. gLongestPreamble = thisPreamble;
  77. }
  78.  
  79. // Walk table and look for a matching packet
  80. for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
  81. {
  82. if( gPackets[i].validBytes )
  83. {
  84. // Not an empty slot. Does this slot match this packet? If so, bump count.
  85. if( gPackets[i].validBytes==byteCount )
  86. {
  87. char isPacket = true;
  88. for( int j=0; j<byteCount; j++)
  89. {
  90. if( gPackets[i].data[j] != packetBytes[j] )
  91. {
  92. isPacket = false;
  93. break;
  94. }
  95. }
  96. if( isPacket )
  97. {
  98. gPackets[i].count++;
  99. return false;
  100. }
  101. }
  102. }else{
  103. // Empty slot, just copy over data
  104. gPackets[i].count++;
  105. gPackets[i].validBytes = byteCount;
  106. for( int j=0; j<byteCount; j++)
  107. {
  108. gPackets[i].data[j] = packetBytes[j];
  109. }
  110. return false;
  111. }
  112. }
  113.  
  114. return false;
  115. }
  116.  
  117. // Idle packets are sent here (unless handled in rawpacket handler).
  118. void IdlePacket_Handler(byte byteCount, byte* packetBytes)
  119. {
  120. ++gIdlePacketCount;
  121. }
  122.  
  123. ////////////////////////////////////////////////////////////////////////////////////////
  124. //
  125. // Setup
  126. //
  127. void setup()
  128. {
  129. Serial.begin(115200);
  130. lcd.init(); // initialize the lcd
  131. lcd.backlight();
  132.  
  133. lcd.print("DCC monitor Version ");
  134. lcd.print(VERSION);
  135. delay(1000);
  136. //lcd.clear();
  137.  
  138. DCC.SetRawPacketHandler(RawPacket_Handler);
  139. DCC.SetIdlePacketHandler(IdlePacket_Handler);
  140.  
  141. DCC.SetupMonitor( kDCC_INTERRUPT );
  142. }
  143.  
  144. ////////////////////////////////////////////////////////////////////////////////////////
  145. void DumpAndResetTable()
  146. {
  147. char buffer60Bytes[60];
  148.  
  149. #ifdef D_USB
  150. Serial.print("Total Packet Count: ");
  151. Serial.println(gPacketCount, DEC);
  152. #endif
  153.  
  154. lcd.clear();
  155. //lcd.setCursor(0, 0); // 1e ligne
  156. lcd.print("Idle:");
  157. lcd.print(gIdlePacketCount, DEC);
  158.  
  159. #ifdef D_USB
  160. Serial.print("Idle Packet Count: ");
  161. Serial.println(gIdlePacketCount, DEC);
  162. #endif
  163.  
  164. lcd.print(" Data:");
  165. lcd.print(gPacketCount - gIdlePacketCount, DEC);
  166. lcd.print(" ");
  167.  
  168. #ifdef D_USB
  169. Serial.print("Longest Preamble: ");
  170. Serial.println(gLongestPreamble, DEC);
  171. Serial.println("Count Packet_Data");
  172. #endif
  173.  
  174. lcd.setCursor(0, 1); // 2e ligne
  175. int j=0;
  176. for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
  177. {
  178. if( gPackets[i].validBytes > 0 )
  179. {
  180. if (gPackets[i].count != gIdlePacketCount)
  181. {
  182. #ifdef D_USB
  183. Serial.print(gPackets[i].count, DEC);
  184. if( gPackets[i].count < 10 )
  185. {
  186. Serial.print(" ");
  187. }else{
  188. if( gPackets[i].count < 100 )
  189. {
  190. Serial.print(" ");
  191. }else{
  192. Serial.print(" ");
  193. }
  194. }
  195. Serial.println(
  196. DCC.MakePacketString(
  197. buffer60Bytes,
  198. gPackets[i].validBytes,
  199. &gPackets[i].data[0]
  200. )
  201. );
  202. #endif
  203.  
  204. if (gPackets[i].data[0] != 255)
  205. {
  206. lcd.print(gPackets[i].count, DEC);
  207. lcd.print("@");
  208. lcd.print(gPackets[i].data[0], DEC);
  209. switch (gPackets[i].data[1])
  210. {
  211. case 0x3F: // Advanced Operation Instruction : speed & direction
  212. if (gPackets[i].data[2] > 127)
  213. {
  214. lcd.print(">");
  215. lcd.print(gPackets[i].data[2] - 128);
  216. }else{
  217. lcd.print("<");
  218. lcd.print(gPackets[i].data[2]);
  219. }
  220. break;
  221. case 0x90: // Fonction Group One FL on
  222. lcd.print("L1");
  223. break;
  224. case 0x80: // Fonction Group One FL off
  225. lcd.print("L0");
  226. break;
  227. default:
  228. lcd.print(gPackets[i].data[1], HEX);
  229. break;
  230. }
  231. j++;
  232. if (j>5) j=0;
  233. switch (j)
  234. {
  235. case 0:
  236. lcd.setCursor(0, 1); // 2e ligne
  237. break;
  238. case 1:
  239. lcd.

    >setCursor
    (10, 1); // 2e ligne
  240. break;
  241. case 2:
  242. lcd.setCursor(0, 2); // 2e ligne
  243. break;
  244. case 3:
  245. lcd.setCursor(10, 2); // 2e ligne
  246. break;
  247. case 4:
  248. lcd.setCursor(0, 3); // 2e ligne
  249. break;
  250. case 5:
  251. lcd.setCursor(10, 3); // 2e ligne
  252. break;
  253. }
  254. }
  255. }
  256. }
  257. gPackets[i].validBytes = 0;
  258. gPackets[i].count = 0;
  259. }
  260. #ifdef D_USB
  261. Serial.println("============================================");
  262. #endif
  263.  
  264. gPacketCount = 0;
  265. gIdlePacketCount = 0;
  266. gLongestPreamble = 0;
  267. }
  268.  
  269. ////////////////////////////////////////////////////////////////////////////////////////
  270. //
  271. // Main loop
  272. //
  273. void loop()
  274. {
  275. DCC.loop();
  276.  
  277. if( millis()-lastMillis > 2000 )
  278. {
  279. DumpAndResetTable();
  280. lastMillis = millis();
  281. }
  282. }

Le code peut être téléchargé ici :

Zip - 2.7 ko

La bibliothèque peut être téléchargée ici :

Zip - 17 ko

Dernièrement j’ai ajouté un circuit d’alimentation à partir des rails : il contient un pont redresseur reliée au connecteur des rails et suivi d’une capacité de 22uF, puis d’un régulateur 7805 et d’une capacité de 100uF. La sortie est reliée directement au 5V de l’Arduino.

23 Messages

Réagissez à « Un moniteur de signaux DCC »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Projets »

Un chenillard de DEL

Enseigne de magasin

Feux tricolores

Multi-animations lumineuses

L’Arduino et le système de commande numérique DCC

Un décodeur d’accessoire DCC versatile basé sur Arduino

Un moniteur de signaux DCC

Une barrière infrarouge

Un capteur RFID

Un TCO xpressnet

Une animation sonore

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

Calcul de la vitesse d’un train miniature avec l’Arduino

La génèse d’un réseau 100% Arduino

Une horloge à échelle H0

Simulateur de soudure à arc

Un automatisme de Passage à Niveau

La rétro-signalisation sur Arduino

Décodeur pour aiguillage à solénoïdes sur Arduino

Un décodeur DCC pour les signaux à deux ou trois feux sur Arduino NANO/UNO

Etude d’un passage à niveau universel

Réalisation pratique d’un système de mesure de vitesse à l’échelle N

Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation

Un décodeur DCC pour 16 feux tricolores

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"

Réalisation d’un affichage de gare ARRIVEE DEPART

Comment piloter trains et accessoires en DCC avec un Arduino (1)

Comment piloter trains et accessoires en DCC avec un Arduino (2)

Comment piloter trains et accessoires en DCC avec un Arduino (3)

Comment piloter trains et accessoires en DCC avec un Arduino (4)

SGDD : Système de Gestion DD (1)

SGDD : Système de Gestion DD (2)

SGDD : Système de Gestion DD (3)

La PWM : Qu’est-ce que c’est ? (1)

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)

Mise en oeuvre du Bus CAN entre modules Arduino (1)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Un gestionnaire en C++ pour votre réseau (1)

Un gestionnaire en C++ pour votre réseau (2)

Un gestionnaire en C++ pour votre réseau (3)

Un gestionnaire en C++ pour votre réseau (4)

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

Contrôleur à télécommande infrarouge pour centrale DCC++

Gestion d’une gare cachée (1)

Gestion d’une gare cachée (2)

Gestion d’une gare cachée (3)

Les derniers articles

La PWM : Qu’est ce que c’est ? (4)


Jean-Luc

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"


Christian Bézanger

Réalisation d’un affichage de gare ARRIVEE DEPART


Gilbert

Contrôleur à télécommande infrarouge pour centrale DCC++


Daniel

La PWM : Qu’est ce que c’est ? (3)


Jean-Luc

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)


bobyAndCo

Un gestionnaire en C++ pour votre réseau (4)


Pierre59

Un décodeur DCC pour 16 feux tricolores


Dominique, JPClaude, Thierry

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)


bobyAndCo

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)


Dominique

Les articles les plus lus

La PWM : Qu’est-ce que c’est ? (1)

Comment piloter trains et accessoires en DCC avec un Arduino (1)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Comment piloter trains et accessoires en DCC avec un Arduino (3)

La PWM : Qu’est ce que c’est ? (4)

Une barrière infrarouge

Comment piloter trains et accessoires en DCC avec un Arduino (4)

L’Arduino et le système de commande numérique DCC

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

La PWM : Qu’est-ce que c’est ? (2)