Using SonMicro SM130 RFID Modules with Arduino

It took me ages to find an inexpensive RFID module that would work with the Philips Mifare system. Most readers on the market were USB devices costing £50. Completely unsuitable for use with the Arduino or other inexpensive microprocessors.

Then I discovered SonMicro who make a whole range of useful RFID modules. The SM130 is a simple 13.56Mhz RFID reader that reads and writes to cards. Everything you need apart from the antenna is included so you just wire up power and communicate with the device using serial or I2C.

Using I2c requires a firmware update available from the manufacturer. Communication with the device is via a simple protocol described in the datasheet.

Sparkfun is now selling the devices which makes them easier to get hold of. I ordered my directly from SonMicro who were excellent and the devices arrived from Turkey to the UK in, from memory, less than two days. However, there is a minimum order value, shipping fees and import taxes to pay so Sparkfun is going to make this much more accessible to a hobbyist who’s not ordering large quantities.

Below is the Arduino code I used for playing with the device’s functionality:

#include <NewSoftSerial.h>

// rfid module is connected to pins 2 and 3 of the Arduino
NewSoftSerial rfid(2, 3);
int val;
int ANTENNA_OFF[] = {
  0x90, 0x00 };
int ANTENNA_ON[] = {
  0x90, 0x01 };
int FIRMWARE[] = {
  0x81 };
int HALT[] = {
  0x93 };
int READ_INPUT[] = {
  0x91 };
int READ_I2C[] = {
  0x9C };

int RESET[] = {
  0x80 };
int SEEK[] = {
  0x82 };
int SELECT[] = {
  0x83 };
int SLEEP[] = {
  0x96 };

bool antenna = true;
bool output_bit1 = true;
bool output_bit2 = true;
int count = 0;
char inputBuffer[20];

void setup()
{
  Serial.begin(57600);

  // set the data rate to 19200 to talk to the RFID module
  rfid.begin(19200);
  //send_command(SEEK, 3);
  Serial.print("Initalised\n");
}

// Send a command to the device to put into SEEK MODE
// TODO: Update to automatically calculate length and checksum
void send_command(int command[], int length) {
  // Send header
  rfid.print((char)0xFF);
  rfid.print((char)0x00);

  rfid.print((char)length);
  int checksum = length;
  for (int idx = 0; idx < length; idx++) {
    rfid.print((char)command[idx]);
    checksum += command[idx];
    //   Serial.print(command[idx], HEX);
  }
  checksum = checksum % 0x100;
  // checksum = 0x88; // testing purposes see 5.5 of manual - AUTHENTICATE
  rfid.print((char)checksum);
  /* Serial.print("Sending checksum: ");
   Serial.print(checksum, HEX);
   Serial.println();*/
}

void process_antenna_command(int buffer[], int length) {
  if (length == 2) {
    switch (buffer[1]) {
    case 0x0:
      Serial.println("Antenna OFF");
      break;
    case 0x1:
      Serial.println("Antenna ON");
      break;
    default:
      Serial.println("Invalid ANTENNA");
      break;
    }
  }
  else
  {
    Serial.println("Invalid ANTENNA");
  }
}

void process_firmware_command(int buffer[], int length) {
  Serial.print("RFID Firmware: ");
  for(int idx=0; idx < length; idx++) {
    Serial.print(buffer[idx], BYTE);
  }
  Serial.println();
}

void process_read_input(int buffer[], int length) {
  if (length == 2) {
    Serial.print("INPUT1 ");
    if (buffer[1] && 0x01) {
      Serial.println("ON");
    }
    else
    {
      Serial.println("OFF");
    }
    Serial.print("INPUT2 ");
    if (buffer[1] && 0x02) {
      Serial.println("ON");
    }
    else
    {
      Serial.println("OFF");
    }
  }
  else
  {
    Serial.println("Invalid READ INPUT");
  }
}

