Faire un scanneur de bus 1-Wire avec une carte Arduino

MacGyver fait la même chose avec un élastique et deux cures dents

Image d'entête

par skywodd | | Licence (voir pied de page)

Catégories : Tutoriels Arduino Projets | Mots clefs : Arduino Genuino OneWire 1-Wire Scanner

Cet article a été modifié pour la dernière fois le


Dans ce projet, nous allons apprendre ensemble à fabriquer un scanneur de bus 1-Wire qui servira par la suite pour de multiples projets et tutoriels. Grâce à ce scanner, il sera possible de connaitre les adresses des différents capteurs et périphériques 1-Wire connectés à la carte Arduino. En bonus, je vous présenterai une version améliorée du scanneur, capable de déterminer le type des périphériques connectés en plus de leurs adresses.

Sommaire

Bonjour à toutes et à tous !

Si vous avez déjà entendu parler ou même utiliser un capteur de température DS18B20, vous savez déjà ce qu'est un bus 1-Wire. Pour les autres, voici un bref résumé du bus 1-Wire.

Un bus 1-wire est un bus de communication bidirectionnel qui permet à une carte maître (une carte Arduino par exemple) de discuter avec de multiples périphériques via un unique câble (comme des capteurs de température DS18B20). C'est une technologie de Dallas Semiconductor (depuis acheté par la société Maxim) que l'on rencontre très souvent en électronique, car il s'agit d'une technologie simple, fiable et très pratique. Avec seulement trois fils (ou deux), il est possible de connecter une multitude de capteurs avec des longueurs de câbles assez importantes sans avoir de problème.

Seulement voilà, comme il peut y avoir plusieurs périphériques sur un même bus 1-Wire, chaque périphérique doit avoir une adresse unique pour que le maître puisse parler avec un périphérique en particulier. Pour connaitre cette adresse, il faut un scanneur. Ça tombe bien, c'est justement le sujet de l'article d'aujourd'hui.

Le montage

Avant d'étudier la partie code qui est la plus importante dans cet article. Arrêtons-nous un instant pour mettre en place un petit montage de test qui nous servira de base pour la suite.

Photographie du montage de test pour le scanneur 1-Wire Arduino

Le montage de test

Pour réaliser ce montage, il va nous falloir :

  • Une carte Arduino UNO (et son câble USB),

  • Une résistance de 4.7K ohms, code couleur jaune – violet – rouge,

  • Un ou plusieurs périphériques 1-Wire,

  • Une plaque d'essai et des fils pour câbler notre montage.

Schéma du montage de test du scanneur de bus 1-Wire Arduino.

Schéma du montage

L'avantage avec les bus 1-Wire est que le câblage est extrêmement simple.

Il suffit de relier ensemble la masse des périphériques 1-Wire (un simple capteur de température DS18B20 sur mon schéma ci-dessus) avec la masse GND de la carte Arduino. Il faut ensuite faire de même avec l'alimentation 5V de la carte Arduino et l'alimentation des périphériques 1-Wire.

Il ne reste alors plus qu'à relier la broche de données du bus 1-Wire à une broche de la carte Arduino. Pour cet article, j'utilise la broche D10 de la carte Arduino, mais vous pouvez utiliser n'importe quelle autre broche si vous le souhaitez.

Pour finir le montage, il convient de placer une résistance de 4.7K ohms entre l'alimentation 5V et la broche de données du bus 1-Wire.

N.B. La valeur de cette résistance est importante. Ce doit être une résistance de 4.7K ohms. Une résistance plus grande limitera le bon fonctionnement des périphériques 1-Wire et une résistance plus faible endommagera les périphériques 1-Wire.

Le code

Le but d'un scanner est assez trivial : chercher toutes les adresses disponibles sur le bus 1-Wire. Le protocole de communication 1-Wire intègre de base un système de recherche permettant de détecter tous les périphériques disponibles sur le bus. Faire un scanneur 1-Wire est donc relativement simple.

1
2
/* Dépendances */
#include <OneWire.h>

