ino compatibility
parent
b158beac3c
commit
5b0e1cbb36
|
@ -0,0 +1,7 @@
|
|||
Arduino library for StrongLink SL018/SL030 RFID readers
|
||||
|
||||
http://www.stronglink.cn/english/sl018.htm
|
||||
http://www.stronglink.cn/english/sl030.htm
|
||||
|
||||
June 2011
|
||||
marc@marcboon.com
|
|
@ -0,0 +1,507 @@
|
|||
/**
|
||||
* @title StrongLink SL018/SL030 RFID reader library
|
||||
*
|
||||
* @file SL018.cpp
|
||||
* @author marc@marcboon.com
|
||||
* @modified fil@rezox.com (Filipe Laborde-Basto) [to make binary safe]
|
||||
* @date February 2012
|
||||
*
|
||||
* @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 string of 16 characters (binary safe)
|
||||
*/
|
||||
void SL018::writeBlock(byte block, const char* message)
|
||||
{
|
||||
data[0] = 18;
|
||||
data[1] = CMD_WRITE16;
|
||||
data[2] = block;
|
||||
//strncpy((char*)data + 3, message, 15); // not binary safe
|
||||
memcpy( (char*)data + 3, message, 16 );
|
||||
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 String of 4 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); // not binary safe
|
||||
memcpy( (char*)data + 3, message, 4 );
|
||||
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.
|
||||
*/
|
||||
/*
|
||||
data[0] = 18;
|
||||
data[1] = CMD_WRITE16;
|
||||
data[2] = block;
|
||||
strncpy((char*)data + 3, message, 15);
|
||||
data[18] = 0;
|
||||
*/
|
||||
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++)
|
||||
{
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
Wire.write(data[i]);
|
||||
#else
|
||||
Wire.send(data[i]);
|
||||
#endif
|
||||
}
|
||||
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
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
data[0] = Wire.read();
|
||||
#else
|
||||
data[0] = Wire.receive();
|
||||
#endif
|
||||
|
||||
// get data
|
||||
for (byte i = 1; i <= data[0]; i++)
|
||||
{
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
data[i] = Wire.read();
|
||||
#else
|
||||
data[i] = Wire.receive();
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* @file SL018.h
|
||||
* @brief Header file for SL018 library
|
||||
* @author Marc Boon <http://www.marcboon.com>
|
||||
* @date February 2012
|
||||
*/
|
||||
|
||||
#ifndef SL018_h
|
||||
#define SL018_h
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
|
||||
#define SIZE_PACKET 19
|
||||
|
||||
// Global functions
|
||||
void printArrayAscii(byte array[], byte len);
|
||||
void printArrayHex(byte array[], byte len);
|
||||
void printHex(byte val);
|
||||
|
||||
class SL018
|
||||
{
|
||||
public:
|
||||
static const int VERSION = 1; //!< version of this library
|
||||
|
||||
static const byte MIFARE_1K = 1;
|
||||
static const byte MIFARE_PRO = 2;
|
||||
static const byte MIFARE_ULTRALIGHT = 3;
|
||||
static const byte MIFARE_4K = 4;
|
||||
static const byte MIFARE_PROX = 5;
|
||||
static const byte MIFARE_DESFIRE = 6;
|
||||
|
||||
static const byte CMD_IDLE = 0x00;
|
||||
static const byte CMD_SELECT = 0x01;
|
||||
static const byte CMD_LOGIN = 0x02;
|
||||
static const byte CMD_READ16 = 0x03;
|
||||
static const byte CMD_WRITE16 = 0x04;
|
||||
static const byte CMD_READ_VALUE = 0x05;
|
||||
static const byte CMD_WRITE_VALUE = 0x06;
|
||||
static const byte CMD_WRITE_KEY = 0x07;
|
||||
static const byte CMD_INC_VALUE = 0x08;
|
||||
static const byte CMD_DEC_VALUE = 0x09;
|
||||
static const byte CMD_COPY_VALUE = 0x0A;
|
||||
static const byte CMD_READ4 = 0x10;
|
||||
static const byte CMD_WRITE4 = 0x11;
|
||||
static const byte CMD_SEEK = 0x20;
|
||||
static const byte CMD_SET_LED = 0x40;
|
||||
static const byte CMD_SLEEP = 0x50;
|
||||
static const byte CMD_RESET = 0xFF;
|
||||
|
||||
static const byte OK = 0x00;
|
||||
static const byte NO_TAG = 0x01;
|
||||
static const byte LOGIN_OK = 0x02;
|
||||
static const byte LOGIN_FAIL = 0x03;
|
||||
static const byte READ_FAIL = 0x04;
|
||||
static const byte WRITE_FAIL = 0x05;
|
||||
static const byte CANT_VERIFY = 0x06;
|
||||
static const byte COLLISION = 0x0A;
|
||||
static const byte KEY_FAIL = 0x0C;
|
||||
static const byte NO_LOGIN = 0x0D;
|
||||
static const byte NO_VALUE = 0x0E;
|
||||
|
||||
boolean debug; //!< debug mode, prints all I2C communication to Serial port
|
||||
byte address; //!< I2C address (default 0x50)
|
||||
byte pinRESET; //!< RESET pin (default -1)
|
||||
byte pinDREADY; //!< DREADY pin (default -1)
|
||||
|
||||
private:
|
||||
byte data[SIZE_PACKET]; //!< packet data
|
||||
byte tagNumber[7]; //!< tag number as byte array
|
||||
byte tagLength; //!< length of tag number in bytes (4 or 7)
|
||||
char tagString[15]; //!< tag number as hex string
|
||||
byte tagType; //!< type of tag
|
||||
char errorCode; //!< error code from some commands
|
||||
byte cmd; //!< last sent command
|
||||
unsigned long t; //!< timer for sending I2C commands
|
||||
|
||||
public:
|
||||
//! Constructor
|
||||
SL018();
|
||||
|
||||
//! Hardware or software reset of the SL018 module
|
||||
void reset();
|
||||
|
||||
//! Returns true if a response packet is available
|
||||
boolean available();
|
||||
|
||||
//! Returns a pointer to the response packet
|
||||
byte* getRawData() { return data; };
|
||||
|
||||
//! Returns the last executed command
|
||||
byte getCommand() { return cmd; };
|
||||
|
||||
//! Returns the packet length, excluding checksum
|
||||
byte getPacketLength() { return data[0]; };
|
||||
|
||||
//! Returns a pointer to the packet payload
|
||||
byte* getPayload() { return data+2; };
|
||||
|
||||
//! Returns the block number for read/write commands
|
||||
byte getBlockNumber() { return data[2]; };
|
||||
|
||||
//! Returns a pointer to the read block (with a length of 16 bytes)
|
||||
byte* getBlock() { return data+3; };
|
||||
|
||||
//! Returns the tag's serial number as a byte array
|
||||
byte* getTagNumber() { return tagNumber; };
|
||||
|
||||
//! Returns the length of the tag's serial number obtained by getTagNumer()
|
||||
byte getTagLength() { return tagLength; };
|
||||
|
||||
//! Returns the tag's serial number as a hexadecimal null-terminated string
|
||||
const char* getTagString() { return tagString; };
|
||||
|
||||
//! Returns the tag type (SL018::MIFARE_XX)
|
||||
byte getTagType() { return tagType; };
|
||||
|
||||
//! Returns the tag type as a null-terminated string
|
||||
const char* getTagName() { return tagName(tagType); };
|
||||
|
||||
//! Returns the error code of the last executed command
|
||||
char getErrorCode() { return errorCode; };
|
||||
|
||||
//! Returns a human-readable error message corresponding to the error code
|
||||
const char* getErrorMessage();
|
||||
|
||||
//! Starts SEEK mode
|
||||
void seekTag() { selectTag(); cmd = CMD_SEEK; };
|
||||
|
||||
//! Sends a SELECT_TAG command
|
||||
void selectTag() { sendCommand(CMD_SELECT); };
|
||||
|
||||
//! Sends a HALT_TAG command
|
||||
void haltTag() { cmd = CMD_IDLE; };
|
||||
|
||||
//! Sends a SLEEP command (can only wake-up with hardware reset!)
|
||||
void sleep() { sendCommand(CMD_SLEEP); };
|
||||
|
||||
//! Writes a null-terminated string of maximum 15 characters to a block
|
||||
void writeBlock(byte block, const char* message);
|
||||
|
||||
//! Writes a null-terminated string of maximum 3 characters to a Mifare Ultralight page
|
||||
void writePage(byte page, const char* message);
|
||||
|
||||
//! Authenticate a sector using the transport key
|
||||
void authenticate(byte sector);
|
||||
|
||||
//! Authenticate a sector using the specified key
|
||||
void authenticate(byte sector, byte keyType, byte key[6]);
|
||||
|
||||
//! Reads a 16-byte block
|
||||
void readBlock(byte block);
|
||||
|
||||
//! Reads a 4-byte page
|
||||
void readPage(byte page);
|
||||
|
||||
//! Write master key (key A)
|
||||
void writeKey(byte sector, byte key[6]);
|
||||
|
||||
//! LED control (SL018 only)
|
||||
void led(boolean on);
|
||||
|
||||
private:
|
||||
//! Send single-byte command
|
||||
void sendCommand(byte cmd);
|
||||
//! Transmit command packet over I2C
|
||||
void transmitData();
|
||||
//! Receive response packet over I2C
|
||||
byte receiveData(byte length);
|
||||
//! Returns human-readable tag name corresponding to tag type
|
||||
const char* tagName(byte type);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @title: StrongLink SL018/SL030 RFID reader demo
|
||||
* @author: marc@marcboon.com
|
||||
* @see: http://www.stronglink.cn/english/sl018.htm
|
||||
* @see: http://www.stronglink.cn/english/sl030.htm
|
||||
*
|
||||
* Arduino to SL018/SL030 wiring:
|
||||
* A4/SDA 2 3
|
||||
* A5/SCL 3 4
|
||||
* 5V 4 -
|
||||
* GND 5 6
|
||||
* 3V3 - 1
|
||||
*/
|
||||
|
||||
#include <Wire.h>
|
||||
#include <SL018.h>
|
||||
|
||||
SL018 rfid;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Wire.begin();
|
||||
Serial.begin(19200);
|
||||
|
||||
// prompt for tag
|
||||
Serial.println("Show me your tag");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// start seek mode
|
||||
rfid.seekTag();
|
||||
// wait until tag detected
|
||||
while(!rfid.available());
|
||||
// print tag id
|
||||
Serial.println(rfid.getTagString());
|
||||
}
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
// SL018 demo application
|
||||
// Marc Boon <http://www.marcboon.com>
|
||||
// April 2010
|
||||
|
||||
// Controls a StrongLink SL018 or SL030 RFID reader by I2C
|
||||
// Arduino to SL018/SL030 wiring:
|
||||
// A3/TAG 1 5
|
||||
// A4/SDA 2 3
|
||||
// A5/SCL 3 4
|
||||
// 5V 4 -
|
||||
// GND 5 6
|
||||
// 3V3 - 1
|
||||
|
||||
#include <Wire.h>
|
||||
#include <SL018.h>
|
||||
|
||||
// TAG pin (low level when tag present)
|
||||
#define TAG 17 // A3
|
||||
|
||||
// Actions
|
||||
#define NONE 0
|
||||
#define SEEK 1
|
||||
#define READ 2
|
||||
#define WRITE 3
|
||||
|
||||
// Create SL018 instance
|
||||
SL018 rfid;
|
||||
|
||||
// Global vars
|
||||
byte action = NONE;
|
||||
boolean autoRead = false;
|
||||
boolean tagPresent = false;
|
||||
boolean authenticated;
|
||||
byte block;
|
||||
byte numBlocks;
|
||||
byte tagType;
|
||||
char msg[16];
|
||||
|
||||
void setup()
|
||||
{
|
||||
pinMode(TAG, INPUT);
|
||||
Wire.begin();
|
||||
Serial.begin(19200);
|
||||
Serial.println("SL018 demo");
|
||||
|
||||
// reset rfid module
|
||||
// rfid.reset();
|
||||
|
||||
// help
|
||||
Serial.println("Type ? for help");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
// check for tag presence when auto read is enabled
|
||||
if(autoRead && !tagPresent && !digitalRead(TAG) && action == NONE)
|
||||
{
|
||||
tagPresent = true;
|
||||
// read tag
|
||||
action = READ;
|
||||
// specify what to read
|
||||
block = 0;
|
||||
numBlocks = 16;
|
||||
// tag has to be selected first
|
||||
rfid.selectTag();
|
||||
}
|
||||
|
||||
// check if tag has gone
|
||||
if(tagPresent && digitalRead(TAG))
|
||||
{
|
||||
tagPresent = false;
|
||||
}
|
||||
|
||||
// check for command from serial port
|
||||
if(Serial.available() > 0)
|
||||
{
|
||||
switch(Serial.read())
|
||||
{
|
||||
case '?':
|
||||
Serial.println("Commands:");
|
||||
Serial.println("A - Auto read on/off");
|
||||
Serial.println("D - Debug on/off");
|
||||
Serial.println("S - Seek tag");
|
||||
Serial.println("R - Read sector");
|
||||
Serial.println("W - Write string");
|
||||
Serial.println("Q - Sleep");
|
||||
Serial.println("X - Reset");
|
||||
break;
|
||||
case 'a':
|
||||
case 'A':
|
||||
// auto read on/off
|
||||
autoRead = !autoRead;
|
||||
Serial.print("Auto read ");
|
||||
Serial.println(autoRead ? "on" : "off");
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
// debug on/off
|
||||
rfid.debug = !rfid.debug;
|
||||
Serial.print("Debug ");
|
||||
Serial.println(rfid.debug ? "on" : "off");
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
// seek tag
|
||||
Serial.println("Seek");
|
||||
rfid.seekTag();
|
||||
action = SEEK;
|
||||
break;
|
||||
case 'r':
|
||||
case 'R':
|
||||
// read tag
|
||||
Serial.println("Read");
|
||||
action = READ;
|
||||
// specify what to read
|
||||
block = 0;
|
||||
numBlocks = 16;
|
||||
// tag has to be selected first
|
||||
rfid.selectTag();
|
||||
break;
|
||||
case 'w':
|
||||
case 'W':
|
||||
// collect up to 15 characters from input, and terminate with zero
|
||||
if(readQuotedString(msg, sizeof(msg)) > 0)
|
||||
{
|
||||
// write string to tag
|
||||
Serial.print("Write '");
|
||||
Serial.print(msg);
|
||||
Serial.println("'");
|
||||
action = WRITE;
|
||||
block = 1;
|
||||
// tag has to be selected first
|
||||
rfid.selectTag();
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
case 'Q':
|
||||
// enter sleep mode
|
||||
Serial.println("Sleep");
|
||||
rfid.sleep();
|
||||
break;
|
||||
case 'x':
|
||||
case 'X':
|
||||
// reset
|
||||
Serial.println("Reset");
|
||||
rfid.reset();
|
||||
action = NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check for response from rfid
|
||||
if(rfid.available())
|
||||
{
|
||||
// check for errors
|
||||
if(rfid.getErrorCode() != SL018::OK && rfid.getErrorCode() != SL018::LOGIN_OK)
|
||||
{
|
||||
if(action != SEEK) // ignore errors while in SEEK mode
|
||||
{
|
||||
Serial.println(rfid.getErrorMessage());
|
||||
rfid.haltTag();
|
||||
action = NONE;
|
||||
}
|
||||
}
|
||||
else // deal with response if no error
|
||||
{
|
||||
switch(rfid.getCommand())
|
||||
{
|
||||
case SL018::CMD_SEEK:
|
||||
case SL018::CMD_SELECT:
|
||||
// store tag type
|
||||
tagType = rfid.getTagType();
|
||||
// show tag name and serial number
|
||||
Serial.print(rfid.getTagName());
|
||||
Serial.print(' ');
|
||||
Serial.println(rfid.getTagString());
|
||||
// in case of read or write action, authenticate first
|
||||
if(action == READ || action == WRITE)
|
||||
{
|
||||
// mifare ultralight does not need authentication, and has 4-byte blocks
|
||||
if(tagType == SL018::MIFARE_ULTRALIGHT)
|
||||
{
|
||||
authenticated = true;
|
||||
if(action == READ)
|
||||
{
|
||||
rfid.readPage(block);
|
||||
}
|
||||
else
|
||||
{
|
||||
// write to last page (because all others are write-protected on tikitags)
|
||||
rfid.writePage(15, msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
authenticated = false;
|
||||
rfid.authenticate(block >> 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// terminate seek
|
||||
rfid.haltTag();
|
||||
action = NONE;
|
||||
}
|
||||
break;
|
||||
case SL018::CMD_LOGIN:
|
||||
authenticated = true;
|
||||
if(action == READ)
|
||||
{
|
||||
rfid.readBlock(block);
|
||||
}
|
||||
else if(action == WRITE)
|
||||
{
|
||||
rfid.writeBlock(block, msg);
|
||||
}
|
||||
break;
|
||||
case SL018::CMD_READ4:
|
||||
case SL018::CMD_READ16:
|
||||
if(rfid.getCommand() == SL018::CMD_READ4)
|
||||
{
|
||||
// print 4-byte page in hex and ascii
|
||||
Serial.print("Page ");
|
||||
printHex(block);
|
||||
Serial.print(": ");
|
||||
printArrayHex(rfid.getBlock(), 4);
|
||||
Serial.print(" ");
|
||||
printArrayAscii(rfid.getBlock(), 4);
|
||||
Serial.println();
|
||||
}
|
||||
else
|
||||
{
|
||||
// print 16-byte block in hex and ascii
|
||||
Serial.print("Block ");
|
||||
printHex(block);
|
||||
Serial.print(": ");
|
||||
printArrayHex(rfid.getBlock(), 16);
|
||||
Serial.print(" ");
|
||||
printArrayAscii(rfid.getBlock(), 16);
|
||||
Serial.println();
|
||||
}
|
||||
// get next block
|
||||
if(++block && --numBlocks)
|
||||
{
|
||||
// mifare ultralight does not need authentication, and has 4-byte pages
|
||||
if(tagType == SL018::MIFARE_ULTRALIGHT)
|
||||
{
|
||||
rfid.readPage(block);
|
||||
}
|
||||
else if(authenticated && (block & 0x03) != 0)
|
||||
{
|
||||
// blocks from same sector don't need further authentication
|
||||
rfid.readBlock(block);
|
||||
}
|
||||
else
|
||||
{
|
||||
// authenticate next sector (4 blocks per sector)
|
||||
authenticated = false;
|
||||
rfid.authenticate(block >> 2);
|
||||
}
|
||||
}
|
||||
else // read completed
|
||||
{
|
||||
rfid.haltTag();
|
||||
action = NONE;
|
||||
}
|
||||
break;
|
||||
case SL018::CMD_WRITE4:
|
||||
case SL018::CMD_WRITE16:
|
||||
// write completed
|
||||
Serial.println("OK");
|
||||
rfid.haltTag();
|
||||
action = NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int readQuotedString(char *s, int len)
|
||||
{
|
||||
int i = 0;
|
||||
char quote = 0;
|
||||
while(i < len)
|
||||
{
|
||||
delay(5);
|
||||
if(Serial.available() == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
*s = Serial.read();
|
||||
if(quote == 0)
|
||||
{
|
||||
if(*s == '"' || *s == '\'')
|
||||
{
|
||||
quote = *s;
|
||||
}
|
||||
else if(*s != ' ')
|
||||
{
|
||||
++s;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else if(*s == quote)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
++s;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
*s = 0;
|
||||
return i;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @title: StrongLink SL018/SL030 RFID 2-reader demo
|
||||
* @author: lukasz.szostek@gmail.com based on code of:
|
||||
* @author: marc@marcboon.com
|
||||
* @see: http://www.stronglink.cn/english/sl018.htm
|
||||
* @see: http://www.stronglink.cn/english/sl030.htm
|
||||
*
|
||||
* Arduino to SL018/SL030 wiring:
|
||||
* A4/SDA 2 3
|
||||
* A5/SCL 3 4
|
||||
* 5V 4 -
|
||||
* GND 5 6
|
||||
* 3V3 - 1
|
||||
* 5 1 5 // first reader
|
||||
* 4 1 5 // second reader
|
||||
*/
|
||||
|
||||
#include <Wire.h>
|
||||
#include <SL018.h>
|
||||
|
||||
//pins to listen for the RFID board signalling that it has detected a tag
|
||||
int reader1OutPin = 5;
|
||||
int reader2OutPin = 4;
|
||||
const char* tagString;
|
||||
|
||||
SL018 rfid1;
|
||||
SL018 rfid2;
|
||||
|
||||
void setup()
|
||||
{
|
||||
//make sure these two addresses match your reader configuration
|
||||
rfid1.address = 0x50;
|
||||
rfid2.address = 0x52;
|
||||
pinMode(reader1OutPin, INPUT);
|
||||
pinMode(reader2OutPin, INPUT);
|
||||
Wire.begin();
|
||||
Serial.begin(57600);
|
||||
|
||||
// prompt for tag
|
||||
Serial.println("Show me your tag");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
//if the board has signalled that it found a tag
|
||||
if(!digitalRead(reader1OutPin))
|
||||
{
|
||||
//query tag data
|
||||
rfid1.seekTag();
|
||||
|
||||
/* for some reason the loop defined as:
|
||||
while(!digitalRead(reader1OutPin) || !rfid1.available());
|
||||
does not work so we need a workaround:*/
|
||||
|
||||
//while we are waiting for data
|
||||
while(!rfid1.available()) {
|
||||
//break if the tag has been removed
|
||||
if (digitalRead(reader1OutPin)) {
|
||||
break;
|
||||
};
|
||||
};
|
||||
tagString = rfid1.getTagString();
|
||||
Serial.print("Reader 1 found: ");
|
||||
Serial.println(tagString);
|
||||
//wait a while before querying the tag again
|
||||
delay(1500);
|
||||
};
|
||||
|
||||
//"in parallel" we wait for the other reader
|
||||
if(!digitalRead(reader2OutPin))
|
||||
{
|
||||
rfid2.seekTag();
|
||||
while(!rfid2.available()) {
|
||||
if (digitalRead(reader2OutPin)) {
|
||||
break;
|
||||
};
|
||||
};
|
||||
tagString = rfid2.getTagString();
|
||||
Serial.print("Reader 2 found: ");
|
||||
Serial.println(tagString);
|
||||
delay(1500);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map For SL018
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
SL018 KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
seekTag KEYWORD2
|
||||
selectTag KEYWORD2
|
||||
authenticate KEYWORD2
|
||||
readBlock KEYWORD2
|
||||
readPage KEYWORD2
|
||||
writeBlock KEYWORD2
|
||||
writePage KEYWORD2
|
||||
reset KEYWORD2
|
||||
led KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
MIFARE_1K LITERAL1
|
||||
MIFARE_PRO LITERAL1
|
||||
MIFARE_ULTRALIGHT LITERAL1
|
||||
MIFARE_4K LITERAL1
|
||||
MIFARE_PROX LITERAL1
|
||||
MIFARE_DESFIRE LITERAL1
|
||||
|
||||
OK LITERAL1
|
||||
NO_TAG LITERAL1
|
||||
LOGIN_OK LITERAL1
|
||||
LOGIN_FAIL LITERAL1
|
||||
READ_FAIL LITERAL1
|
||||
WRITE_FAIL LITERAL1
|
||||
CANT_VERIFY LITERAL1
|
||||
COLLISION LITERAL1
|
||||
KEY_FAIL LITERAL1
|
||||
NO_LOGIN LITERAL1
|
||||
NO_VALUE LITERAL1
|
||||
|
||||
|
Loading…
Reference in New Issue