void process_write_value_block(int buffer[], int length) {
  switch (buffer[0]) {
  case 0x87:
    Serial.print("Read Value Block");
    break;
  case 0x8a:
    Serial.print("Write Value Block");
    break;
  case 0x8d:
    Serial.print("Increment Value Block");
    break;
  case 0x8e:
    Serial.print("Decrement Value Block");
    break;
  }

  if (length == 2) {
    // Error
    switch (buffer[1]) {
    case 0x4E:
      Serial.println(" - No Tag");
      break;
    case 0x49:
      Serial.println(" - Invalid Value Block");
      break;
    case 0x46:
      Serial.println(" - Read Failed during verification");
      break;
    }
  }
  else
  {
    // Sucess
    Serial.print(" successfully written to block: ");
    Serial.println(buffer[1], HEX);
    Serial.print("Value: ");
    for (int idx = 2; idx < length; idx++)
    {
      Serial.print(buffer[idx], HEX);
    }
    Serial.println();
  }

}

void process_seek_command(int buffer[], int length) {
  if (length == 2) {
    // Seek command has been received by module
    switch (buffer[1]) {
    case 0x4C:
      Serial.println("SEEK in progress");
      break;
    case 0x4E:
      Serial.println("SELECT - No Tag");
      break;
    case 0x55:
      Serial.println("SEEK in progress but RF Field is OFF");
      break;
    }
  }
  else
  {
    // Card has appeared within range
    // Output card type
    switch (buffer[1])
    {
      Serial.print("Mifare ");
    case 0x01:
      Serial.print("Ultralight");
      break;
    case 0x02:
      Serial.print("Standard 1K");
      break;
    case 0x03:
      Serial.print("Classic 4K");
      break;
    default:
      Serial.print("Unknown");
      break;
    }
    Serial.print(" card found, serial #");
    for (int idx = length - 1; idx > 1; idx--) {
      Serial.print(buffer[idx], HEX);
      Serial.print(" ");
    }
    // Put the RFID module into SEEK mode again
    //send_command(SEEK, 1);
  }
  Serial.print("\n");
}

void process_authenticate(int buffer[], int length) {
  if (length == 2) {
    switch (buffer[1]) {
    case 0x4c:
      Serial.println("Authenticate - Login Successful");
      break;
    case 0x4e:
      Serial.println("Authenticate - No Tag Present or Login Failed");
      break;
    case 0x55:
      Serial.println("Authenticate - Login Failed");
      break;
    case 0x45:
      Serial.println("Authenticate - Invalid Key Format in E2PROM");
      break;
    default:
      Serial.println("Invalid response from AUTHENTICATE command");
      break;
    }
  }
  else
  {
    Serial.println("Invalid response from AUTHENTICATE command");
  }
}

void process_halt_command(int buffer[], int length) {
  if (length == 2) {
    switch(buffer[1]) {
    case 0x4C:
      Serial.println("PICC(tag) is halted");
      break;
    case 0x55:
      Serial.println("PICC cannot be halted due to RF Field is OFF");
      break;
    default:
      Serial.println("Invalid HALT");
      break;
    }
  }
  else {
    Serial.println("Invalid HALT");
  }
}

void process_read_i2c_address(int buffer[], int length) {
  if (length == 2) {
    Serial.print("I2C Address is: ");
    Serial.println(buffer[1], HEX);
  }
  else
  {
    Serial.println("Invalid READ I2C ADDRESS");
    for (int idx =0 ; idx < length ; idx++) {
      Serial.print(buffer[idx], HEX);
      Serial.print(' ');
      Serial.println();
    }
  }
}

void process_write_i2c_address(int buffer[], int length) {

  if (length == 2 && buffer[1] == 0x4C){
    Serial.print("I2C was written sucessfully.");
  }
  else
  {
    Serial.println("Invalid WRITE I2C ADDRESS");
  }
}

void process_write_output(int buffer[], int length) {
  if (length == 2) {
    if (buffer[1] && 0x01) {
      Serial.println("OUTPUT1 is ON");
    }
    else
    {
      Serial.println("OUTPUT1 is OFF");
    }
    if (buffer[1] && 0x02) {
      Serial.println("OUTPUT2 is ON");
    }
    else
    {
      Serial.println("OUTPUT2 is OFF");
    }
  }
  else
  {
    Serial.println("Invalid WRITE OUTPUT");
  }
}