Pour faire notre scanneur, nous allons utiliser la bibliothèque de code OneWire. Cette bibliothèque de code permet de mettre en oeuvre des bus de communication 1-Wire facilement et sans prise de tête avec une carte Arduino.

1
2
/** Broche pour le bus 1-Wire */
const byte ONEWIRE_BUS_PIN = 10;

Comme d'habitude, le code commence avec la déclaration des constantes du programme.

Pour ce programme, il n'y a besoin que d'une seule constante : le numéro de broche pour le bus de données 1-Wire. J'utilise la broche D10 de la carte Arduino, comme vu dans le chapitre précédent.

1
2
/** L'object OneWire pour communiquer via le protocole 1-Wire sur la broche spécifiée */
OneWire ds(ONEWIRE_BUS_PIN);

Vient ensuite la déclaration de l'objet OneWire qui permet de gérer un bus 1-Wire sur la broche spécifiée.

PS Vous pouvez créer autant de bus 1-Wire que vous le souhaitez, mais pour notre scanner, un seul bus 1-Wire est suffisant. D'autant plus que plusieurs péripéhiques peuvent être câblés sur un même bus 1-Wire.

1
2
3
4
5
6
7
/** Fonction setup() */
void setup(void) {
  
  /* Initialise le port série */
  Serial.begin(115200);
  Serial.println(F("~~ Scanner 1-Wire ~~"));
}

La fonction setup() n'a pas grand-chose à faire aujourd'hui. Elle s'occupe seulement d'initialiser le port série de la carte Arduino pour pouvoir par la suite afficher les résultats de la recherche.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/** Fonction loop() */
void loop(void) {
  byte address[8];
  
  /* Cherche un module 1-Wire sur le bus */
  if (!ds.search(address)) {
    Serial.println(F("End of Scan."));
    ds.reset_search();
    for(;;);
  }
  
  /* Module 1-Wire découvert ! */
  Serial.print(F("Found "));
  for(byte i = 0; i < 8; ++i) {
    if (address[i] < 0x10) Serial.write('0');
    Serial.print(address[i], HEX);
    Serial.write(' ');
  }

  /* Vérifie si l'adresse est valide */
  if (OneWire::crc8(address, 7) != address[7]) {
      Serial.print(F("(CRC invalid)"));
  }
  
  /* Fin de ligne */
  Serial.println();
}

C'est la fonction loop() qui fait tout le travail.

  • Tous d'abord on déclare un tableau de 8 octets pour stocker une adresse 1-Wire.

  • Ensuite on utilise la fonction search() pour chercher le prochain périphérique 1-Wire disponible sur le bus. Si la fonction retourne zéro, c'est qu'il n'y a plus rien à chercher. On appelle dans ce cas la fonction reset_search() pour arrêter la recherche et on bloque le programme en affichant un petit message indiquant la fin de la recherche.

  • Après l'appel à la fonction search(), l'adresse du périphérique 1-Wire se trouve dans le tableau d'octets déclaré plus tôt. On utilise donc une boucle pour afficher l'adresse en hexadécimal, en ajoutant quelques espaces pour rendre l'adresse plus lisible.

  • On teste si l'adresse est valide en vérifiant que le dernier octet de l'adresse correspond bien à la somme de contrôle des octets précédent. En cas d'erreur on affiche un message d'avertissement.

  • Pour finir, on termine la ligne de texte avec un retour chariot.

Le code complet avec commentaires :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * Scanner 1-Wire (Dallas) pour cartes Arduino et compatible.
 */

/* Dépendances */
#include <OneWire.h>

/** Broche pour le bus 1-Wire */
const byte ONEWIRE_BUS_PIN = 10;

/** L'object OneWire pour communiquer via le protocole 1-Wire sur la broche spécifiée */
OneWire ds(ONEWIRE_BUS_PIN);

/** Fonction setup() */
void setup(void) {
  
  /* Initialise le port série */
  Serial.begin(115200);
  Serial.println(F("~~ Scanner 1-Wire ~~"));
}

