-
Notifications
You must be signed in to change notification settings - Fork 50
/
discover-classify_fake_DS18B20.ino
1531 lines (1380 loc) · 49.4 KB
/
discover-classify_fake_DS18B20.ino
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright Chris Petrich, 2024
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* File: discover-classify_fake_DS18B20.ino
* Author: Chris Petrich
* Version: 9 Nov 2024
*
* Source: https://github.com/cpetrich/counterfeit_DS18B20/
* Documentation: https://github.com/cpetrich/counterfeit_DS18B20/
*
* This demonstration script performs tests on DS18B20 sensors to identify
* differences from Dallas / Maxim / Analog DS18B20+ sensors.
* It does not test the power-up state and it does not write to or test the EEPROM.
* Tests 0, 1, and 2 use only documented commands and are safe to execute.
* Test 3 sends undocumented commands and could conceivably mess up
* calibration parameters of clones.
*
* The sketch is INTENDED for EDUCATIONAL PURPOSES, only.
* There may be circumstances under which the sketch permanently damages one-wire
* sensors in obvious or non-obvious ways.
* (I don't think it does that to authentic sensors, but I won't guarantee
* anything. See licence text for details.)
*
*
* This sketch was designed for Arduino Uno. Wiring:
*
* CPU Vcc -------------- DS18B20 Vcc
*
* CPU Vcc -------\
* |
* [R] <- choose resistor appropriate for supply voltage and current that the microcontroller is able to sink.
* |
* CPU pin_onewire -------o------ DS18B20 data
*
* CPU GND -------------- DS18B20 GND
*
*/
// Tested with OneWire Version 2.3
// https://github.com/PaulStoffregen/OneWire
#include "OneWire.h"
#define pin_onewire 7
#define pin_LED 13
#define Comm Serial
const int ms750 = 750;
OneWire *ds;
void print_hex(uint8_t value) {
if (value < 16) Comm.write('0');
Comm.print(value, HEX);
}
void print_array(uint8_t *data, int n, char sep = ',') {
int idx;
for (idx=0; idx<n; idx++) {
print_hex(data[idx]);
if (idx != n-1)
Comm.write(sep);
}
}
void dump_w1_address_format(uint8_t *addr) {
// output address in w1 subsystem format, i.e. without CRC
print_hex(addr[0]);
Comm.print(F("-"));
for (int i=6; i>0; i--)
print_hex(addr[i]);
}
bool read_scratchpad(uint8_t *addr, uint8_t *buff9) {
ds->reset();
ds->select(addr);
ds->write(0xBE); // read scratchpad
int idx;
for (idx=0; idx<9; idx++)
buff9[idx] = ds->read();
return 0 == OneWire::crc8(buff9, 9);
}
void setup() {
Comm.begin(115200);
digitalWrite(pin_LED, HIGH);
pinMode(pin_LED, OUTPUT);
ds = new OneWire(pin_onewire);
{
// output file name without leading path
char file[] = __FILE__;
int i;
for (i = strlen(file); i > 0; i--)
if ((file[i] == '\\') || (file[i] == '/')) {
i++;
break;
}
Comm.print(F("\n--- # "));
Comm.println(&file[i]);
}
digitalWrite(pin_LED, LOW);
Comm.println(F("This is the November 2024 version of discover-classify_fake_DS18B20."));
Comm.println(F(" We are in a game of whack-a-mole. At least six new clones have"));
Comm.println(F(" appeared on the market between 2019 and 2024. Please consider"));
Comm.println(F(" reporting suspicious test results to help keep the sketch current."));
Comm.println(F("This sketch will test DS18B20 sensors attached to"));
Comm.print(F(" pin "));
Comm.print(pin_onewire, DEC);
Comm.println(F(" for differences with the DS18B20 produced by"));
Comm.println(F(" Analog Devices / Maxim Integrated / Dallas Semiconductor."));
Comm.println(F(" Details: https://github.com/cpetrich/counterfeit_DS18B20"));
Comm.println();
}
void loop() {
Comm.println();
Comm.print(F("Test DS18B20 sensors attached to"));
Comm.print(F(" pin "));
Comm.print(pin_onewire, DEC);
Comm.println(F("."));
Comm.println(F("Select the test:"));
Comm.println(F(" 0. Enumerate attached sensors."));
Comm.println();
Comm.println(F(" 1. Check temperature alarm function with a safe test according to datasheet."));
Comm.println();
Comm.println(F(" 2. Discover clones with a safe test, using only function codes documented in the datasheet."));
Comm.println(F(" This may be sufficient to detect clones."));
Comm.println();
Comm.println(F(" 3. Classify clones with an agressive test, using undocumented function codes."));
Comm.println(F(" This could conceivably mess up calibration parameters. Only use this on sensors"));
Comm.println(F(" dedicated to testing."));
Comm.println();
Comm.print(F("Your choice> "));
Comm.flush();
while (Comm.available())
Comm.read();
for (;;) {
uint32_t start = millis();
while ((!Comm.available()) && (millis()-start < 1000))
delay(50);
digitalWrite(pin_LED, digitalRead(pin_LED) == HIGH ? LOW : HIGH);
if (Comm.available()) {
int select = Comm.read();
Comm.write(select);
Comm.write('\n');
Comm.write('\n');
Comm.flush();
if (select == '0') return loop_enumerate();
if (select == '1') return loop_test_alarm();
if (select == '2') return loop_discover();
if (select == '3') return loop_classify();
delay(150); // let read buffer fill
Comm.println(F("Unknown choice."));
return;
}
}
}
void loop_enumerate() {
uint8_t addr[8];
uint8_t buffer[9];
{
Comm.println(F("Sensor ROM and Current Scratchpad Content:"));
int count = 0;
ds->reset_search();
while (ds->search(addr)) {
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
count ++;
Comm.print(F(" "));
if (count < 10)
Comm.write(' ');
Comm.print(count, DEC);
Comm.print(F(". "));
print_array(addr, 8, '-');
Comm.print(F(", "));
dump_w1_address_format(addr);
{
ds->reset();
ds->select(addr);
ds->write(0xB4);
uint8_t normal_power = ds->read_bit();
if (!normal_power) {
Comm.print(F(" (Parasite Power Mode)"));
}
}
Comm.print(F(": "));
print_array(buffer, 9, '/');
Comm.print(F(" "));
float T = (int16_t)((uint16_t)buffer[0] + 256 * (uint16_t)buffer[1]) / 16.0f;
Comm.print(T);
Comm.println(F(" oC"));
}
Comm.print(F(" Number of Sensors: "));
Comm.print(count, DEC);
Comm.println(F(".\n"));
}
Comm.println(F("-------------")); // indicate end
}
void loop_test_alarm() {
const int long_wait_ms = ms750;
uint8_t addr[8];
uint8_t buffer[9];
int16_t current_min_T, current_max_T; // stores only H-byte
const int16_t window_T = 3; // allowance for temperature change. Hast to be at least 1 if resolution > 9 bit.
int attached_sensor_count = 0;
bool detected_alarm_implementation_error = false;
// we set the alarm thresholds based on the current temperature
// rather than using hard-coded values to be able to verify if
// the chips actually do a comparisons.
// For this test the temperatures need to be reasonably constant.
//
// As of 2024, I am not aware of sensors that fail this test.
{
Comm.println(F("1. Trigger Temperature Conversion of All Sensors"));
ds->reset();
ds->write(0xCC); // skip ROM ==> all sensors
ds->write(0x44, 1); // perform temperature conversion
delay(long_wait_ms);
ds->depower();
}
{
Comm.println(F("2. Find Current Temperature Range"));
current_min_T = (int8_t)0x7F;
current_max_T = (int8_t)0x80;
int count = 0;
ds->reset_search();
while (ds->search(addr)) {
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
current_min_T = min(current_min_T, (int16_t)(buffer[1]*16 + buffer[0] / 16));
current_max_T = max(current_max_T, (int16_t)(buffer[1]*16 + buffer[0] / 16));
Comm.print(F(" "));
print_array(addr, 8, '-');
Comm.print(F(": "));
print_array(buffer, 9, '/'); // dump scratchpad
Comm.print(F(" "));
float T = (int16_t)(buffer[0] + 256 * buffer[1]) / 16.0f;
Comm.print(T);
Comm.print(F(" oC"));
Comm.println();
count ++;
}
attached_sensor_count = count;
Comm.print(F(" Number of Sensors: "));
Comm.print(attached_sensor_count, DEC);
Comm.println(F(".\n"));
}
{
Comm.println(F("3. Set Alarm Registers to Trigger High Temperature Alarm"));
// trigger high temperature alarm
uint8_t alarm_H = ((current_min_T - window_T) & 0xFF);
uint8_t alarm_L = alarm_H - 1;
int count = 0;
ds->reset_search();
while (ds->search(addr)) {
ds->reset();
ds->select(addr);
ds->write(0x4E);
ds->write(alarm_H);
ds->write(alarm_L);
ds->write(0x7F); // 12-bit conversion
Comm.print(F(" "));
print_array(addr, 8, '-');
Comm.print(F(": "));
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
print_array(&buffer[2], 3, '/');
if ((buffer[2] == alarm_H) && (buffer[3] == alarm_L) && (buffer[4] == 0x7F))
Comm.println(F(" Ok."));
else
Comm.println(F(" Unsuccessful Write. Error."));
count ++;
}
if (count != attached_sensor_count)
Comm.println(F(" Inconsistent Sensor Count, Check Connections!\n"));
}
{
Comm.println(F("4. Trigger Temperature Conversion of All Sensors"));
ds->reset();
ds->write(0xCC); // skip ROM ==> all sensors
ds->write(0x44, 1); // perform temperature conversion
delay(long_wait_ms);
ds->depower();
}
{
Comm.println(F("5. Find Sensors Signalling Alarm"));
Comm.println(F(" (this should be all sensors)"));
int count = 0;
ds->reset_search();
while (ds->search(addr, 0)) {
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
Comm.print(F(" "));
print_array(addr, 8, '-');
Comm.print(F(": "));
print_array(buffer, 9, '/'); // dump scratchpad
Comm.print(F(" "));
float T = (int16_t)(buffer[0] + 256 * buffer[1]) / 16.0f;
Comm.print(T);
Comm.println(F(" oC"));
count++;
}
Comm.print(F(" Number of Sensors: "));
Comm.print(count, DEC);
Comm.print(F("."));
if (count == attached_sensor_count)
Comm.println(F(" Ok."));
else if (count < attached_sensor_count) {
Comm.println(F(" ** Not all sensors raised alarm!!! **"));
detected_alarm_implementation_error = true;
}
else if (count > attached_sensor_count)
Comm.println(F(" Inconsistent sensor count, check connections!"));
Comm.println();
}
{
Comm.println(F("6. Set Alarm Registers to Trigger Low Temperature Alarm"));
// trigger low temperature alarm
uint8_t alarm_L = ((current_max_T + window_T) & 0xFF);
uint8_t alarm_H = alarm_L + 1;
int count = 0;
ds->reset_search();
while (ds->search(addr)) {
ds->reset();
ds->select(addr);
ds->write(0x4E);
ds->write(alarm_H);
ds->write(alarm_L);
ds->write(0x7F); // 12-bit conversion
Comm.print(F(" "));
print_array(addr, 8, '-');
Comm.print(F(": "));
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
print_array(&buffer[2], 3, '/');
if ((buffer[2] == alarm_H) && (buffer[3] == alarm_L) && (buffer[4] == 0x7F))
Comm.println(F(" Ok."));
else
Comm.println(F(" Unsuccessful Write. Error."));
count ++;
}
if (count != attached_sensor_count)
Comm.println(F(" Inconsistent Sensor Count, Check Connections!\n"));
}
{
Comm.println(F("7. Trigger Temperature Conversion of All Sensors"));
ds->reset();
ds->write(0xCC); // skip ROM ==> all sensors
ds->write(0x44, 1); // perform temperature conversion
delay(long_wait_ms);
ds->depower();
}
{
Comm.println(F("8. Find Sensors Signalling Alarm"));
Comm.println(F(" (this should be all sensors)"));
int count = 0;
ds->reset_search();
while (ds->search(addr, 0)) {
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
Comm.print(F(" "));
print_array(addr, 8, '-');
Comm.print(F(": "));
print_array(buffer, 9, '/'); // dump scratchpad
Comm.print(F(" "));
float T = (int16_t)(buffer[0] + 256 * buffer[1]) / 16.0f;
Comm.print(T);
Comm.println(F(" oC"));
count++;
}
Comm.print(F(" Number of Sensors: "));
Comm.print(count, DEC);
Comm.print(F("."));
if (count == attached_sensor_count)
Comm.println(F(" Ok."));
else if (count < attached_sensor_count) {
Comm.println(F(" ** Not all sensors raised alarm!!! **"));
detected_alarm_implementation_error = true;
}
else if (count > attached_sensor_count)
Comm.println(F(" Inconsistent sensor count, check connections!"));
Comm.println();
}
{
Comm.println(F("9. Set Alarm Registers to Not Trigger"));
uint8_t alarm_L = ((current_min_T - window_T) & 0xFF);
uint8_t alarm_H = ((current_max_T + window_T) & 0xFF);
ds->reset_search();
while (ds->search(addr)) {
ds->reset();
ds->select(addr);
ds->write(0x4E);
ds->write(alarm_H);
ds->write(alarm_L);
ds->write(0x7F); // 12-bit conversion
Comm.print(F(" "));
print_array(addr, 8, '-');
Comm.print(F(": "));
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
print_array(&buffer[2], 3, '/');
if ((buffer[2] == alarm_H) && (buffer[3] == alarm_L) && (buffer[4] == 0x7F))
Comm.println(F(" Ok."));
else
Comm.println(F(" Unsuccessful Write. Error."));
}
Comm.println();
}
{
Comm.println(F("10. Trigger Temperature Conversion of All Sensors"));
ds->reset();
ds->write(0xCC); // skip ROM ==> all sensors
ds->write(0x44, 1); // perform temperature conversion
delay(long_wait_ms);
ds->depower();
}
{
Comm.println(F("11. Find Sensors Signalling Alarm"));
Comm.println(F(" (this should be no sensors)"));
int count = 0;
ds->reset_search();
while (ds->search(addr, 0)) {
if (!read_scratchpad(addr, buffer)) read_scratchpad(addr, buffer);
Comm.print(F(" "));
print_array(addr, 8, '-');
Comm.print(F(": "));
print_array(buffer, 9, '/'); // dump scratchpad
Comm.print(F(" "));
float T = (int16_t)(buffer[0] + 256 * buffer[1]) / 16.0f;
Comm.print(T);
Comm.println(F(" oC"));
count++;
}
Comm.print(F(" Number of Sensors: "));
Comm.print(count, DEC);
Comm.print(F("."));
if (count == 0)
Comm.println(F(" Ok."));
else {
Comm.println(F(" ** Sensors raised alarm even though they should not!!! **"));
detected_alarm_implementation_error = true;
}
Comm.println();
}
{
Comm.println(F("12. Request All Sensors to Recall EEPROM"));
ds->reset();
ds->write(0xCC); // skip ROM ==> all sensors
ds->write(0xB8); // recall alarm register and config from EEPROM
delay(10);
}
Comm.println();
if (detected_alarm_implementation_error) {
Comm.println(F("** Test Revealed Alarm Implementation Errors **"));
Comm.println(F(" (assuming sensor temperatures did not change too much)"));
} else
Comm.println(F("No Alarm Implementation Errors Found"));
Comm.println();
Comm.println(F("-------------")); // indicate end
}
void loop_discover() { // this is the safe choice
// ROM address of current sensor
uint8_t addr[8];
// buffers for scratchpad register
uint8_t buffer0[9];
uint8_t buffer1[9];
uint8_t buffer2[9];
uint8_t buffer3[9];
// flag to indicate if validation
// should be repeated at a different
// sensor temperature
bool t_ok;
ds->reset_search();
while (ds->search(addr)) {
int fake_flags = 0;
print_array(addr, 8, '-');
if (0 != OneWire::crc8(addr, 8)) {
// some clones can have their ROM overwritten with
// arbitrary nonsense, so we don't expect anything good
// if the ROM doesn't check out
fake_flags += 1;
Comm.print(F(" (CRC Error -> Error.)"));
}
if ((addr[6] != 0) || (addr[5] != 0) || (addr[0] != 0x28)) {
// as of 2024: catches all families but A1 and A3
fake_flags += 1;
Comm.print(F(": ROM does not follow expected pattern 28-xx-xx-xx-xx-00-00-crc. Error."));
} else if ((addr[6] == 0) && (addr[5] == 0) && (addr[4] == 0) && (addr[0] == 0x28)) {
// catches Family A3
fake_flags += 1;
Comm.print(F(": ROM pattern pre-dates C4 die version, suggesting the chip is either"));
Comm.print(F(" over 15 years old or a clone.\n Assuming sensor is a clone. Error."));
} else {
Comm.print(F(": ROM ok."));
}
Comm.println();
if (!read_scratchpad(addr, buffer0)) read_scratchpad(addr, buffer0);
Comm.print(F(" Scratchpad Register: "));
print_array(buffer0, 9, '/');
if (0 != OneWire::crc8(buffer0, 9)) {
// Unlikely that a sensor will mess up the CRC of the scratchpad.
// --> Assume we're dealing with a bad connection rather than a bad
// sensor, dump data, and move on to next sensor.
Comm.println(F(" CRC Error. Check connections or replace sensor."));
continue;
}
Comm.println();
{
// query sensor whether it believes to be in parasite power mode
ds->reset();
ds->select(addr);
ds->write(0xB4);
uint8_t normal_power = ds->read_bit();
Comm.print(F(" Info only: "));
if (normal_power) {
// this is good because it means we can perform timing measurements toward the end
Comm.println(F("Sensor is not reporting Parasite Power Mode. Ok."));
} else {
Comm.println(F("Sensor is reporting to be in Parasite Power Mode."));
Comm.println(F(" Note that some parts of this need the sensor"));
Comm.println(F(" to not be in Parasite Power Mode. Hence:"));
Comm.println(F(" ** THE RESULT OF THIS TEST IS INVALID **"));
}
}
// Check content of values loaded from EEPROM. Since the EEPROM may have been
// programmed by the user earlier we do not use this as a test. Rather, we dump this as info.
Comm.print(F(" Info only: Scratchpad bytes 2,3,4 ("));
print_array(buffer0+2,3,'/');
Comm.print(F("):"));
if ((buffer0[2] != 0x4b) || (buffer0[3] != 0x46) || (buffer0[4] != 0x7f))
Comm.println(F(" not DS18B20 default values 4B/46/7F."));
else
Comm.println(F(" DS18B20 default values."));
Comm.print(F(" Scratchpad <byte 5> = 0x"));
print_hex(buffer0[5]);
Comm.print(F(":"));
if (buffer0[5] != 0xff) {
// potentially catches Families B1 (but not B1v2), B2, D1, and D2
fake_flags += 1;
Comm.println(F(" should have been 0xFF according to datasheet. Error."));
} else {
Comm.println(F(" ok."));
}
Comm.print(F(" Scratchpad <byte 6> = 0x"));
print_hex(buffer0[6]);
Comm.print(F(":"));
if ( ((buffer0[6] == 0x00) || (buffer0[6] > 0x10)) || // totall wrong value
( ((buffer0[0] != 0x50) || (buffer0[1] != 0x05)) && ((buffer0[0] != 0xff) || (buffer0[1] != 0x07)) && // check for valid conversion...
(buffer0[6] != (0x10 - (buffer0[0] & 0x0f))) ) ) { //...before assessing DS18S20 compatibility.
// this will typically catch Families B1 (but not B1v2), B2, C, D1, D2, F, G after a temperature conversion
fake_flags += 1;
Comm.println(F(" unexpected value. Error."));
} else
Comm.println(F(" ok."));
Comm.print(F(" Scratchpad <byte 7> = 0x"));
print_hex(buffer0[7]);
Comm.print(F(":"));
if (buffer0[7] != 0x10) {
// this may catch Family B1, B2, D1, D2 (but not B1v2)
fake_flags += 1;
Comm.println(F(" should have been 0x10 according to datasheet. Error."));
} else {
Comm.println(F(" ok."));
}
{
// send 5 bytes into scratchpad and read back
ds->reset();
ds->select(addr);
ds->write(0x4E); // write scratchpad. MUST be followed by 3 bytes as per datasheet.
ds->write(buffer0[2]);
ds->write(buffer0[3]);
ds->write(0x7F);
ds->write(0x11);
ds->write(0x22);
ds->reset();
if (!read_scratchpad(addr, buffer1)) read_scratchpad(addr, buffer1);
Comm.print(F(" Last three bytes of scratchpad after sending 5 bytes: "));
print_array(&buffer1[5],3,'/');
// bytes 5 and 7 should be fixed default values, and byte 6 should not have changed.
if ((buffer1[5] != 0xFF) || (buffer1[6] != buffer0[6]) || (buffer1[7] != 0x10)) {
// catching Families B1 (but not B1v2) and B2
fake_flags += 1;
Comm.println(F(" unexpected (expected FF/<unchanged>/10). Error."));
} else {
Comm.println(F(" ok."));
}
}
{
ds->reset();
ds->select(addr);
ds->write(0x4E); // write scratchpad. MUST be followed by 3 bytes as per datasheet.
ds->write(buffer0[2]);
ds->write(buffer0[3]);
ds->write(0x00);
ds->reset();
if (!read_scratchpad(addr, buffer1)) read_scratchpad(addr, buffer1);
Comm.print(F(" Scratchpad config register <byte 4> after writing 0x00: 0x"));
print_hex(buffer1[4]);
if (buffer1[4] != 0x1F) {
// catches Families C and F
fake_flags += 1;
Comm.println(F(" unexpected (expected 1F). Error."));
} else {
Comm.println(F(" ok."));
}
ds->reset();
ds->select(addr);
ds->write(0x4E); // write scratchpad. MUST be followed by 3 bytes as per datasheet.
ds->write(buffer0[2]);
ds->write(buffer0[3]);
ds->write(0xFF);
ds->reset();
if (!read_scratchpad(addr, buffer1)) read_scratchpad(addr, buffer1);
Comm.print(F(" Scratchpad config register <byte 4> after writing 0xFF: 0x"));
print_hex(buffer1[4]);
if (buffer1[4] != 0x7F) {
// catches Family F
fake_flags += 1;
Comm.println(F(" unexpected (expected 7F). Error."));
} else {
Comm.println(F(" ok."));
}
}
// set the resolution to 10 bit and modify alarm registers
ds->reset();
ds->select(addr);
ds->write(0x4E); // write scratchpad. MUST be followed by 3 bytes as per datasheet.
ds->write(buffer0[2] ^ 0xff);
ds->write(buffer0[3] ^ 0xff);
ds->write(0x3F);
ds->reset();
if (!read_scratchpad(addr, buffer1)) read_scratchpad(addr, buffer1);
Comm.print(F(" 0x4E modifies alarm registers:"));
if ((buffer1[2] != (buffer0[2] ^ 0xff)) || (buffer1[3] != (buffer0[3] ^ 0xff))) {
fake_flags += 1;
Comm.print(F(" cannot modify content as expected (want: "));
print_hex(buffer0[2] ^ 0xff);
Comm.write('/');
print_hex(buffer0[3] ^ 0xff);
Comm.print(F(", got: "));
print_array(buffer1+2, 2, '/');
Comm.println(F("). Error."));
} else
Comm.println(F(" ok."));
Comm.print(F(" 0x4E accepts 10 bit resolution:"));
if (buffer1[4] != 0x3f) {
// catches Families C, F
fake_flags += 1;
Comm.print(F(" rejected (expected: 0x3F, got: 0x"));
print_hex(buffer1[4]);
Comm.println(F("). Error."));
} else
Comm.println(F(" ok."));
Comm.print(F(" 0x4E preserves reserved bytes:"));
if ((buffer1[5] != buffer0[5]) || (buffer1[6] != buffer0[6]) || (buffer1[7] != buffer0[7])) {
// this may catch Family B1, B2, D1, D2 (but not B1v2)
fake_flags += 1;
Comm.print(F(" no, got: "));
print_array(buffer1+5, 3, '/');
Comm.println(F(". Error."));
} else
Comm.println(F(" ok."));
// set the resolution to 12 bit
ds->reset();
ds->select(addr);
ds->write(0x4E); // write scratchpad. MUST be followed by 3 bytes as per datasheet.
ds->write(buffer0[2]);
ds->write(buffer0[3]);
ds->write(0x7f);
ds->reset();
if (!read_scratchpad(addr, buffer2)) read_scratchpad(addr, buffer2);
Comm.print(F(" 0x4E accepts 12 bit resolution:"));
if (buffer2[4] != 0x7f) {
// no sensor should fail this
fake_flags += 1;
Comm.print(F(" rejected (expected: 0x7F, got: 0x"));
print_hex(buffer2[4]);
Comm.println(F("). Error."));
} else
Comm.println(F(" ok."));
Comm.print(F(" 0x4E preserves reserved bytes:"));
if ((buffer2[5] != buffer1[5]) || (buffer2[6] != buffer1[6]) || (buffer2[7] != buffer1[7])) {
// again, Familes B1 and B2 would fail this
fake_flags += 1;
Comm.print(F(" no, got: "));
print_array(buffer2+5, 3, '/');
Comm.println(F(". Error."));
} else
Comm.println(F(" ok."));
Comm.print(F(" Checking <byte 6> upon temperature change:"));
{
// We'll do a few temperature conversions in a row.
// Usually, the temperature rises slightly if we do back-to-back
// conversions, so we can check <byte 6> at different temperatures.
int count = 5;
do {
count -- ;
if (count < 0)
break;
// perform temperature conversion
ds->reset();
ds->select(addr);
ds->write(0x44, 1); // keep line high in case of parasite power
delay(ms750);
if (!read_scratchpad(addr, buffer3)) read_scratchpad(addr, buffer3);
} while ( ((buffer3[0] == 0x50) && (buffer3[1] == 0x05)) || ((buffer3[0] == 0xff) && (buffer3[1] == 0x07)) ||
((buffer3[6] == 0x0c) && (((buffer3[0] + buffer3[6]) & 0x0f) == 0x00)) );
if (count < 0) {
Comm.println(F(" Inconclusive. Please change sensor temperature and repeat."));
t_ok = false;
} else {
t_ok = true;
const uint8_t is_value = buffer3[6];
const uint8_t expect_value = 0x10 - (buffer3[0] & 0x0F);
if ((buffer3[6] != 0x0c) && (is_value == expect_value)) {
Comm.println(F(" ok."));
} else {
fake_flags += 1;
Comm.print(F(" <byte 0> = 0x"));
print_hex(buffer3[0]);
Comm.print(F(" but <byte 6> = 0x"));
print_hex(buffer3[6]);
Comm.print(F(" rather than 0x"));
print_hex(expect_value);
Comm.println(F(". Error."));
}
}
{
Comm.print(F(" Info only: Temperature reading is "));
float T_C = (int16_t)((uint16_t)buffer3[0] + 256*(uint16_t)buffer3[1]) / 16.0f;
Comm.print(T_C);
Comm.println(F(" oC."));
}
}
Comm.print(F(" Testing if end of temperature conversion can be polled: "));
bool reports_conversion_progress = true;
{
const int short_ms = 3;
// test if sensor reports conversion completion
// test if sensor returns LOW after 3 ms, and HIGH after 750 ms.
// perform temperature conversion
ds->reset();
ds->select(addr);
ds->write(0x44);
// we assume that we won't see a sensor that completes conversion in less than short_ms.
delay(short_ms);
const uint8_t after_short = ds->read_bit();
delay(ms750 - short_ms);
const uint8_t after_750ms = ds->read_bit();
if ((after_short == 0) && (after_750ms != 0)) {
Comm.println(F("ok."));
} else {
// this catches Family F
fake_flags += 1;
Comm.print(F("\n "));
Comm.print(F("Read "));
Comm.print(after_short ? F("HIGH ") : F("LOW "));
Comm.print(F("after "));
Comm.print(short_ms, DEC);
Comm.print(F(" ms (expected LOW), "));
Comm.print(F("read "));
Comm.print(after_750ms ? F("HIGH ") : F("LOW "));
Comm.print(F("after "));
Comm.print(ms750, DEC);
Comm.println(F(" ms (expected HIGH). Error."));
reports_conversion_progress = false;
}
}
Comm.print(F(" Polling status valid immediately: "));
if (reports_conversion_progress) {
// test if sensor reports conversion completion without delay
const int n_samples = 4;
uint8_t result[n_samples];
uint8_t ok_count = 0;
for (int i=0; i<n_samples; i++) {
ds->reset();
ds->select(addr);
ds->write(0x44);
result[i] = ds->read_bit();
if (result[i] == 0)
ok_count ++;
delay(ms750); // let conversion finish
}
if (ok_count == sizeof result) {
Comm.println(F("ok."));
} else {
// catches Family A3
fake_flags += 1;
Comm.print(F("Sensor failed to signal immediately "));
Comm.print(sizeof result - ok_count, DEC);
Comm.print(F(" out of "));
Comm.print(sizeof result, DEC);
Comm.println(F(" times. Error."));
}
} else {
fake_flags ++;
Comm.println(F("test not possible. Error."));
}
Comm.print(F(" Conversion speed at different resolutions: "));
if (reports_conversion_progress) {
uint8_t bit_set_ok = 0;
uint32_t conv_time[4];
for (int idx = 0; idx < 4; idx++) {
const int timeout_ms = 3000;
ds->reset();
ds->select(addr);
ds->write(0x4E);
ds->write(buffer0[2]);
ds->write(buffer0[3]);
ds->write(idx * 32 + 0x1f);
delay(1);
// check for set resolution
if (!read_scratchpad(addr, buffer2)) read_scratchpad(addr, buffer2);
const int true_idx = buffer2[4] / 32;
if (true_idx == idx)
bit_set_ok += 1;
Comm.print(true_idx+9, DEC);
Comm.print(F("-bit: "));
ds->reset();
ds->select(addr);
ds->write(0x44); // start conversion
uint32_t start = millis();
delay(1); // wait a bit in case the sensor is just slow to pull down the line (e.g. Family A3)
while ((ds->read_bit() == 0) && (millis()-start < timeout_ms)); // wait for "done" signal
conv_time[idx] = millis() - start;
if (conv_time[idx] < 3000) {
Comm.print(conv_time[idx], DEC);
Comm.print(F(" ms"));
} else {
Comm.print(F("(timeout)"));
}
if (idx != 3)
Comm.print(F(", "));
// one could check if the returned temperature is actually of reduced discretization
}
Comm.print(F(".\n "));
bool div_resolution = true;
if (bit_set_ok != 4) {
// catches Families C, F
fake_flags ++;
Comm.print(F("Could not change resolution. Error.\n "));
div_resolution = false;
} else {
Comm.print(F("Changing resolution possible. Ok.\n "));
}
if (div_resolution) {
float r1 = (float)conv_time[1] / (float)conv_time[0];
float r2 = (float)conv_time[2] / (float)conv_time[1];
float r3 = (float)conv_time[3] / (float)conv_time[2];
float r_avg = (r1+r2+r3)/3.0f;
if (fabs(r_avg-2.0f) < 0.3f) {
Comm.print(F("Conversion time doubles with each bit in resolution. Ok.\n "));
} else if (fabs(r_avg-1.41f) < 0.2f) { // test for sqrt(2)
// catches Family H
fake_flags ++;
Comm.print(F("Conversion time increases by factor 1.41 with each bit in resolution. Error.\n "));
} else if (fabs(r_avg-1.0f) < 0.15f) {
// catches Families C, D1, D2, E, F.
fake_flags ++;
Comm.print(F("Conversion time independent of resolution. Error.\n "));
} else {
fake_flags ++;
Comm.print(F("Conversion time does not scale as expected with resolution. Error.\n "));
}
}
if (conv_time[3] < 550) {
// catches Families A2, C, D1, D2, E, G
fake_flags ++;
Comm.println(F("12-bit conversion faster than expected. Error."));
} else if (conv_time[3] > 660) {
// some sensors of Family B1, B1v2, and B2 may fail this test
fake_flags ++;
Comm.println(F("12-bit conversion slower than expected. Error."));
} else {
// NB: the time bounds are established empirically and are neither documented nor guaranteed.
// note that we're not testing whether this actually was 12 bit
Comm.println(F("12-bit conversion time as expected. Ok."));
}
} else {
fake_flags ++;
Comm.println(F("test not possible. Error."));
}
Comm.print(F(" --> "));
if (!t_ok) {
Comm.print(F("Temperature test not completed, otherwise sensor"));
} else
Comm.print(F("Sensor"));
if (fake_flags == 0) {
Comm.println(F(" responded like a genuine DS18B20."));
Comm.println(F(" Not tested: EEPROM, Parasite Power, and undocumented commands."));
} else {
Comm.print(F(" is not a genuine DS18B20 based on "));
Comm.print(fake_flags, DEC);
Comm.println(F(" deviations."));
}
Comm.println();
} // done with all sensors
Comm.println(F("------------------------------------------------"));
}
// ****************************************************************************
int16_t time_conversion(uint8_t *addr) {
const uint32_t timeout_ms = 3000;
ds->reset();
ds->select(addr);
ds->write(0x44); // start conversion