void process_sleep_command(int buffer[], int length) {
  if (buffer[1] == 0x00) {
    Serial.println("Sleep OK");
  }
  else
  {
    Serial.println("Invalid SLEEP");
  }
}

void process_command(int buffer[], int length)
{
  switch (buffer[0]) {
  case 0x81:
    process_firmware_command(buffer, length);
    break;
  case 0x82:
  case 0x83:
    process_seek_command(buffer, length);
    break;
  case 0x85:
    process_authenticate(buffer, length);
    break;
  case 0x87:
  case 0x8a:
  case 0x8d:
  case 0x8e:
    process_write_value_block(buffer, length);
    break;
  case 0x90:
    process_antenna_command(buffer, length);
    break;
  case 0x91:
    process_read_input(buffer, length);
    break;
  case 0x92:
    process_write_output(buffer, length);
    break;
  case 0x93:
    process_halt_command(buffer, length);
    break;
  case 0x96:
    process_sleep_command(buffer, length);
    break;
  case 0x9B:
    process_write_i2c_address(buffer, length);
    break;
  case 0x9C:
    process_read_i2c_address(buffer, length);
    break;

  default:
    Serial.print("Unknown command: ");
    Serial.print(buffer[0], HEX);
    Serial.print("\n");
  }
}

void loop()
{

  if (rfid.available()) {
    val = (int)rfid.read();
    if (val == 0xFF) {
      // Serial.print("Got command\n");
      // Begin command
      val = rfid.read();
      if (val != 00) {
        Serial.print("Invalid command");
      }
      int length = rfid.read();
      int buffer[20] = {
      };
      int checksum = length;
      for (int idx = 0; idx < length; idx++) {
        buffer[idx] = rfid.read();
        checksum += buffer[idx];
      }
      int recv_checksum = rfid.read();
      checksum = checksum % 0x100;

      if (checksum != recv_checksum)
      {
        Serial.println("Invalid checksum received");
        Serial.print("Recv checksum:\t\t");
        Serial.print(recv_checksum, HEX);
        Serial.print("\n");
        Serial.print("Checksum:\t\t");
        Serial.print(checksum, HEX);
        Serial.print("\n");
        Serial.print("Length:\t\t");
        Serial.print(length, HEX);
        Serial.println();
        Serial.print("COMMAND: ");
        for (int idx = 0; idx < length; idx++) {
          Serial.print(buffer[idx], HEX);
          Serial.print(" ");
        }
        Serial.println();
      } 

      process_command(buffer, length);
    }
  }

  if (Serial.available()) {
    char userCommand = (char)Serial.read();
    if (userCommand == '*') {
      process_user_command(inputBuffer, count);
      count = 0;
    }
    else
    {
      inputBuffer[count] = userCommand;
      count++;
      if (count > 20) {
        count = 0;
      }
    }
  }
}

void command_authenticate(int block) {
  int command[3];
  command[0] = 0x85;
  command[1] = 0x01; // block
  command[2] = 0xff;
  send_command(command,3);
}

void send_write_i2c_command() {
  int command[2] = {
    0x9b, 0x42   };
  send_command(command, 2);
}

// VALUE BLOCKS
void decrement_value() {
  int command[6] = {
    0x8e, 0x01, 0x03, 0x00, 0x00, 0x00                       };
  send_command(command, 6);
}

void increment_value() {
  int command[6] = {
    0x8d, 0x01, 0x03, 0x00, 0x00, 0x00                       };
  send_command(command, 6);
}

void read_value() {
  int command[2] = {
    0x87, 0x01                       };
  send_command(command, 2);
}

void write_value(int block, long data) {
  int command[6] = {
    0x8a, 0x01, 0x02, 0x00, 0x00, 0x00                       };
  send_command(command, 6);
}

