Well, that took a long time - I'd been hoping to use the Arduino code as a base but something just seemed a bit 'off' with it. Ended up using https://github.com/pimylifeup/MFRC522-python as the datasheet itself doesn't really seem to provide enough info by itself.
Ok, this is a bit hacky at the moment - I need to turn it into a proper module that works the same way as the old one, but:
var exports = {};
/* Copyright (c) 2019 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. */
// based on https://github.com/pimylifeup/MFRC522-python
exports.connect = function(spi, cs) {
var m = new MFRC522(spi, cs);
m.init();
return m;
};
MI_OK = "ok";
MI_ERR = "err";
var PCD = {
IDLE : 0x00,
AUTHENT : 0x0E,
RECEIVE : 0x08,
TRANSMIT : 0x04,
TRANSCEIVE : 0x0C,
RESETPHASE : 0x0F,
CALCCRC : 0x03
};
var PICC = {
REQIDL : 0x26,
REQALL : 0x52,
ANTICOLL : 0x93,
SElECTTAG : 0x93,
AUTHENT1A : 0x60,
AUTHENT1B : 0x61,
READ : 0x30,
WRITE : 0xA0,
DECREMENT : 0xC0,
INCREMENT : 0xC1,
RESTORE : 0xC2,
TRANSFER : 0xB0,
HALT : 0x50
};
var R = {
Command:2,
CommIEn:4,
DivlEn:6,
CommIrq:8,
DivIrq:10,
Error:12,
Status1:14,
Status2:16,
FIFOData:18,
FIFOLevel:20,
WaterLevel:22,
Control:24,
BitFraming:26,
Coll:28,
Mode:34,
TxMode:36,
RxMode:38,
TxControl:40,
TxAuto:42,
TxSel:44,
RxSel:46,
RxThreshold:48,
Demod:50,
Mifare:56,
SerialSpeed:62,
CRCResultH:66,
CRCResultL:68,
ModWidth:72,
RFCfg:76,
GsN:78,
CWGsP:80,
ModGsP:82,
TMode:84,
TPrescaler:86,
TReloadH:88,
TReloadL:90,
TCounterValueH:92,
TCounterValueL:94
};
function MFRC522(spi, cs) {
this.spi = spi;
this.cs = cs;
}
/// The access key to use - this is set to the default for mifare tags
MFRC522.prototype.KEY = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF];
/// read from a register
MFRC522.prototype.r = function(addr) {
if (!addr) throw new Error("invalid register");
return this.spi.send([addr|0x80,0], this.cs)[1];
};
/// multiple read from a register
MFRC522.prototype.ra = function(addr, c) {
if (!addr) throw new Error("invalid register");
var a = new Uint8Array(c+1);
a.fill(addr|0x80);
a[c]=0;
return this.spi.send(a, this.cs).slice(1);
};
/// write to a register
MFRC522.prototype.w = function(addr,data) {
if (!addr) throw new Error("invalid register");
if (data===undefined) throw new Error("invalid data");
this.spi.write(addr, data, this.cs);
};
/// turn antenna on/off
MFRC522.prototype.antenna = function(on) {
if (on) this.w(R.TxControl, this.r(R.TxControl) | 0x03);
else this.w(R.TxControl, this.r(R.TxControl) & ~0x03);
};
/// reset and initialise the MFRC522 - this is done automatically when `connect` is called
MFRC522.prototype.init = function() {
// soft reset
this.w(R.Command, PCD.RESETPHASE);
var timeout=50;
while (--timeout && (this.r(R.Command) & 16));
if (!timeout) throw new Error("SW Timeout");
// setup
this.w(R.TMode, 0x8D);
this.w(R.TPrescaler, 0x3E);
this.w(R.TReloadH, 0);
this.w(R.TReloadL, 30);
this.w(R.TxAuto, 0x40);
this.w(R.Mode, 0x3D);
this.antenna(1);
};
/// returns {status, data, bitCount}
MFRC522.prototype.MFRC522_ToCard = function(command, sendData) {
//console.log("MFRC522_ToCard ",arguments);
var backData = [];
var backLen = 0;
var status = MI_ERR;
var irqEn = 0x00;
var waitIRq = 0x00;
var lastBits;
if (command == PCD.AUTHENT) {
irqEn = 0x12;
waitIRq = 0x10;
}
if (command == PCD.TRANSCEIVE) {
irqEn = 0x77;
waitIRq = 0x30;
}
this.w(R.Command, PCD.IDLE);//g
this.w(R.CommIEn, irqEn | 0x80);
//this.w(R.CommIrq, 0x7F); // g clear IRQs
this.w(R.CommIrq, this.r(R.CommIrq)&~0x80);
this.w(R.FIFOLevel, 0x80); // g clear fifo
//this.w(R.FIFOLevel, this.r(R.FIFOLevel)|0x80);
//this.w(R.Command, PCD.IDLE);
this.w(R.FIFOData, sendData);
this.w(R.Command, command);
if (command == PCD.TRANSCEIVE)
this.w(R.BitFraming, this.r(R.BitFraming)|0x80);
var timeout = 500;
var n;
while (--timeout) {
n = this.r(R.CommIrq);
if (n&waitIRq) break;
if (n&1) throw new Error("HW Timeout");
}
if (!timeout) throw new Error("SW Timeout "+n);
this.w(R.BitFraming, this.r(R.BitFraming)&~0x80);
if ((this.r(R.Error) & 0x1B) == 0x00) {
status = MI_OK;
if (command == PCD.TRANSCEIVE) {
n = this.r(R.FIFOLevel);
lastBits = this.r(R.Control) & 0x07;
if (lastBits)
backLen = (n - 1) * 8 + lastBits;
else
backLen = n * 8;
if (n == 0) n = 1;
if (n > 16) n = 16;
backData = this.ra(R.FIFOData, n);
}
} else status = MI_ERR;
var r = {status:status, data:backData, bitCount:backLen};
//console.log("MFRC522_ToCard > ",r);
return r;
}
// returns {status,tagType}
MFRC522.prototype.MFRC522_Request = function(reqMode) {
var TagType = [];
this.w(R.BitFraming, 0x07);
TagType.push(reqMode);
var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, TagType);
if ((r.status != MI_OK) | (r.bitCount != 0x10))
r.status = MI_ERR;
//console.log("MFRC522_Request > "+r.status);
return { status : r.status, tagType : r.bitCount };
};
MFRC522.prototype.MFRC522_Anticoll = function() {
this.w(R.BitFraming, 0x00);
var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, [PICC.ANTICOLL,0x20]);
if (r.status == MI_OK) {
if (r.data.length == 5) {
var serNumCheck = 0;
for (var i=0;i<4;i++)
serNumCheck = serNumCheck ^ r.data[i];
if (serNumCheck != r.data[4])
r.status = MI_ERR;
}
} else
r.status = MI_ERR;
var r = {status:r.status, uid:r.data};
//console.log("MFRC522_Anticoll > ",r);
return r;
};
/// Given data returns a 2 byte array of the CRC
MFRC522.prototype.CalulateCRC = function(pIndata) {
this.w(R.DivIrq, this.r(R.DivIrq)&~0x04);
this.w(R.FIFOLevel, this.r(R.FIFOLevel)|0x80);
this.w(R.FIFOData, pIndata);
this.w(R.Command, PCD.CALCCRC);
var timeout = 200;
var n;
while (--timeout) {
n = this.r(R.DivIrq);
if (n&4) break;
if (n&1) throw new Error("HW Timeout");
}
if (!timeout) throw new Error("SW Timeout "+n);
return [this.r(R.CRCResultL), this.r(R.CRCResultH)];
};
/// Select the tag - returns the size?
MFRC522.prototype.MFRC522_SelectTag = function(serNum) {
var backData = [];
var buf = [PICC.SElECTTAG, 0x70];
for (var i=0;i<5;i++)
buf.push(serNum[i]);
var pOut = this.CalulateCRC(buf);
buf.push(pOut[0]);
buf.push(pOut[1]);
var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, buf);
if ((r.status == MI_OK) && (r.bitCount == 0x18)) {
return r.data[0];
}
};
MFRC522.prototype.MFRC522_Auth = function(authMode, BlockAddr, Sectorkey, serNum) {
var buff = [];
// First byte should be the authMode (A or B)
buff.push(authMode);
// Second byte is the trailerBlock (usually 7)
buff.push(BlockAddr);
// Now we need to.push the authKey which usually is 6 bytes of 0xFF
for (var i=0;i<Sectorkey.length;i++)
buff.push(Sectorkey[i]);
// Next we.push the first 4 bytes of the UID
for (var i=0;i<4;i++)
buff.push(serNum[i]);
// Now we start the authentication itself
var r = this.MFRC522_ToCard(PCD.AUTHENT, buff);
// Check if an error occurred
if (r.status != MI_OK)
throw new Error("AUTH ERROR!!");
if ((this.r(R.Status2) & 0x08) == 0)
throw new Error("AUTH ERROR(status2reg & 0x08) != 0");
// Return the status
return {status:r.status};
};
MFRC522.prototype.MFRC522_StopCrypto1 = function() {
this.w(R.Status2, this.r(R.Status2)&~0x08);
};
/// Read the given block address, return the data
MFRC522.prototype.MFRC522_Read = function(blockAddr) {
var recvData = [];
recvData.push(PICC.READ);
recvData.push(blockAddr);
pOut = this.CalulateCRC(recvData);
recvData.push(pOut[0]);
recvData.push(pOut[1]);
var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, recvData);
if (r.status != MI_OK)
throw new Error("Error while reading!");
if (r.data.length == 16) {
return r.data;
}
};
/// Write the given block address
MFRC522.prototype.MFRC522_Write = function(blockAddr, writeData) {
var buf = [PICC.WRITE,blockAddr];
crc = this.CalulateCRC(buf);
buf = buf.concat(crc);
var r = this.MFRC522_ToCard(PCD.TRANSCEIVE, buf);
if ((r.status != MI_OK) || (r.bitCount != 4) || ((r.data[0] & 0x0F) != 0x0A))
r.status = MI_ERR;
//console.log(r.bitCount,"backdata &0x0F == 0x0A ",r.data[0] & 0x0F);
if (r.status == MI_OK) {
buf = [];
// pad with 0s
for (var i=0;i<16;i++)
buf.push(0|writeData[i]);
crc = this.CalulateCRC(buf);
buf = buf.concat(crc);
r = this.MFRC522_ToCard(PCD.TRANSCEIVE, buf);
if ((r.status != MI_OK) || (r.bitCount != 4) || ((r.data[0] & 0x0F) != 0x0A))
throw new Error("Error while writing");
}
return {status:r.status};
};
MFRC522.prototype.read = function(blockAddr) {
var r = this.MFRC522_Request(PICC.REQIDL);
if (r.status != MI_OK)
return;
r = this.MFRC522_Anticoll();
if (r.status != MI_OK)
throw new Error("Anticoll failed");
this.MFRC522_SelectTag(r.uid);
r = this.MFRC522_Auth(PICC.AUTHENT1A, 11, this.KEY, r.uid);
if (r.status != MI_OK)
throw new Error("Auth failed");
var data = this.MFRC522_Read(blockAddr);
nfc.MFRC522_StopCrypto1();
return data;
};
MFRC522.prototype.write = function(blockAddr, data) {
var r = this.MFRC522_Request(PICC.REQIDL);
if (r.status != MI_OK)
return;
r = this.MFRC522_Anticoll();
if (r.status != MI_OK)
throw new Error("Anticoll failed");
this.MFRC522_SelectTag(r.uid);
r = this.MFRC522_Auth(PICC.AUTHENT1A, 11, this.KEY, r.uid);
if (r.status != MI_OK)
throw new Error("Auth failed");
data = E.toUint8Array(data);
blockAddr = 0|blockAddr;
var blocks = (data.length+15)>>4;
for (var b=0;b<blocks;b++) {
r = nfc.MFRC522_Write(blockAddr+b, new Uint8Array(data.buffer, b*16, 16));
if (r.status != MI_OK)
throw new Error("Write to block "+(blockAddr+b)+" failed");
}
nfc.MFRC522_StopCrypto1();
return r.status;
};
var spi = new SPI();
spi.setup({sck:A6, miso:B1, mosi:A7 });
var nfc = exports.connect(spi, A5/*CS*/);
nfc.write(8,"This is a test")
//="ok"
print(JSON.stringify(E.toString(nfc.read(8))));
//="This is a test\0\0"
So using the standard reader and tags, it seems you can only really do stuff with pages 8 and over, which are 16 bytes each?
Be careful - I think I 'bricked' a tag by writing lots to it and maybe overwriting the 'auth key' with something else. Definitely stick to pages 8/9/0 and 16 bytes each and you're fine though.
Also, there will be exceptions thrown every so often - sometimes a read will fail and you'll get a "HW Timeout" - but it's easy enough to catch it.
Espruino is a JavaScript interpreter for low-power Microcontrollers. This site is both a support community for Espruino and a place to share what you are working on.
Well, that took a long time - I'd been hoping to use the Arduino code as a base but something just seemed a bit 'off' with it. Ended up using https://github.com/pimylifeup/MFRC522-python as the datasheet itself doesn't really seem to provide enough info by itself.
Ok, this is a bit hacky at the moment - I need to turn it into a proper module that works the same way as the old one, but:
So using the standard reader and tags, it seems you can only really do stuff with pages 8 and over, which are 16 bytes each?
Be careful - I think I 'bricked' a tag by writing lots to it and maybe overwriting the 'auth key' with something else. Definitely stick to pages 8/9/0 and 16 bytes each and you're fine though.
Also, there will be exceptions thrown every so often - sometimes a read will fail and you'll get a
"HW Timeout"
- but it's easy enough to catch it.