This is Part 3 of 3-part series of articles. Part 1 talks about ways of tweaking SPI code while Part 2 talks about hardware modifications.
After finishing hardware modifications for my three-SPI-device setup I started coding and hit a snag. Any device would happily work by its own, WiFi and SD were also happy together, however, adding NFC Shield to the mix would disable other two. If I moved NFC initialization to the beginnig, other two devices would work but NFC would not. At the same time, if a device was just present on a bus and not initialized, other two devices were not affected. It became clear that initialization itself was the source of error.
Seeedstudio NFC Shield uses NXP PN532 transmission module. This module supports several communication interfaces, namely SPI, I2C, and HSU. In SPI mode the data format is ‘LSB first’, i.e., transmission starts from bit 0. Such format is uncommon; all other SPI shields I’m aware of – Ethernet, WiFi, USB Host, etc., are using ‘MSB first’ format – the transmission starts with bit 7.
A quick look into NFC library source code revealed the following line in PN532::begin()
function:
pn532_SPI.setBitOrder(LSBFIRST);
This line sets the data format. In Atmega microcontrollers the default SPI data format is ‘MSB first’ – all other SPI devices don’t have to set it during initalization. Initial revision of PN532 code ( written by Adafruit ) was using software SPI and awkward bit order was dealt with in write()
and read()
functions. When Seeedstudio modified the code to work with hardware SPI, they just switched SPI to ‘LSB first’ format without much thinking, breaking compatibility with the rest of the world. Surely, when I commented out this line, NFC initialization stopped breaking WiFi. Predictably, NFC also stopped working.
Luckily for me, the SPI is pretty basic protocol and bit order setting in SPI controller doesn’t mean much. It really doesn’t matter how the bit order is set; if we need ‘MSB first’ for the majority of the devices we can initialize SPI in a normal way and then modify write()
and read()
functions for ‘LSB first’ device to serve it reversed bytes. This is exactly what I did. Below is modified version of Seeedstudio PN532 library (also presented on a title screenshot) – lines 6 and 16 perform bit reversing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /************** low level SPI ********/ /*Function:Transmit a byte to PN532 through the SPI interface. */ inline void PN532::write(uint8_t _data) { /* bit reversing code copied vetbatim from http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious */ _data = ((_data * 0x0802LU & 0x22110LU) | (_data * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; pn532_SPI.transfer(_data); } /*Function:Receive a byte from PN532 through the SPI interface */ inline uint8_t PN532::read(void) { uint8_t data_ = pn532_SPI.transfer(0); /* bit reversing code copied vetbatim from http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious */ data_ = ((data_ * 0x0802LU & 0x22110LU) | (data_ * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; return data_; } |
After making these modifications, commenting out the ‘LSB first’ setting in the PN532:begin()
and also modifying WiFi code to stay away from pin 9 (see this article for discussion) all three devices are happily working together without conflicts. The library mod for PN532 can be left there permanently – the chip will never know that it is communicating with “misconfigured” SPI host. I’m hoping Seeedstudio will fix their code eventually; in the mean time, if you have SPI compatibility issues, simply make code modifications presented in this article.
Enjoy,
Oleg.