forked from cpetrich/counterfeit_DS18B20
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclassify_fake_DS18B20.ino
414 lines (363 loc) · 12.6 KB
/
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
/*
* Copyright 2020 Chris Petrich
*
* 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: classify_fake_DS18B20.ino
* Author: Chris Petrich
* Version: 25 May 2020
*
* Source: https://github.com/cpetrich/counterfeit_DS18B20/
* Documentation: https://github.com/cpetrich/counterfeit_DS18B20/
*
* This demonstration script attempts to classify DS18B20 clones
* assuming the chip belongs to one of the Families detailed on
* https://github.com/cpetrich/counterfeit_DS18B20/
* MAX31820 sensors cannot be distinguished from DS18B20 in software.
*
* This script evaluates the response to UNDOCUMENTED FUNCTION CODES.
* If this script is used on DS18B20 clones that do not belong to one
* of the Families listed on
* https://github.com/cpetrich/counterfeit_DS18B20/
* then write operations could be triggered inadvertently that may
* mess up calibration constants and render the part useless -- or
* produce permanent damage in other ways.
*
* Disclaimer: This code sends undocumented function codes to 1-wire
* chips and may therefore permanently damage the part attached.
* USE AT YOUR OWN RISK AND FOR YOUR OWN SAKE DO NOT USE THIS SCRIPT
* ON CHIPS INTENDED FOR DEPLOYMENT OR SALE!
*
*
* This script is written for Arduino. Wiring:
*
* CPU Vcc or GND -------------- 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
*
*
* Note: Always attach the power pin of the DS18B20 to either GND or Vcc unless it is a DS18B20-PAR.
*
*/
// Tested with OneWire Version 2.3
// https://github.com/PaulStoffregen/OneWire
#include "OneWire.h"
#define pin_onewire 7
#define Comm Serial
OneWire *ds;
volatile uint64_t timer_us;
volatile uint64_t timer_interval_us;
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);
}
}
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);
}
int lengthFF(uint8_t *buff, int len) {
// RIGHT-STRIPPING all 0xff, what is the byte length of the buffer? (NB: may be 0)
while (len>0) {
if (buff[len-1] != 0xff) break;
len--;
}
return len;
}
void send_reset(uint8_t *addr) {
ds->reset();
ds->select(addr);
ds->write(0x64);
delay(10);
}
uint8_t one_byte_return(uint8_t *addr, uint8_t cmd) {
ds->reset();
ds->select(addr);
ds->write(cmd);
return ds->read();
}
uint8_t bit_invert(uint8_t a) {
uint8_t b = 0;
int i;
for (i=0; i<8; i++) {
b *= 2;
b += (a & 0x01);
a /= 2;
}
return b;
}
void param2trim(uint16_t offset_param_11bit, uint8_t curve_param_5bit, uint8_t *param1, uint8_t *param2) {
*param1 = bit_invert(offset_param_11bit & 0x0ff);
*param2 = curve_param_5bit * 8 + offset_param_11bit / 256;
}
void trim2param(uint8_t param1, uint8_t param2, uint16_t *offset_param_11bit, uint8_t *curve_param_5bit) {
*offset_param_11bit = bit_invert(param1) + ((uint16_t)(param2 & 0x07)) * 256;
*curve_param_5bit = param2 / 8;
}
void get_trim_A(uint8_t *addr, uint8_t *trim1, uint8_t *trim2) {
ds->reset();
if (addr) ds->select(addr);
else ds->write(0xCC);
ds->write(0x93);
*trim1 = ds->read();
ds->reset();
if (addr) ds->select(addr);
else ds->write(0xCC);
ds->write(0x68);
*trim2 = ds->read();
}
void set_trim_A(uint8_t *addr, uint8_t trim1, uint8_t trim2) {
ds->reset();
ds->select(addr);
ds->write(0x95);
ds->write(trim1);
ds->reset();
ds->select(addr);
ds->write(0x63);
ds->write(trim2);
// don't store permanently, don't call reset
}
void get_trim_params_A(uint8_t *addr, uint16_t *offset_param_11bit, uint8_t *curve_param_5bit) {
uint8_t trim1, trim2;
get_trim_A(addr, &trim1, &trim2);
trim2param(trim1, trim2, offset_param_11bit, curve_param_5bit);
}
void set_trim_params_A(uint8_t *addr, uint16_t offset_param_11bit, uint8_t curve_param_5bit) {
uint8_t trim1, trim2;
param2trim(offset_param_11bit, curve_param_5bit, &trim1, &trim2);
set_trim_A(addr, trim1, trim2);
}
bool is_valid_A_scratchpad(uint8_t *buff) {
if ((buff[4] != 0x7f) && (buff[4] != 0x5f) && (buff[4] != 0x3f) && (buff[4] != 0x1f)) return false;
if ((buff[0] == 0x50) && (buff[1] == 0x05) && (buff[6] == 0x0C)) return true; // power-up
if ((buff[0] == 0xff) && (buff[1] == 0x07) && (buff[6] == 0x0C)) return true; // unsuccessful conversion
return buff[6] == (0x10 - (buff[0] & 0x0f));
}
bool is_all_00(uint8_t *buff, int N) {
int i;
for (i=0; i<N; i++)
if (buff[i] != 0x00) return false;
return true;
}
void trigger_convert(uint8_t *addr, uint8_t conf, uint16_t wait) {
ds->reset();
ds->select(addr);
ds->write(0x4E);
ds->write(0xaf);
ds->write(0xfe);
ds->write(conf);
ds->reset();
ds->select(addr);
ds->write(0x44, 1); // start conversion and keep data line high in case we need parasitic power
delay(wait);
}
int curve_param_prop(uint8_t *addr) {
// check if the curve parameter is unsigned (1) signed (2), temperature range inconclusive (-1), doesn't seem to be A (-2), CRC error (-3), or doesn't seem to do anything (0)
uint8_t buff[9];
uint16_t off = 0x3ff; // half way
set_trim_params_A(addr, off, 0x0f);
trigger_convert(addr, 0x7f, 800);
if (!read_scratchpad(addr, buff)) read_scratchpad(addr, buff);
if (0 != OneWire::crc8(buff,9)) return -3;
if (!is_valid_A_scratchpad(buff)) return -2;
int16_t r0f = buff[0] + 256*buff[1];
{
// check if we can set and read trim parameters
uint16_t o;
uint8_t c;
get_trim_params_A(addr, &o, &c);
if ((o != off) || (c != 0x0f)) return 0;
}
set_trim_params_A(addr, off, 0x00);
trigger_convert(addr, 0x7f, 800);
if (!read_scratchpad(addr, buff)) read_scratchpad(addr, buff);
if (0 != OneWire::crc8(buff,9)) return -3;
if (!is_valid_A_scratchpad(buff)) return -2;
int16_t r00 = buff[0] + 256*buff[1];
set_trim_params_A(addr, off, 0x1f);
trigger_convert(addr, 0x7f, 800);
if (!read_scratchpad(addr, buff)) read_scratchpad(addr, buff);
if (0 != OneWire::crc8(buff,9)) return -3;
if (!is_valid_A_scratchpad(buff)) return -2;
int16_t r1f = buff[0] + 256*buff[1];
set_trim_params_A(addr, off, 0x10);
trigger_convert(addr, 0x7f, 800);
if (!read_scratchpad(addr, buff)) read_scratchpad(addr, buff);
if (0 != OneWire::crc8(buff,9)) return -3;
if (!is_valid_A_scratchpad(buff)) return -2;
int16_t r10 = buff[0] + 256*buff[1];
int16_t mini = min(r00, min(r0f, min(r10, r1f)));
int16_t maxi = max(r00, max(r0f, max(r10, r1f)));
bool is_signed = (r0f-r10 > r1f-r00);
bool is_unsigned = (r0f-r10 < r1f-r00);
if (is_signed && (maxi-mini > 20*16)) return 2; // A2
if (is_unsigned && (maxi-mini > 1*16) && (maxi-mini < 6*16)) return 1; // A1
return -1;
}
void setup() {
Comm.begin(115200);
Comm.println();
ds = new OneWire(pin_onewire);
Comm.println(F("classify_fake_DS18B20.ino version 2020/05/25"));
Comm.println();
Comm.println(F("This sketch performs a minimal test to classify"));
Comm.print(F(" 1-wire sensors attached to pin "));
Comm.print(pin_onewire, DEC);
Comm.println(F(" according to"));
Comm.println(F(" https://github.com/cpetrich/counterfeit_DS18B20/"));
Comm.println(F(" assuming they belong to one of the DS18B20"));
Comm.println(F(" Families in circulation in 2019."));
Comm.println(F(" The script evaluates the sensors based on their"));
Comm.println(F(" response to undocumented function codes."));
Comm.println(F(" Proceed at your own risk!"));
Comm.println();
Comm.println(F(" Make sure power pin is connected to either Vcc or GND."));
Comm.println(F(" Hit enter to start analysis."));
Comm.println();
}
void loop() {
// ROM address of current sensor
uint8_t addr[8];
// Wait for user to send (any) data,
// depending on terminal this typically
// means pushing 'enter'. We ignore
// everyting sent.
while (Comm.available())
Comm.read();
while (!Comm.available());
while (Comm.available())
Comm.read();
int sensor_count = 0;
ds->reset_search();
while (ds->search(addr)) {
sensor_count ++;
// dump ROM
print_array(addr, 8, '-');
if (0 != OneWire::crc8(addr, 8)) {
Comm.print(F(" (CRC Error)"));
}
Comm.print(F(":"));
int identified = 0;
{ // test for family A
uint8_t r68 = one_byte_return(addr, 0x68);
uint8_t r93 = one_byte_return(addr, 0x93);
if (r68 != 0xff) {
int cpp = curve_param_prop(addr);
if (cpp == 1) Comm.print(F(" Family A1 (Genuie Maxim).")); // unsigned and 3.8 oC range
else if (cpp == 2) Comm.print(F(" Family A2 (Clone).")); // signed and 32 oC range
else if (cpp == -3) Comm.print(F(" (Error reading scratchpad register [A].)"));
else if (cpp == -1) {
Comm.print(F(" Family A, unknown subtype (0x93="));
print_hex(one_byte_return(addr, 0x93));
Comm.print(F(", 0x68="));
print_hex(r68);
Comm.print(F(")."));
}
// cpp==0 or cpp==-2: these aren't Family A as we know them.
if ((cpp==1) || (cpp==2) || (cpp==-1)) identified++;
}
send_reset(addr);
}
{ // test for family B
uint8_t r97 = one_byte_return(addr, 0x97);
if (r97 == 0x22) Comm.print(F(" Family B1 (Clone)."));
else if (r97 == 0x31) Comm.print(F(" Family B2 (Clone)."));
else if (r97 != 0xFF) {
Comm.print(F(" Family B (Clone), unknown subtype (0x97="));
print_hex(r97);
Comm.print(F(")."));
}
if (r97 != 0xff) identified++;
}
{ // test for family D
uint8_t r8B = one_byte_return(addr, 0x8B);
// mention if parasitic power is known not to work.
if (r8B == 0x06) Comm.print(F(" Family D1 (Clone w/o parasitic power)."));
else if (r8B == 0x02) Comm.print(F(" Family D1 (Clone)."));
else if (r8B == 0x00) Comm.print(F(" Family D2 (Clone w/o parasitic power)."));
else if (r8B != 0xFF) {
Comm.print(F(" Family D (Clone), unknown subtype (0x8B="));
print_hex(r8B);
Comm.print(F(")."));
}
if (r8B != 0xff) identified++;
}
{
// this should be Family C by implication.
// Don't have a test for response to undocumented codes, so check if
// config register is constant, which is unique property among 2019 Families.
uint8_t buff[9];
uint8_t cfg1, cfg2;
ds->reset();
ds->select(addr);
ds->write(0x4E);
ds->write(0xaa);
ds->write(0x55);
ds->write(0x00);
if (!read_scratchpad(addr, buff)) read_scratchpad(addr, buff);
if (is_all_00(buff, 9) || (0 != OneWire::crc8(buff,9))) goto err_C;
cfg1 = buff[4];
ds->reset();
ds->select(addr);
ds->write(0x4E);
ds->write(0xaa);
ds->write(0x55);
ds->write(0xff);
if (!read_scratchpad(addr, buff)) read_scratchpad(addr, buff);
if (is_all_00(buff, 9) || (0 != OneWire::crc8(buff,9))) goto err_C;
cfg2 = buff[4];
ds->reset();
ds->select(addr);
ds->write(0x64);
delay(10);
if (cfg1 == cfg2) {
Comm.print(F(" Family C (Clone)."));
identified++;
}
if (0) {
err_C:
Comm.print(F(" (Error reading scratchpad register [C].)"));
}
}
if (identified == 0)
Comm.print(F(" (Could not identify Family.)"));
if (identified > 1)
Comm.print(F(" (Part may belong to a Family not seen in 2019.)"));
Comm.println();
} // end iterate over all sensors
if (sensor_count == 0) {
// produce output so the user knows we tried (and failed)
// to detect a sensor. May have forgotten the pull-up
// resistor.
Comm.println(F("No sensors detected."));
}
Comm.println(F("-------------")); // indicate end
}