void process_user_command(char inputBuffer[], int length) {
  switch (inputBuffer[0]) {
  case 'r':
    Serial.println("Reading value in block 1");
    read_value();
    break;
  case 'd':
    Serial.println("Incrementing value in block 1 by 3");
    decrement_value();
    break;
  case 'i':
    Serial.println("Incrementing value in block 1 by 3");
    increment_value();
    break;
  case 'w':
    Serial.println("Writing value of 2 to block 1");
    write_value(0,0);
    break;
  case 'A':
    if (antenna) {
      Serial.println("Turning antenna OFF");
      antenna = false;
      send_command(ANTENNA_OFF, 2);
    }
    else
    {
      Serial.println("Turning antenna ON");
      antenna = true;
      send_command(ANTENNA_ON, 2);
    }
    break;
  case 'C':
    Serial.println("Reading I2C address");
    send_command(READ_I2C, 1);
    break;
  case 'D':
    Serial.println("Writing I2C address");
    send_write_i2c_command();
    break;

  case 'F':
    Serial.println("Sending FIRMWARE command");
    send_command(FIRMWARE, 1);
    break;

  case 'G':
    Serial.println("Sending SEEK command");
    send_command(SEEK, 1);
    break;
  case 'H':
    Serial.println("Sending HALT command");
    send_command(HALT, 1);
    break;
  case 'I':
    Serial.println("Reading INPUT ports");
    send_command(READ_INPUT, 1);
    break;
  case 'L':
    Serial.println("Sending SELECT command");
    send_command(SELECT, 1);
    break;
  case 'R':
    Serial.println("Sending RESET command");
    send_command(RESET, 1);
    break;
  case 'S':
    Serial.println("Sending SLEEP command");
    send_command(SLEEP, 1);
    break;
  case 'U':
    Serial.println("Sending AUTHENTICATE command");
    //    int block = (int)inputBuffer[1];
    //int block =5;
    command_authenticate((int)inputBuffer[1]);
    break;
  default:
    Serial.println("Invalid user input");
    break;

  }
}

Ghosts of PDC Past

Microsoft’s currently hosting a two day Professional Developer’s Conference at their campus in Redmond and broadcasting live via the web. This is great as it provides access to far more people, doesn’t require any travel and is free to participate. The video quality is excellent. Of course you don’t get all the interesting conversations over breakfast/lunch, the free drinks and the goodies.

The last PDC I attended was in 2008 in Los Angeles. The big announcements were Windows 7 and Azure. The danger is that you spend a lot of time learning about technologies that are in very early stages of development and either never see a release or are substantially changed. A couple of other things were demoed in 2008 that have yet to see daylight.

Compiler as a Service

Anders Hejlsberg, architect of C#, mentioned in his 2008 talk that his team were rewriting the C# compiler in C# and going to make it available as a service. Apple recently did the same thing when they integrated LLVM into XCode 4. It opens the door to lots of intelligent refactorings and better error messages in the IDE.

In 2008 Anders demonstrated a C# REPL, i.e. an interactive C# command line. Mono already has this. The compiler service was mentioned again yesterday in Anders’ talk but not the REPL. The demo didn’t quite work and no timeline was mentioned. I’m guessing it won’t be released until the next version of Visual Studio so we’re looking at at least a year or two. Given that a new compiler will require extensive testing maybe they’ll release a CTP for VS2010 before then.

Oslo

A blue-sky project for modelling applications accompanied by a new language, ‘M’, and an editing tool called Quadrant. I’m guessing this was someone’s pet project. It’s now dead. According to the linked blog post ‘M’ the language is apparently still living but given the amount of investment required to support a language I can’t imagine Microsoft releasing another version. F# is clearly Microsoft’s preferred niche language and is widely used by scientific and financial developers. IronPython and IronRuby have just been turned over to the community, maybe ‘M’ will follow suit.

InfoCard/Windows CardSpace

Probably an over-ambitious project to provide single-sign-on for websites by allowing users to store identity profiles on their Windows machines. Unfortuantely it was heavily tied to Windows and Internet Explorer although a 3rd-party Firefox plugin did surface. It received little take-up and Microsoft abandoned the idea. It didn’t solve the problem of logging in from multiple computers as all your profiles were tied to a single machine. This is especially cumbersome when you want to login from a random public computer. Unsurprisingly the project died.

Microsoft have just announced AppFabric Access Control for Azure which looks much more promising. It allows users to easily login using their Facebook, OpenID and Windows Domain crendentials. It can be easily integrated to any ASP.Net application and requires no browser support or change to user behaviour and provides an immediate benefit.