/** Fonction loop() */
void loop(void) {
  byte address[8];
  
  /* Cherche un module 1-Wire sur le bus */
  if (!ds.search(address)) {
    Serial.println(F("End of Scan."));
    ds.reset_search();
    for(;;);
  }
  
  /* Module 1-Wire découvert ! */
  Serial.print(F("Found "));
  for(byte i = 0; i < 8; ++i) {
    if (address[i] < 0x10) Serial.write('0');
    Serial.print(address[i], HEX);
    Serial.write(' ');
  }

  /* Vérifie si l'adresse est valide */
  if (OneWire::crc8(address, 7) != address[7]) {
      Serial.print(F("(CRC invalid)"));
  }
  
  /* Fin de ligne */
  Serial.println();
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Voici un exemple de sorti dans le moniteur série avec trois capteurs de température DS18B20 connectés sur le bus 1-Wire :

1
2
3
4
5
~~ Scanner 1-Wire ~~
Found 28 9E 9C 1F 00 00 80 4 
Found 28 1D 9B 1F 00 00 80 E6 
Found 28 F 91 1F 00 00 80 6E 
End of Scan.

En testant chaque périphérique indépendamment, il est possible de connaitre l'adresse unique de chacun.

Bonus : Scanneur 1-Wire avec détection du type de périphérique

Le code du scanneur ci-dessus est simple et efficace, mais il lui manque une petite fonctionnalité bien sympathique : la détection du type de périphérique 1-Wire.

Chaque périphérique 1-Wire a une adresse unique dont le premier octet est un identifiant de "famille" de produit (40 pour les capteurs de température DS18B20 par exemple). Or parfois, on se retrouve avec des périphériques 1-Wire (en particulier les versions iButton en forme de capsule métallique) dont la référence est illisible.

J'ai donc fait le tour d'internet pour obtenir un dictionnaire des différentes familles de produit 1-Wire actuellement disponible. Le résultat de mes recherches est disponible sous la forme d'un fichier JSON contenant le numéro de famille, les références produites associées et une description rapide du type de produit.

Avec un peu de magie informatique, j'ai transformé ce fichier JSON en code Arduino :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/* Dictionnaire des modules 1-Wire connus */
const __FlashStringHelper * get_onewire_family_description(const byte * address) {
  switch(address[0]) {
    case 0: return F("(Link locator): Provide location information");
    case 1: return F("(DS2401, DS1990R, DS2411, DS2490A, DS1990A): 1-Wire serial number");
    case 2: return F("(DS1425, DS1991): Multikey iButton secure memory");
    case 4: return F("(DS2404, DS1994): Econoram NVRAM memory and clock, timer, alarms");
    case 5: return F("(DS2405): Single addressable switch");
    case 6: return F("(DS1993): 4Kb NVRAM memory");
    case 8: return F("(DS1992): 1Kb NVRAM memory");
    case 9: return F("(DS2704, DS2703, DS2502, DS1982): 1Kb EPROM memory");
    case 10: return F("(DS1995): 16Kb NVRAM memory");
    case 11: return F("(DS2505, DS1985): 16Kb EPROM memory");
    case 12: return F("(DS1996): 64Kb NVRAM memory");
    case 15: return F("(DS2506, DS1986): 64Kb EPROM memory");
    case 16: return F("(DS1920, DS18S20): High precision digital thermometer with alarm trips");
    case 18: return F("(DS2406, DS2407): 1Kb EPROM memory, two channel addressable switch");
    case 20: return F("(DS2430A, DS1971): 256-bit EEPROM memory and 64-bit OTP register");
    case 22: return F("(DS1954, DS1957): crypto-ibutton");
    case 24: return F("(DS1963S, DS1962): SHA iButton");
    case 26: return F("(DS1963L): 4Kb NVRAM memory with write cycle counters");
    case 27: return F("(DS2436): Battery monitor and ID");
    case 28: return F("(DS28E04-100): 4Kb EEPROM memory, two channel addressable switch");
    case 29: return F("(DS2423): 4Kb NVRAM memory with external counters");
    case 30: return F("(DS2437): Battery monitor");
    case 31: return F("(DS2409): Microhub, two channel addressable coupler for sub-netting");
    case 32: return F("(DS2450): Quad channel A/D converter");
    case 33: return F("(DS1921, DS1921H, DS1921Z, DS1921G): Thermochron temperature logger");
    case 34: return F("(DS1822, DS1922): Econo Digital Thermometer");
    case 35: return F("(DS1973, DS2433): 4Kb EEPROM memory");
    case 36: return F("(DS1904, DS2415): Real-time clock (RTC)");
    case 38: return F("(DS2438): Smart battery monitor");
    case 39: return F("(DS2417): Real-time clock (RTC) with interrupt");
    case 40: return F("(DS18B20): Programmable resolution digital thermometer");
    case 41: return F("(DS2408): 8-channel addressable switch");
    case 44: return F("(DS2890): 1-channel digital potentiometer");
    case 45: return F("(DS1972, DS2431): 1Kb EEPROM memory");
    case 46: return F("(DS2770): Battery monitor and charge controller");
    case 48: return F("(DS2760, DS2762, DS2761): High precision li+ battery monitor with alerts");
    case 49: return F("(DS2720): Single-cell rechargable lithium battery protection and ID");
    case 50: return F("(DS2780): Battery");
    case 51: return F("(DS1961S, DS2432): 1Kb protected EEPROM with SHA-1");
    case 52: return F("(DS2703): SHA-1 Battery");
    case 53: return F("(DS2755): Battery");
    case 54: return F("(DS2740): High precision Coulomb counter");
    case 55: return F("(DS1977): Password-protected 32KB (bytes) EEPROM");
    case 58: return F("(DS2413): Dual channel addressable switch");
    case 59: return F("(DS1825, MAX31826): Temperature and memory");
    case 61: return F("(DS2781): Battery");
    case 65: return F("(DS1922L, DS2422, DS1922T, DS1923): High-capacity Thermochron (temperature) and Hygrochron (humidity) loggers");
    case 66: return F("(DS28EA00): Programmable resolution digital thermometer with sequenced detection and PIO");
    case 67: return F("(DS28EC20): 20Kb EEPROM memory");
    case 68: return F("(DS28E10): SHA-1 Authenticator");
    case 81: return F("(DS2751): Battery monitor and level gauge");
    case 126: return F("(EDS00xx): Environmental Monitors");
    case 129: return F("(DS1420): Serial ID Button");
    case 130: return F("(DS1425): Authorization");
    case 132: return F("(DS2404S): Dual port plus time");
    case 137: return F("(DS2502-UNW, DS1982U, DS2502-E48): Uniqueware 48-bits node address chip");
    case 139: return F("(DS2505-UNW, DS1985U): Uniqueware 16Kb add-only memory");
    case 143: return F("(DS1986U, DS2506-UNW): Uniqueware 64k add-only memory");
    case 160: return F("(mRS001): Rotation Sensor");
    case 161: return F("(mVM001): Vibration");
    case 162: return F("(mCM001): AC Voltage");
    case 166: return F("(mTS017): IR Temperature");
    case 177: return F("(mTC001): Thermocouple Converter");
    case 178: return F("(mAM001): DC Current or Voltage");
    case 179: return F("(mTC002): Thermocouple Converter");
    case 238: return F("(UVI): Ultra Violet Index");
    case 239: return F("(Moisture Hub): Moisture meter, 4 channel hub");
    case 252: return F("(BAE0910, BAE0911): Programmable Microprocessor");
    case 255: return F("(LCD): Swart LCD (Swart)");
    default: return F("(unknown) : Unknown 1-Wire device");
  }
}

La fonction get_onewire_family_description() prend en paramètre l'adresse du périphérique et retourne une chaine de caractères contenant la description du périphérique et les références des produits associés.

Le code complet du scanneur V2 avec commentaires :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
 * Scanner 1-Wire (Dallas) pour cartes Arduino et compatible.
 */

/* Dépendances */
#include <OneWire.h>
#include <avr/pgmspace.h>

/** Broche pour le bus 1-Wire */
const byte ONEWIRE_BUS_PIN = 10;

/** L'object OneWire pour communiquer via le protocole 1-Wire sur la broche spécifiée */
OneWire ds(ONEWIRE_BUS_PIN);

/** Fonction setup() */
void setup(void) {
  
  /* Initialise le port série */
  Serial.begin(115200);
  Serial.println(F("~~ Scanner 1-Wire ~~"));
}

/** Fonction loop() */
void loop(void) {
  byte address[8];
  
  /* Cherche un module 1-Wire sur le bus */
  if (!ds.search(address)) {
    Serial.println(F("End of Scan."));
    ds.reset_search();
    for(;;);
  }
  
  /* Module 1-Wire découvert ! */
  Serial.print(F("Found "));
  for(byte i = 0; i < 8; ++i) {
    if (address[i] < 0x10) Serial.write('0');
    Serial.print(address[i], HEX);
    Serial.write(' ');
  }

  /* Vérifie si l'adresse est valide */
  if (OneWire::crc8(address, 7) != address[7]) {
      Serial.print(F("(CRC invalid)"));
  }
  
  /* Affiche les informations sur le module */
  Serial.println(get_onewire_family_description(address));
}

/* Dictionnaire des modules 1-Wire connus */
const __FlashStringHelper * get_onewire_family_description(const byte * address) {
  switch(address[0]) {
    case 0: return F("(Link locator): Provide location information");
    case 1: return F("(DS2401, DS1990R, DS2411, DS2490A, DS1990A): 1-Wire serial number");
    case 2: return F("(DS1425, DS1991): Multikey iButton secure memory");
    case 4: return F("(DS2404, DS1994): Econoram NVRAM memory and clock, timer, alarms");
    case 5: return F("(DS2405): Single addressable switch");
    case 6: return F("(DS1993): 4Kb NVRAM memory");
    case 8: return F("(DS1992): 1Kb NVRAM memory");
    case 9: return F("(DS2704, DS2703, DS2502, DS1982): 1Kb EPROM memory");
    case 10: return F("(DS1995): 16Kb NVRAM memory");
    case 11: return F("(DS2505, DS1985): 16Kb EPROM memory");
    case 12: return F("(DS1996): 64Kb NVRAM memory");
    case 15: return F("(DS2506, DS1986): 64Kb EPROM memory");
    case 16: return F("(DS1920, DS18S20): High precision digital thermometer with alarm trips");
    case 18: return F("(DS2406, DS2407): 1Kb EPROM memory, two channel addressable switch");
    case 20: return F("(DS2430A, DS1971): 256-bit EEPROM memory and 64-bit OTP register");
    case 22: return F("(DS1954, DS1957): crypto-ibutton");
    case 24: return F("(DS1963S, DS1962): SHA iButton");
    case 26: return F("(DS1963L): 4Kb NVRAM memory with write cycle counters");
    case 27: return F("(DS2436): Battery monitor and ID");
    case 28: return F("(DS28E04-100): 4Kb EEPROM memory, two channel addressable switch");
    case 29: return F("(DS2423): 4Kb NVRAM memory with external counters");
    case 30: return F("(DS2437): Battery monitor");
    case 31: return F("(DS2409): Microhub, two channel addressable coupler for sub-netting");
    case 32: return F("(DS2450): Quad channel A/D converter");
    case 33: return F("(DS1921, DS1921H, DS1921Z, DS1921G): Thermochron temperature logger");
    case 34: return F("(DS1822, DS1922): Econo Digital Thermometer");
    case 35: return F("(DS1973, DS2433): 4Kb EEPROM memory");
    case 36: return F("(DS1904, DS2415): Real-time clock (RTC)");
    case 38: return F("(DS2438): Smart battery monitor");
    case 39: return F("(DS2417): Real-time clock (RTC) with interrupt");
    case 40: return F("(DS18B20): Programmable resolution digital thermometer");
    case 41: return F("(DS2408): 8-channel addressable switch");
    case 44: return F("(DS2890): 1-channel digital potentiometer");
    case 45: return F("(DS1972, DS2431): 1Kb EEPROM memory");
    case 46: return F("(DS2770): Battery monitor and charge controller");
    case 48: return F("(DS2760, DS2762, DS2761): High precision li+ battery monitor with alerts");
    case 49: return F("(DS2720): Single-cell rechargable lithium battery protection and ID");
    case 50: return F("(DS2780): Battery");
    case 51: return F("(DS1961S, DS2432): 1Kb protected EEPROM with SHA-1");
    case 52: return F("(DS2703): SHA-1 Battery");
    case 53: return F("(DS2755): Battery");
    case 54: return F("(DS2740): High precision Coulomb counter");
    case 55: return F("(DS1977): Password-protected 32KB (bytes) EEPROM");
    case 58: return F("(DS2413): Dual channel addressable switch");
    case 59: return F("(DS1825, MAX31826): Temperature and memory");
    case 61: return F("(DS2781): Battery");
    case 65: return F("(DS1922L, DS2422, DS1922T, DS1923): High-capacity Thermochron (temperature) and Hygrochron (humidity) loggers");
    case 66: return F("(DS28EA00): Programmable resolution digital thermometer with sequenced detection and PIO");
    case 67: return F("(DS28EC20): 20Kb EEPROM memory");
    case 68: return F("(DS28E10): SHA-1 Authenticator");
    case 81: return F("(DS2751): Battery monitor and level gauge");
    case 126: return F("(EDS00xx): Environmental Monitors");
    case 129: return F("(DS1420): Serial ID Button");
    case 130: return F("(DS1425): Authorization");
    case 132: return F("(DS2404S): Dual port plus time");
    case 137: return F("(DS2502-UNW, DS1982U, DS2502-E48): Uniqueware 48-bits node address chip");
    case 139: return F("(DS2505-UNW, DS1985U): Uniqueware 16Kb add-only memory");
    case 143: return F("(DS1986U, DS2506-UNW): Uniqueware 64k add-only memory");
    case 160: return F("(mRS001): Rotation Sensor");
    case 161: return F("(mVM001): Vibration");
    case 162: return F("(mCM001): AC Voltage");
    case 166: return F("(mTS017): IR Temperature");
    case 177: return F("(mTC001): Thermocouple Converter");
    case 178: return F("(mAM001): DC Current or Voltage");
    case 179: return F("(mTC002): Thermocouple Converter");
    case 238: return F("(UVI): Ultra Violet Index");
    case 239: return F("(Moisture Hub): Moisture meter, 4 channel hub");
    case 252: return F("(BAE0910, BAE0911): Programmable Microprocessor");
    case 255: return F("(LCD): Swart LCD (Swart)");
    default: return F("(unknown) : Unknown 1-Wire device");
  }
}

L'extrait de code ci-dessus est disponible en téléchargement sur cette page (le lien de téléchargement en .zip contient le projet Arduino prêt à l'emploi).

Voici un exemple de sorti dans le moniteur série avec les trois mêmes capteurs de température DS18B20 que dans l'exemple précédent :

1
2
3
4
5
~~ Scanner 1-Wire ~~
Found 28 9E 9C 1F 00 00 80 4 (DS18B20): Programmable resolution digital thermometer
Found 28 1D 9B 1F 00 00 80 E6 (DS18B20): Programmable resolution digital thermometer
Found 28 F 91 1F 00 00 80 6E (DS18B20): Programmable resolution digital thermometer
End of Scan.

Conclusion

Ce projet est désormais terminé.

Si ce projet vous a plu, n'hésitez pas à le commenter sur le forum, à le partager sur les réseaux sociaux et à soutenir le site si cela vous fait plaisir.