NFC with PN352 on STM32F4

ChibiOS public support forum for topics related to the STMicroelectronics STM32 family of micro-controllers.

Moderator: RoccoMarco

Post Reply
kiwi
Posts: 13
Joined: Mon Feb 23, 2015 3:47 pm

NFC with PN352 on STM32F4

Post by kiwi »

Hello guys,

I'm planning on using the PN352 over SPI to communicate via NFC with an Android mobile phone. I'm probably going to use the breakout board from adafruit http://www.adafruit.com/products/364 and they are also providing a C++ library to communicate with the PN352 over SPI on Arduino. See https://github.com/adafruit/Adafruit-PN532/
I was wondering if anyone of you maybe already implemented a driver for ChibiOS for the PN352 over SPI, so I don't need to port the driver from Adafruit to Chibios.

best,
Steve
kiwi
Posts: 13
Joined: Mon Feb 23, 2015 3:47 pm

Re: NFC with PN352 on STM32F4

Post by kiwi »

Hi guys,

I started porting the driver from Adafruit for Arduino to the ST32M4 and since it is the first driver port I do, I ran into some problems.
According to the driver from Adafruit and the datasheet of the PN532 http://www.nxp.com/documents/user_manual/141520.pdf the SPI is configured as following:

Code: Select all

PN532Driver nfc;
static SPIConfig hs_nfc_spicfg = {NULL, GPIOF, GPIOF_PIN6, SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_LSBFIRST};
static SPIConfig ls_nfc_spicfg = {NULL, GPIOF, GPIOF_PIN6, SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_LSBFIRST};
static PN532Config nfccfg = {&SPID5, &ls_nfc_spicfg, &hs_nfc_spicfg};

I hooked a logic analyzer to the SPI bus and captured the initialization sequence the Arduino is sending to the PN532. I did the same while initializing the PN532 via the STM32F4 you can find both captures in CSV and the bottom post. The SPI is configured in the same, but still it yields different results.
This is the expected dialog:

Code: Select all

2.028776125000000,SPI,MOSI: '1' (0x01);  MISO: '1' (0x01)
2.028787125000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
2.028798125000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
2.028809125000000,SPI,MOSI: '255' (0xFF);  MISO: '255' (0xFF)
2.028820125000000,SPI,MOSI: '2' (0x02);  MISO: '255' (0xFF)
2.028831125000000,SPI,MOSI: '254' (0xFE);  MISO: '255' (0xFF)
2.028842125000000,SPI,MOSI: '212' (0xD4);  MISO: '255' (0xFF)
2.034147250000000,SPI,MOSI: '2' (0x02);  MISO: '255' (0xFF)
2.035422250000000,SPI,MOSI: * (0x2A);  MISO: '255' (0xFF)
2.035433250000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
2.041011375000000,SPI,MOSI: '2' (0x02);  MISO: '255' (0xFF)
2.041029875000000,SPI,MOSI: '0' (0x00);  MISO: '1' (0x01)
2.043073500000000,SPI,MOSI: '3' (0x03);  MISO: '1' (0x01)
2.044181625000000,SPI,MOSI: '0' (0x00);  MISO: '0' (0x00)
2.045297250000000,SPI,MOSI: '0' (0x00);  MISO: '0' (0x00)
2.046407250000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
2.047569000000000,SPI,MOSI: '0' (0x00);  MISO: '0' (0x00)
2.048674875000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
2.049832500000000,SPI,MOSI: '0' (0x00);  MISO: '0' (0x00)


and this is the dialog I receive on the STM32F4

Code: Select all

3.007068375000000,SPI,MOSI: '1' (0x01);  MISO: '8' (0x08)
3.007074375000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
3.007080500000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
3.007086625000000,SPI,MOSI: '255' (0xFF);  MISO: '255' (0xFF)
3.007092750000000,SPI,MOSI: '2' (0x02);  MISO: '255' (0xFF)
3.007098750000000,SPI,MOSI: '254' (0xFE);  MISO: '255' (0xFF)
3.007104875000000,SPI,MOSI: '212' (0xD4);  MISO: '255' (0xFF)
3.007111000000000,SPI,MOSI: '2' (0x02);  MISO: '255' (0xFF)
3.007117125000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
3.007123125000000,SPI,MOSI: * (0x2A);  MISO: '255' (0xFF)
3.007129250000000,SPI,MOSI: '0' (0x00);  MISO: '255' (0xFF)
3.009067125000000,SPI,MOSI: '2' (0x02);  MISO: '255' (0xFF)
3.010067500000000,SPI,MOSI: '0' (0x00);  MISO: '8' (0x08)
3.019067625000000,SPI,MOSI: '2' (0x02);  MISO: '8' (0x08)
3.020068000000000,SPI,MOSI: '0' (0x00);  MISO: '8' (0x08)
3.029068125000000,SPI,MOSI: '2' (0x02);  MISO: '8' (0x08)

