This post is a deep dive into my exploration of Near-Field Communication (NFC) using a Raspberry Pi running Android Things. This subject area was completely new to me - it was fascinating to learn about this new technology. Ultimately, I came up against some roadblocks that prevented me from integrating it into my Cooker Connector project, and here I will explain my journey and what I learned.
Getting a Raspberry Pi on WiFi
Perhaps the most annoying thing about using a new Raspberry Pi is getting it on WiFi. I don’t have an extra monitor, keyboard, and mouse lying around that aren’t connected to a computer, so any time I need to get a Raspberry Pi online, I have to crawl under a desk, unplug some cables, detangle some wires, and hope they all reach -- just so I can type in one word. In my Cooker Connector proposal, I hoped to solve this problem with an NFC reader. The idea was that I could transfer the WiFi credentials from a smartphone to the Raspberry Pi using NFC by simply tapping them together.
The Explore NFC Board
The Pi Chef challenger’s kit came with the Explore NFC Board, which is made for Raspberry Pi. The board plugs right into the GPIO header and uses SPI to communicate. The supporting documentation is good, and element14 links to the related downloads and instructions, so it seemed like I could easily incorporate this board into my project as a nice add-on feature. The board also came with a credit-card-sized NFC tag called NTag, which can store just under a kilobyte of data.
Figure 1. Explore NFC Board next to a Raspberry Pi.
The main chip on the Explore NFC board is the PN512 by NXP. This chip is apparently one of the most popular NFC devices used in transportation systems and other contactless, “tap your card here” technology. According to the schematic of the board, it is basically a breakout for the PN512 that includes an optimized antenna and some convenient configuration wiring.
Looking at the board and schematic, I noticed that it is hard-wired to support SPI instead of I2C. The board has some zero-ohm surface-mount resistors that function as semi-permanent switches. If you want to use I2C to communicate with the PN512, you have to de-solder these tiny resistors and re-solder them in other locations. This was a step I wasn’t willing to take, since it was potentially destructive, and I didn’t want to ruin the only board I had, so I would have to use SPI.
Conflicts with my Sensor Hub PCB
Unfortunately, the NFC board uses the same Raspberry Pi pin as my Sensor Hub PCB for its SPI “Chip Select” signal, so the two boards would interfere with each other if they were both connected at the same time. This was an oversight I made when designing my PCB. It turns out that I made another mistake in my PCB design that prevents me from attaching another board on top of it: even though I added header pins that would allow another board to stack on top of the GPIO pins, I forgot that the servo would also plug in to the top, so the servo wires would be in the way of the above board. I also stuck a giant capacitor on the top of my board, which would be in the way as well.
Figure 2. Photo of my Sensor Hub PCB with a giant capacitor and servo pins.
Ultimately, I decided that WiFi pairing was a one-time process, and having the two boards stacked together would not be necessary in the long run. Instead, I would mount only the NFC board, transfer the WiFi credentials, and then swap it out with my Sensor Hub PCB.
NXP NFC Library
NXP provides excellent documentation for the Explore NFC board, and they provide a full NFC library that handles a lot, including everything from configuring and communicating with the PN512 chip, to forming a communication channel with an NFC tag, to interpreting the records stored on the tag. However, it is all made for Linux systems, and my project doesn’t use Linux. I’m using Android Things OS instead (Read my post on Android Things to learn more about why I’m using Android instead of Raspbian), so I couldn’t use the standard NFC library. Even though Android does support native C and C++ code, the library depends on many Linux features such as timers, ioctl, and lots of other modules that Android can’t link against. The NFC library would have to be modified, and I was up for the challenge.
I dug into all the documentation I could find. I was pleasantly surprised with how well the software stack was designed to be ported to different chips, communication busses, and other hardware. They even have a guide that helps you through porting the library to another platform. At first, I considered porting the bus layer of the library. Anywhere the library needed a Linux feature, I would need to create a replacement that would call into an equivalent Android feature. For example, I would have to replace the Linux version of the Raspberry Pi SPI device, “/dev/spidev0.0”, with calls to the Android Things SpiDevice class.
Porting an entire layer of the NFC library didn’t seem like a valuable use of my time. If I were to continue on this path, in the best case, I would end up with a complete and fully-functional Android Things version of the NXP NFC library. In the worst case, I could end up wasting a lot of time trying to do something that isn’t even possible to begin with. The most probable outcome would be a hacky Android/Linux Frankenstein library that sorta-kinda-almost worked. None of these options were appealing.
Instead, I decided to forget the NXP library and focus instead on using just the raw, low-level SPI commands. This would obviously mean foregoing all of the robust functionality of the NXP library, but at least I could show some progress quickly, and I was already familiar with the Android Things SPI API.
I decided to stick to a very limited use case in order to keep the scope slim. Instead of being able to handle a broad range of NFC devices and message formats, I decided to pick one simple configuration. I planned to write the WiFi credentials to the NTag card with my phone using the NXP Tag Writer app, and then read the contents of the NTag with the Raspberry Pi.
I also wanted to use the simplest possible message format. The official NFC spec defines a standard set of message protocols, called NDEF, for NFC devices to store and send data. There is a message format for WiFi credentials, but it supports all types of security schemes and network configurations. I could simplify my use case further if I assumed the network would always use the common WPA2/AES standard and would always have a known SSID and password. This would allow me to adopt the the much simpler plain text message format. It would be a clear hack, but it would accomplish my goal.
Figure 3. NTag card that came with the Explore NFC board.
I found the full user manuals for the PN512 and the NTag, which seemed to have all the information I needed to get me up and running. NFC has many protocols and modes, and each of them are designed to be very robust at scale. I learned that the NTag card uses the MiFare (also known as ISO 14443A) protocol to communicate. This is what I needed to implement.
I created a test project (separate from my Sensor Hub app) so that I could test my NFC-related code in isolation. Before I could interact with the NTag via the PN512, I had to make sure I could interact with the PN512 on its own. The documentation outlined a “self test” mode, which seemed to be a good place to start.
As I wrote the code to carry out the Self Test, I built up a collection of methods to perform common SPI commands. The PN512 interface, like most IC protocols, is designed around the concept of registers. The device has 63 registers, and each of them store one 8-bit value. Each bit in each register corresponds to a particular function of the PN512. You can read the values of the registers to get status and data from the device, and you can write the values to control the device and give it data.
PN512 SPI Interface
To read or write a register, you first send the register’s 6-bit address over the SPI interface. You pad the address on the right with a single 0 bit, and you specify whether you want to read or write the register by setting the value of the left-most bit to 1 or 0. If you are reading the register, the PN512 will respond with the byte of data that is stored in that register. If you are writing, you immediately send the byte of data you wish to store in that register.
I created helper methods to perform read and write operations for single-byte registers. I also created methods to read and write multiple bytes at a time, since there is a special register that interacts with the PN512’s FIFO buffer, which is how you transmit and receive data to another tag using the radio.
PN512 Self Test
To perform the self test, you have to give the PN512 a special set of commands, which is described in the user manual. You set some control registers, write some bytes to the FIFO, and write a command to the Command Register to start the test. When the test is completed, you read some bytes from the FIFO and compare them against the expected output, which is given in the user manual. To my total surprise and shock, the first time I tried a self test, it worked! My Android Things app was successfully communicating to the Explore NFC board, and I was ready to move on to communicating with the NTag.
After the successful self test, I began looking at the NTag’s documentation. Most of the communication over the MiFare protocol includes CRC (Cyclic Redundancy Check) bytes at the end of each transmission. It was unclear to me how exactly to compute the CRC, however, since it only referred to it as CRC-16, and there are several such algorithms that produce slightly different outputs. I decided to do some testing.
The PN512 has a built-in CRC-16 coprocessor. When you enable the coprocessor, it continuously calculates the CRC result for all data subsequently written into the FIFO. When you need to get the CRC result value, you read two specific registers that together contain the 16-bit value. I decided to test which CRC method it uses by running some bytes through it and comparing the output to some known CRC algorithms. I found a website that lets you type in some ASCII text and see the CRC-16 output of several different algorithms at once. I wrote the text, “Hello, world!” to the PN512 FIFO and got the CRC result, which was D15E(hex). I ran the same text through the online CRC-16 generator and found that it matched the KERMIT algorithm. Knowing this algorithm was useful information, as it should allow me to interpret and debug the CRC bytes transmitting to and from the NTag.
MiFare NFC Protocol
I began researching the MiFare communication protocol, which is fairly complicated. At the center of MiFare is the state machine that prescribes how the tag behaves. The protocol is designed so that multiple tags can be within range of the scanner antenna, and ultimately only one will be active at a time. There is a complex anti-collision algorithm that involves waking up all nearby tags, asking them to share their unique IDs, detecting collisions, and using a binary search to isolate a specific tag, and “activating” it. Once a tag is activated, you can then send it commands to read or write from its memory, and all of the other nearby tags are smart enough to ignore the commands until they are individually activated. There are security and encryption modes that support monetary transactions and electronic tickets, but the NTag is not meant for these use cases, so I didn’t have to deal with these extra modes.
Feeling pretty confident at this point, I continued on to the next logical step, which was using the PN512 to get the text data from the NTag using this MiFare communication protocol. I used the helper methods I had written earlier to set up the PN512 in MiFare mode as a communication initiator. I then placed the NTag card on the antenna of the Explore NFC board, and ran my code, expecting it to send a Wakeup command to the NTag and receive a response.
Figure 4. Placing the NTag near the Explore NFC antenna.
Nothing happened. I read the related error registers from the PN512, and they seemed to indicate that the device was not transmitting or receiving any data. I looked back at the documentation, scouring it for any details I may have missed. I found one clue in the “Status2” register: the modem was stuck in a state called “TxWait”, which meant that it was it was waiting for the RF field to be present before actually transmitting. The way I am using NFC, the PN512 is supposed to produce a high-frequency RF field, which the tag picks up and uses to power itself. Information can be encoded in this high-frequency wave in both directions, which is how NFC devices communicate with each other.
Knowing that this RF field was not present meant that I needed to tell the PN512 to generate it. This led me to the “TxControl” register, which had a bunch of configuration options for the antenna. The documentation didn’t clearly explain when and how to use each of these options, so I didn’t feel confident that I was doing it right. I experimented with a few different ways to enable the RF field, and it seemed to solve the problem of being stuck in TxWait forever.
The modem was now progressing through the transmit state seemingly correctly, but it was now getting stuck in the “Wait for data” state. This indicated that the response from the NTag was missing. This could mean one of several things: A, the Wakeup command I was transmitting was wrong; B, the NTag was ignoring the command; C, the NTag received the command but didn’t send a response; D, the NTag sent a response but the PN512 didn’t receive it; or E, the PN512 received the response but didn’t understand it.
I started on point A. I again scoured the documentation for any clue that would help. I noticed that in the NTag documentation, most of the commands are supposed to be 8 bits long, but the Wakeup command is a special case and is supposed to be 7 bits long instead. I found “TxLastBits” within the “BitFraming” register on the PN512, which lets you set the number of bits to transmit. Setting this to 7 seemed to be appropriate, so I tried it. Still no luck. I tried several other things, but nothing seemed to get the NTag to respond to my Wakeup command.
At this point, I had hit a dead end. I didn’t have the right tools to debug this any further. I verified that the NTag still worked by reading and writing it using the app on my phone, so I knew I hadn’t broken it with a bad command, and it wasn’t faulty. Something in my configuration of the PN512 chip was wrong, but I had no way to narrow down the problem.
It was here that I decided to pause and consider how much more work I wanted to put into this feature. I reasoned that without additional debugging tools, I didn’t have much hope. I assume there is some type of professional NFC debugging hardware that could help here, like a protocol analyzer that can decode the radio signals being sent back and forth. My only path forward at this point is to continue guessing at the problem, which doesn’t seem promising.
I posted my test code to GitHub. If someone with a fresh set of eyes wants to pick up where I left off, I’d love to work with you on this.
Although I didn’t accomplish my goal of quick WiFi setup, I learned a lot about NFC in the process. I researched some other technologies that might be able to provide this functionality that have explicit Android Things support. I won’t have time to try any of these before the end of the Pi Chef Challenge, but they seem promising:
- Bluetooth LE
- Google Nearby Connections API
- Scanning a QR code with a camera
- Reading a file from a USB flash drive
I’m looking forward to releasing one more blog post this week. Be sure to check out my final project summary before the contest is over!