-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSL018.cpp
More file actions
executable file
·485 lines (445 loc) · 9.82 KB
/
SL018.cpp
File metadata and controls
executable file
·485 lines (445 loc) · 9.82 KB
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
/**
* @title StrongLink SL018/SL030 RFID reader library
*
* @file SL018.cpp
* @author marc@marcboon.com
* @date April 2010
*
* @see http://www.stronglink.cn/english/sl018.htm
* @see http://www.stronglink.cn/english/sl030.htm
*/
#include <Wire.h>
#include <string.h>
#include "SL018.h"
// local prototypes
void arrayToHex(char *s, byte array[], byte len);
char toHex(byte b);
/** Constructor.
*
* An instance of SL018 should be created as a global variable, outside of
* any function.
* The constructor sets public data fields to default values.
* These may be changed in setup() before SL018::reset() is called.
*/
SL018::SL018()
{
address = 0x50;
pinRESET = -1;
pinDREADY = -1;
cmd = CMD_IDLE;
debug = false;
t = millis() + 10;
}
/* Public member functions ****************************************************/
/** Reset the SL018 module
*
* This function should be called in setup(). It initializes the IO pins and
* issues a hardware or software reset, depending on the definition of pinRESET.
* After reset, antenna power is switched off to terminate the automatic SEEK mode.
*
* Wire.begin() should also be called in setup(), and Wire.h should be included.
*
* If pinRESET has the value -1 (default), software reset over I2C will be used.
* If pinDREADY has the value -1 (default), the SL018 will be polled over I2C for
* SEEK commands, otherwise the DREADY pin will be polled.
* For other commands, response polling is always over I2C.
*/
void SL018::reset()
{
// Init DREADY pin
if (pinDREADY != 0xff)
{
pinMode(pinDREADY, INPUT);
}
// Init RESET pin
if (pinRESET != 0xff) // hardware reset
{
pinMode(pinRESET, OUTPUT);
digitalWrite(pinRESET, HIGH);
delay(10);
digitalWrite(pinRESET, LOW);
}
else // software reset
{
sendCommand(CMD_RESET);
}
// Allow enough time for reset
delay(200);
}
/** Checks for availability of a valid response packet.
*
* This function should always be called and return true prior to using results
* of a command.
*
* @returns true if a valid response packet is available
*/
boolean SL018::available()
{
// Set the maximum length of the expected response packet
byte len;
switch(cmd)
{
case CMD_IDLE:
case CMD_RESET:
len = 0;
break;
case CMD_LOGIN:
case CMD_SET_LED:
case CMD_SLEEP:
len = 3;
break;
case CMD_READ4:
case CMD_WRITE4:
case CMD_READ_VALUE:
case CMD_WRITE_VALUE:
case CMD_DEC_VALUE:
case CMD_INC_VALUE:
case CMD_COPY_VALUE:
len = 7;
break;
case CMD_WRITE_KEY:
len = 9;
break;
case CMD_SEEK:
case CMD_SELECT:
len = 11;
break;
default:
len = SIZE_PACKET;
}
// If valid data received, process the response packet
if (len && receiveData(len) > 0)
{
// Init response variables
tagType = tagLength = *tagString = 0;
errorCode = data[2];
// Process command response
switch (getCommand())
{
case CMD_SEEK:
case CMD_SELECT:
// If no error, get tag number
if(errorCode == 0 && getPacketLength() >= 7)
{
tagLength = getPacketLength() - 3;
tagType = data[getPacketLength()];
memcpy(tagNumber, data + 3, tagLength);
arrayToHex(tagString, tagNumber, tagLength);
}
else if(cmd == CMD_SEEK)
{
// Continue seek
seekTag();
return false;
}
}
// Data is available
return true;
}
// No data available
return false;
}
/** Get error message for last command.
*
* @return Human-readable error message as a null-terminated string
*/
const char* SL018::getErrorMessage()
{
switch(errorCode)
{
case 0:
return "OK";
case 1:
return "No tag present";
case 2:
return "Login OK";
case 3:
case 0x10:
return "Login failed";
case 4:
return "Read failed";
case 5:
return "Write failed";
case 6:
return "Unable to read after write";
case 0x0A:
return "Collision detected";
case 0x0C:
return "Load key failed";
case 0x0D:
return "Not authenticated";
case 0x0E:
return "Not a value block";
default:
return "Unknown error";
}
}
/** Authenticate with transport key (0xFFFFFFFFFFFF).
*
* @param sector Sector number
*/
void SL018::authenticate(byte sector)
{
data[0] = 9;
data[1] = CMD_LOGIN;
data[2] = sector;
data[3] = 0xAA;
memset(data + 4, 0xFF, 6);
transmitData();
}
/** Authenticate with specified key A or key B.
*
* @param sector Sector number
* @param keyType Which key to use: 0xAA for key A or 0xBB for key B
* @param key Key value (6 bytes)
*/
void SL018::authenticate(byte sector, byte keyType, byte key[6])
{
data[0] = 9;
data[1] = CMD_LOGIN;
data[2] = sector;
data[3] = keyType;
memcpy(data + 4, key, 6);
transmitData();
}
/** Read 16-byte block.
*
* @param block Block number
*/
void SL018::readBlock(byte block)
{
data[0] = 2;
data[1] = CMD_READ16;
data[2] = block;
transmitData();
}
/** Read 4-byte page.
*
* @param page Page number
*/
void SL018::readPage(byte page)
{
data[0] = 2;
data[1] = CMD_READ4;
data[2] = page;
transmitData();
}
/** Write 16-byte block.
*
* The block will be padded with zeroes if the message is shorter
* than 15 characters.
*
* @param block Block number
* @param message Null-terminated string of up to 15 characters
*/
void SL018::writeBlock(byte block, const char* message)
{
data[0] = 18;
data[1] = CMD_WRITE16;
data[2] = block;
strncpy((char*)data + 3, message, 15);
data[18] = 0;
transmitData();
}
/** Write 4-byte page.
*
* This command is used for Mifare Ultralight tags which have 4 byte pages.
*
* @param page Page number
* @param message Null-terminated string of up to 3 characters
*/
void SL018::writePage(byte page, const char* message)
{
data[0] = 6;
data[1] = CMD_WRITE4;
data[2] = page;
strncpy((char*)data + 3, message, 3);
data[6] = 0;
transmitData();
}
/** Write master key (key A).
*
* @param sector Sector number
* @param key Key value (6 bytes)
*/
void SL018::writeKey(byte sector, byte key[6])
{
data[0] = 8;
data[1] = CMD_WRITE_KEY;
data[2] = sector;
memcpy(data + 3, key, 6);
transmitData();
}
/** Control red LED on SL018 (not implemented on SL030).
*
* @param on true for on, false for off
*/
void SL018::led(boolean on)
{
data[0] = 2;
data[1] = CMD_SET_LED;
data[2] = on;
transmitData();
}
/** Send 1-byte command.
*
* @param cmd Command
*/
void SL018::sendCommand(byte cmd)
{
data[0] = 1;
data[1] = cmd;
transmitData();
}
/* Private member functions ****************************************************/
/** Transmit a packet to the SL018.
*/
void SL018::transmitData()
{
// wait until at least 20ms passed since last I2C transmission
while(t > millis());
t = millis() + 20;
// remember which command was sent
cmd = data[1];
// transmit packet with checksum
Wire.beginTransmission(address);
for (int i = 0; i <= data[0]; i++)
{
Wire.send(data[i]);
}
Wire.endTransmission();
// show transmitted packet for debugging
if (debug)
{
Serial.print("> ");
printArrayHex(data, data[0] + 1);
Serial.println();
}
}
/** Receives a packet from the SL018.
*
* @param length the number of bytes to receive
* @return the number of bytes in the payload, or -1 if bad checksum
*/
byte SL018::receiveData(byte length)
{
// wait until at least 20ms passed since last I2C transmission
while(t > millis());
t = millis() + 20;
// read response
Wire.requestFrom(address, length);
if(Wire.available())
{
// get length of packet
data[0] = Wire.receive();
// get data
for (byte i = 1; i <= data[0]; i++)
{
data[i] = Wire.receive();
}
// show received packet for debugging
if (debug && data[0] > 0 )
{
Serial.print("< ");
printArrayHex(data, data[0] + 1);
Serial.println();
}
// return with length of response
return data[0];
}
return 0;
}
/** Maps tag types to names.
*
* @param type numeric tag type
* @return Human-readable tag name as null-terminated string
*/
const char* SL018::tagName(byte type)
{
switch(type)
{
case 1: return "Mifare 1K";
case 2: return "Mifare Pro";
case 3: return "Mifare UltraLight";
case 4: return "Mifare 4K";
case 5: return "Mifare ProX";
case 6: return "Mifare DesFire";
default: return "";
}
}
// Global helper functions
/** Convert byte array to null-terminated hexadecimal string.
*
* @param s pointer to destination string
* @param array byte array to convert
* @param len length of byte array to convert
*/
void arrayToHex(char *s, byte array[], byte len)
{
for (byte i = 0; i < len; i++)
{
*s++ = toHex(array[i] >> 4);
*s++ = toHex(array[i]);
}
*s = 0;
}
/** Convert low-nibble of byte to ASCII hex.
*
* @param b byte to convert
* $return uppercase hexadecimal character [0-9A-F]
*/
char toHex(byte b)
{
b = b & 0x0f;
return b < 10 ? b + '0' : b + 'A' - 10;
}
/** Print byte array as ASCII string.
*
* Non-printable characters (<0x20 or >0x7E) are printed as dot.
*
* @param array byte array
* @param len length of byte array
*/
void printArrayAscii(byte array[], byte len)
{
for (byte i = 0; i < len;)
{
char c = array[i++];
if (c < 0x20 || c > 0x7e)
{
Serial.print('.');
}
else
{
Serial.print(char(c));
}
}
}
/** Print byte array as hexadecimal character pairs.
*
* @param array byte array
* @param len length of byte array
*/
void printArrayHex(byte array[], byte len)
{
for (byte i = 0; i < len;)
{
printHex(array[i++]);
if (i < len)
{
Serial.print(' ');
}
}
}
/** Print byte as two hexadecimal characters.
*
* @param val byte value
*/
void printHex(byte val)
{
if (val < 0x10)
{
Serial.print('0');
}
Serial.print(val, HEX);
}