'0x08' is not the expected value but also not documented as an error code.
So I'm wondering what is the difference. I noticed that on the Arduino, the MOSI signal immediatly goes to HIGH after a Send is finished and it goes to low roughly one clock before are Receive operation is starting. Hence on STM32F4 the MOSI always defaults to LOW and does not change it's value before a Receive operation is starting.
Can this maybe be the curcial point and is there a possibility to let MOSI default to HIGH when it is not used?
Actually I'm tapping in the dark. :|
But, here are 3 pictures visualize what I mean, the first is on the Arduino, the other are the same sequence on the STM32F4
Image
Image
Image
I hope I made myself clear and some one of you can help me :)

best,
Steve


SPI captures:
https://gist.github.com/bigzed/1aad5713293320719e6b

Code: Select all

#include "pn532_spi.h"

#define PN532_PREAMBLE                      (0x00)
#define PN532_STARTCODE1                    (0x00)
#define PN532_STARTCODE2                    (0xFF)
#define PN532_POSTAMBLE                     (0x00)

#define PN532_HOSTTOPN532                   (0xD4)
#define PN532_PN532TOHOST                   (0xD5)

// PN532 Commands
#define PN532_COMMAND_DIAGNOSE              (0x00)
#define PN532_COMMAND_GETFIRMWAREVERSION    (0x02)
#define PN532_COMMAND_GETGENERALSTATUS      (0x04)
#define PN532_COMMAND_READREGISTER          (0x06)
#define PN532_COMMAND_WRITEREGISTER         (0x08)
#define PN532_COMMAND_READGPIO              (0x0C)
#define PN532_COMMAND_WRITEGPIO             (0x0E)
#define PN532_COMMAND_SETSERIALBAUDRATE     (0x10)
#define PN532_COMMAND_SETPARAMETERS         (0x12)
#define PN532_COMMAND_SAMCONFIGURATION      (0x14)
#define PN532_COMMAND_POWERDOWN             (0x16)
#define PN532_COMMAND_RFCONFIGURATION       (0x32)
#define PN532_COMMAND_RFREGULATIONTEST      (0x58)
#define PN532_COMMAND_INJUMPFORDEP          (0x56)
#define PN532_COMMAND_INJUMPFORPSL          (0x46)
#define PN532_COMMAND_INLISTPASSIVETARGET   (0x4A)
#define PN532_COMMAND_INATR                 (0x50)
#define PN532_COMMAND_INPSL                 (0x4E)
#define PN532_COMMAND_INDATAEXCHANGE        (0x40)
#define PN532_COMMAND_INCOMMUNICATETHRU     (0x42)
#define PN532_COMMAND_INDESELECT            (0x44)
#define PN532_COMMAND_INRELEASE             (0x52)
#define PN532_COMMAND_INSELECT              (0x54)
#define PN532_COMMAND_INAUTOPOLL            (0x60)
#define PN532_COMMAND_TGINITASTARGET        (0x8C)
#define PN532_COMMAND_TGSETGENERALBYTES     (0x92)
#define PN532_COMMAND_TGGETDATA             (0x86)
#define PN532_COMMAND_TGSETDATA             (0x8E)
#define PN532_COMMAND_TGSETMETADATA         (0x94)
#define PN532_COMMAND_TGGETINITIATORCOMMAND (0x88)
#define PN532_COMMAND_TGRESPONSETOINITIATOR (0x90)
#define PN532_COMMAND_TGGETTARGETSTATUS     (0x8A)

#define PN532_RESPONSE_INDATAEXCHANGE       (0x41)
#define PN532_RESPONSE_INLISTPASSIVETARGET  (0x4B)