Launching Headless VirtualBox Instances

I use VirtualBox to run Linux instances for development on my Mac and Windows desktops. Since I don’t use a GUI with Linux, just SSH, it’s convenient to run the virtual machines headless i.e. without displays. This is something the standard VirtualBox GUI doesn’t allow. However, there are two great utilities for Mac and Windows that support this.

VirtualBox Menulet for the Mac shows a dropdown list of all your VMs and their status. Select one to launch or shutdown. Slick and effective.

VBoxHeadlessTray provides similar functionality for Windows. It prompts to your to choose a VM and then launches it in headless mode. A icon in the system tray allows you to start and stop the machine and launch a remote desktop connection to it.

Now to find something similar for Amazon EC2.

Bifferboard – Linux in 3 Inches

Bifferboard
The Diminutive Bifferboard

The Bifferboard is a 486 processor running at 150Mhz, one or two USB ports depending on model, 24Mb RAM and a paltry 8Mb of flash storage. This is easily fixed with a £2 USB hub and a £7 2Gb memory stick.

As shipped the Bifferboard comes with a very minimal Linux system but even this contains a webserver. In order to make things more comfortable you need to put a full Linux distribution onto a memory stick. Things get confusing as there are numerous choices including Debian and Slackware. Installing Debian was a breeze, there is a script to copy it onto a memory stick and a ready-made kernel for the Bifferboard hardware.

Once done you have an all singing dancing Linux box albeit based around 15 year-old technology.

What’s exciting is that these things only cost £35 and can run off a battery. So you have a very powerful computer you can deploy anywhere.  I’m very tempted to stick one on the roof of my building plugged into a webcam. I’m already using USB wireless networking, 3G would be equally doable.

I bought one for an electronics project as the board has several GPIO (General Purpose Input Output) lines, and can support the I2C bus, a popular way of interfacing microcontrollers.

Bifferboard with USB Hub
The complete setup

My Sheevaplug Plug Died

I’m a big fan of the Sheevaplug, an all in one Linux device about the size of a power adaptor. One of the nice things about it is that it plugs directly into the mains. Unfortunately one of mine died recently. I thought the flash had become corrupted since the LEDs on the device were flashing but I wasn’t able to communicate with it. I found several forum posts about power supplies failing so decided to open mine up.
The internal metal case of the power supply had rust marks on it. Curiously the output of the PSU was still 5V but I’m guessing it wasn’t providing enough current. Connecting the power supply from another Sheevaplug confirmed the board itself was working perfectly. So I decided to open the broken power supply and take a look:

Sheevaplug Power Supply
Not a pretty sight

Several exploded capacitors, nasty. I’m surprised it was still letting current flow.

I’ve emailed Globalscale, who make the device, to see whether they sell replacements. The unit is not yet a year old but the warranty is only one month, besides shipping charges to the US would make repair uneconomic.

The power supply is a custom part, not listed in the bill of materials Globalscale supply. Obtaining a part that can provide 15W that will fit into the original space will cost nearly £40 so I’ll replace it with an external PSU.

Disappointing as the unit was not connected to any external USB devices and under normal operation. Otherwise I’ve been very impressed with the Sheevaplugs.

Moving Servers

After ten years of physical boxes in Telehouse I’m moving to a virtual Linux instance hosted on Debian. It’s an opportunity to review the software I’m running. Out goes Apache2 in favour of Lighttpd. SquirrelMail, you served me well but RoundCube is full of AJAX goodness and more importantly can display HTML emails.

Courier IMAP, I always hated you, and was very happy to discover Dovecot. Postfix and BIND, you remain. Behind the scenes I’m running Samba bound to an internal IP address that’s routed via a VPN to my home LAN. The VPN also lets me forget about securing individual services such as PostgreSQL and Subversion, they’re also VPN accessible only and at home or on the road I can access them conveniently without passwords.

For backups, I’m experimenting with AmazonS3 mounted via FuSE and rsync.

As should be fairly obvious I’m using WordPress to run my blog.

Ben Lamb's thoughts on C#, Open Source and Finance…