#define PN532_WAKEUP                        (0x55)

#define PN532_SPI_STATREAD                  (0x02)
#define PN532_SPI_DATAWRITE                 (0x01)
#define PN532_SPI_DATAREAD                  (0x03)
#define PN532_SPI_READY                     (0x01)

uint8_t pn532ack[] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00};
uint8_t pn532response_firmwarevers[] = {0x00, 0xFF, 0x06, 0xFA, 0xD5, 0x03};

#define PN532_PACKBUFFSIZ 128
uint8_t pn532_int_buffer[2*PN532_PACKBUFFSIZ], pn532_ext_buffer[PN532_PACKBUFFSIZ];

static bool_t isReady(PN532Driver *pn532p)
{
  uint8_t retVal;
  /* Chip Select */
  spiSelect(pn532p->config->spip);
  chThdSleepMilliseconds(2);

  retVal = spiPolledExchange(pn532p->config->spip, PN532_SPI_STATREAD);
  retVal = spiPolledExchange(pn532p->config->spip, 0);

  /* Unselect*/
  spiUnselect(pn532p->config->spip);

  return (retVal == PN532_SPI_READY);
}

static bool_t waitReady(PN532Driver *pn532p, uint16_t timeout)
{
  uint16_t timer = 0;
  while(!isReady(pn532p)) {
    if(timeout != 0) {
      timer += 10;
      if (timer > timeout) {
        return FALSE;
      }
    }
    chThdSleepMilliseconds(7);
  }
  return TRUE;
}

static void readData(PN532Driver *pn532p, uint8_t *buff, uint8_t n)
{
  int i;
  /* Chip select */
  spiSelect(pn532p->config->spip);
  chThdSleepMilliseconds(2);

  /* Send SPI read */
  spiPolledExchange(pn532p->config->spip, PN532_SPI_DATAREAD);

  /* Read data */
  spiReceive(pn532p->config->spip, n, buff);

  /* Unselect */
  spiUnselect(pn532p->config->spip);
}

static bool_t readAck(PN532Driver *pn532p)
{
  uint8_t ackbuff[6];

  readData(pn532p, ackbuff, 6);

  return (0 == strncmp((char *)ackbuff, (char *)pn532ack, 6));
}

static void writeCommand(PN532Driver *pn532p, uint8_t *cmd, uint8_t cmdlen)
{
  uint8_t checksum, i;

  cmdlen++;

  /* assemble static data */
  pn532_int_buffer[0] = PN532_SPI_DATAWRITE;
  pn532_int_buffer[1] = PN532_PREAMBLE;
  pn532_int_buffer[2] = PN532_PREAMBLE;
  pn532_int_buffer[3] = PN532_STARTCODE2;
  pn532_int_buffer[4] = cmdlen;
  pn532_int_buffer[5] = ~cmdlen + 1;
  pn532_int_buffer[6] = PN532_HOSTTOPN532;
  /* calculate static checksum */
  checksum = PN532_PREAMBLE + PN532_PREAMBLE + PN532_STARTCODE2 + PN532_HOSTTOPN532;
  /* add command */
  for(i = 7; (i-7)<cmdlen; ++i) {
    pn532_int_buffer[i] = cmd[i-7];
    checksum += cmd[i-7];
  }
  /* add checksum and postamble */
  pn532_int_buffer[7+cmdlen] = ~checksum;
  pn532_int_buffer[8+cmdlen] = PN532_POSTAMBLE;

  /* send over SPI */
  spiSelect(pn532p->config->spip);
  chThdSleepMilliseconds(2);

  spiSend(pn532p->config->spip, 9+cmdlen, pn532_int_buffer);
  spiUnselect(pn532p->config->spip);
}

static bool_t sendCommandCheckAck(PN532Driver *pn532p, uint8_t *cmd, uint8_t cmdlen, uint16_t timeout)
{
  /* Write command */
  writeCommand(pn532p, cmd, cmdlen);

  if(!waitReady(pn532p, timeout))
    return FALSE;

  if(!readAck(pn532p))
    return FALSE;

  if(!waitReady(pn532p, timeout))
    return FALSE;

  return TRUE;
}

void pn532Init(void)
{
}

void pn532ObjectInit(PN532Driver *pn532p)
{
  (void) pn532p;
}

bool_t pn532Start(PN532Driver *pn532p, PN532Config *config)
{
  int i = 0;
  pn532p->config = config;

  /* SPI initialization */
  spiStart(pn532p->config->spip, pn532p->config->lscfg);

  spiSelect(pn532p->config->spip);
  chThdSleepMilliseconds(1000);
 

  /* not exactly sure why but we have to send a dummy command to get synced up */
  pn532_ext_buffer[0] = PN532_COMMAND_GETFIRMWAREVERSION;
  sendCommandCheckAck(pn532p, pn532_ext_buffer, 1, 1000);

  /* Ignore response */
  spiUnselect(pn532p->config->spip);

  return TRUE
;}

bool_t pn532Stop(PN532Driver *pn532p)
{
  spiStop(pn532p->config->spip);

  return TRUE;
}

uint32_t pn532GetFirmwareVersion(PN532Driver *pn532p)
{
  uint32_t response = 0;
  uint8_t offset = 6;

  chThdSleepMilliseconds(1000);

  pn532_ext_buffer[0] = PN532_COMMAND_GETFIRMWAREVERSION;
  if(!sendCommandCheckAck(pn532p, pn532_ext_buffer, 1, 1000))
    return 0;

  readData(pn532p, pn532_ext_buffer, 12);

  if(0 != strncmp((char *)pn532_ext_buffer, (char *)pn532response_firmwarevers, 6))
    return 0;

  response = pn532_ext_buffer[offset++];
  response <<= 8;
  response |= pn532_ext_buffer[offset++];
  response <<= 8;
  response |= pn532_ext_buffer[offset++];
  response <<= 8;
  response |= pn532_ext_buffer[offset++];

  return response;
}


Code: Select all

#ifndef _PN532_SPI_H_
#define _PN532_SPI_H_

#include <string.h>

#include "ch.h"
#include "hal.h"

/**
 * @brief PN532 over SPI driver configuration structure.
 */
typedef struct {
  /**
   * @brief SPI driver associated to this PN532 driver.
   */
  SPIDriver             *spip;
  /**
   * @brief SPI low speed configuration used during initialization.
   */
  const SPIConfig       *lscfg;
  /**
   * @brief SPI high speed configuration used during transfers.
   */
  const SPIConfig       *hscfg;
} PN532Config;


typedef struct {
  const PN532Config *config;
} PN532Driver;

#ifdef __cplusplus
extern "C" {
#endif
  void pn532Init(void);
  void pn532ObjectInit(PN532Driver *pn532p);
  bool_t pn532Start(PN532Driver *pn532p, PN532Config *config);
  bool_t pn532Stop(PN532Driver *pn532p);
  uint32_t pn532GetFirmwareVersion(PN532Driver *pn532p);
#ifdef __cplusplus
}
#endif

#endif /* _PN532_SPI_H_ */
User avatar
Giovanni
Site Admin
Posts: 14892
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: NFC with PN352 on STM32F4

Post by Giovanni »

Hi,

Are CPOL and CPHA programmed as the device requires?

About MOSI, it is not controlled by the driver, the idle value cannot be changed.

Giovanni
kiwi
Posts: 13
Joined: Mon Feb 23, 2015 3:47 pm

Re: NFC with PN352 on STM32F4

Post by kiwi »

HI Giovanni,

the device requires SPI mode 0 or more preciese CPHA 0 and CPOL 0 and it is also configured like this.

best,
Steve
User avatar
Giovanni
Site Admin
Posts: 14892
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: NFC with PN352 on STM32F4

Post by Giovanni »

Hi,

Try inserting small delays between select and start of operations and before unselect. The SPI driver in itself should not have problems, it is probably the most used one.

Giovanni
kiwi
Posts: 13
Joined: Mon Feb 23, 2015 3:47 pm

Re: NFC with PN352 on STM32F4

Post by kiwi »

HI Giovanni,

thanks for your advice.
The error was not with your SPI driver, it was with my protocol. I made a little mistake and added a '0' to much in the command send to the chip. Now the chips talks with me.
I think I learned to always quadruple check the stuff I'm sending over the bus :D

best,
Steve
Post Reply