diff --git a/src/NCI.cpp b/src/NCI.cpp index e42ae89..e88e6a4 100644 --- a/src/NCI.cpp +++ b/src/NCI.cpp @@ -11,25 +11,20 @@ //#include "logging.h" // Toolkit to allow easy logging towards, eg. Serial UART //#include "NciToolkit.h" - - #include "NCI.h" #include "NFCReaderWriter.h" -NCI::NCI(PN7150Interface &theHardwareInterface) : theHardwareInterface(theHardwareInterface), theState(NciState::HwResetRfc) +NCI::NCI(PN7150Interface &aHardwareInterface) : theHardwareInterface(aHardwareInterface), theState(NciState::HwResetRfc), theTagsStatus(TagsPresentStatus::unknown) { } void NCI::initialize() { theHardwareInterface.initialize(); - theState = NciState::HwResetRfc; - } - -void NCI::registrate(NFCReaderWriter *aReaderWriter) - { - theReaderWriter = aReaderWriter; + theState = NciState::HwResetRfc; // re-initializing the state, so we can re-initialize at anytime + theTagsStatus = TagsPresentStatus::unknown; + nmbrOfTags = 0; } void NCI::run() @@ -70,9 +65,9 @@ void NCI::run() case NciState::SwResetRfc: { - sendMessage(MsgTypeCommand, GroupIdCore, CORE_INIT_CMD); // CORE_INIT-CMD - setTimeOut(10); // we should get a RESPONSE within 10 ms, typically it takes 0.5ms - theState = NciState::SwResetWfr; // move to next state, waiting for response + sendMessage(MsgTypeCommand, GroupIdCore, CORE_INIT_CMD); // CORE_INIT-CMD + setTimeOut(10); // we should get a RESPONSE within 10 ms, typically it takes 0.5ms + theState = NciState::SwResetWfr; // move to next state, waiting for response } break; @@ -93,14 +88,14 @@ void NCI::run() } else if (isTimeOut()) { - theState = NciState::Error; // time out waiting for response.. + theState = NciState::Error; // time out waiting for response.. } break; case NciState::EnableCustomCommandsRfc: - sendMessage(MsgTypeCommand, GroupIdProprietary, NCI_PROPRIETARY_ACT_CMD); // Send NCI_PROPRIETARY_ACT_CMD to activate extra PN7150-NCI features - setTimeOut(10); // we should get a RESPONSE within 10 ms, typically it takes 0.5ms - theState = NciState::EnableCustomCommandsWfr; // move to next state, waiting for response + sendMessage(MsgTypeCommand, GroupIdProprietary, NCI_PROPRIETARY_ACT_CMD); // Send NCI_PROPRIETARY_ACT_CMD to activate extra PN7150-NCI features + setTimeOut(10); // we should get a RESPONSE within 10 ms, typically it takes 0.5ms + theState = NciState::EnableCustomCommandsWfr; // move to next state, waiting for response break; case NciState::EnableCustomCommandsWfr: @@ -126,15 +121,8 @@ void NCI::run() break; case NciState::RfIdleCmd: - // After configuring, we instruct the NFCDevice to go into Poll mode.. - { - uint8_t payloadData[] = { 3, NFC_A_PASSIVE_POLL_MODE, 0x01, NFC_B_PASSIVE_POLL_MODE, 0x01, NFC_F_PASSIVE_POLL_MODE, 0x01 }; - // TODO : instead of setting a fixed scanning for these 3 types, we should request the to be scanned for types from the ReaderWriter configuration... - sendMessage(MsgTypeCommand, GroupIdRfManagement, RF_DISCOVER_CMD, payloadData, 7); // - setTimeOut(10); // we should get a RESPONSE within 10 ms - theState = NciState::RfIdleWfr; // move to next state, waiting for Response - } - break; + // After configuring, we are ready to go into Discovery, but we wait for the readerWriter application to give us this trigger + break; case NciState::RfIdleWfr: if (theHardwareInterface.hasMessage()) @@ -143,18 +131,19 @@ void NCI::run() bool isOk = (4 == rxMessageLength); // Does the received Msg have the correct lenght ? isOk = isOk && isMessageType(MsgTypeResponse, GroupIdRfManagement, RF_DISCOVER_RSP); // Is the received Msg the correct type ? isOk = isOk && (STATUS_OK == rxBuffer[3]); // Is the received Status code Status_OK ? - if (isOk) // if everything is OK... + if (isOk) // if everything is OK... { - theState = NciState::RfDiscovery; // ...move to the next state + theState = NciState::RfDiscovery; // ...move to the next state + setTimeOut(500); // set a timeout of 1 second. If it times out, it means no cards are present.. } - else // if not.. + else // if not.. { - theState = NciState::Error; // .. goto error state + theState = NciState::Error; // .. goto error state } } else if (isTimeOut()) { - theState = NciState::Error; // time out waiting for response.. + theState = NciState::Error; // time out waiting for response.. } break; @@ -167,20 +156,26 @@ void NCI::run() if (isMessageType(MsgTypeNotification, GroupIdRfManagement, RF_INTF_ACTIVATED_NTF)) { // When a single tag/card is detected, the PN7150 will immediately activate it and send you this type of notification - theReaderWriter->reportTagProperties(rxBuffer, RF_INTF_ACTIVATED_NTF); // report the properties of this tag to the readerWriter application + saveTag(RF_INTF_ACTIVATED_NTF); // save properties of this Tag in the Tags array + theTagsStatus = TagsPresentStatus::singleTagPresent; theState = NciState::RfPollActive; // move to PollActive, and wait there for further commands.. - } else if (isMessageType(MsgTypeNotification, GroupIdRfManagement, RF_DISCOVER_NTF)) { // When multiple tags/cards are detected, the PN7150 will notify them all and wait for the DH to select one // The first card will have NotificationType == 2 and move the stateMachine to WaitForAllDiscoveries. // More notifications will come in that state - theReaderWriter->reportTagProperties(rxBuffer, RF_DISCOVER_NTF); // report the properties of this tag to the readerWriter application - setTimeOut(10); // we should get more Notifications, so set a timeout so we don't wait forever + saveTag(RF_DISCOVER_NTF); // save properties of this Tag in the Tags array + setTimeOut(25); // we should get more Notifications ubt set a timeout so we don't wait forever + theTagsStatus = TagsPresentStatus::multipleTagsPresent; theState = NciState::RfWaitForAllDiscoveries; } } + else if (isTimeOut()) + { + theTagsStatus = TagsPresentStatus::noTagsPresent; // this means no card has been detected for xxx millisecond, so we can conclude that no cards are present + } + break; case NciState::RfWaitForAllDiscoveries: @@ -194,12 +189,13 @@ void NCI::run() { case notificationType::lastNotification: case notificationType::lastNotificationNfccLimit: - theReaderWriter->reportTagProperties(rxBuffer, RF_DISCOVER_NTF); // report the properties of this tag to the readerWriter application + saveTag(RF_DISCOVER_NTF); // save properties of this Tag in the Tags array theState = NciState::RfWaitForHostSelect; break; case notificationType::moreNotification: - theReaderWriter->reportTagProperties(rxBuffer, RF_DISCOVER_NTF); // report the properties of this tag to the readerWriter application + setTimeOut(25); // we should get more Notifications, so set a timeout so we don't wait forever + saveTag(RF_DISCOVER_NTF); // save properties of this Tag in the Tags array break; default: @@ -286,9 +282,26 @@ void NCI::run() } } +void NCI::activate() + { + NciState tmpState = getState(); + if (tmpState == NciState::RfIdleCmd) + { + uint8_t payloadData[] = { 4, NFC_A_PASSIVE_POLL_MODE, 0x01, NFC_B_PASSIVE_POLL_MODE, 0x01, NFC_F_PASSIVE_POLL_MODE, 0x01, NFC_15693_PASSIVE_POLL_MODE, 0x01 }; + // TODO : instead of setting a fixed scanning for these 4 types, we should pass the to be scanned for types as parameters from the ReaderWriter... https://github.com/Strooom/PN7150/issues/1 + sendMessage(MsgTypeCommand, GroupIdRfManagement, RF_DISCOVER_CMD, payloadData, 9); // + setTimeOut(10); // we should get a RESPONSE within 10 ms + theState = NciState::RfIdleWfr; // move to next state, waiting for Response + } + else + { + // Error : we can only activate polling when in Idle... + } + } + void NCI::deActivate(NciRfDeAcivationMode theMode) { - // TODO : investigate how the different types of deactivation should behave + nmbrOfTags = 0; NciState tmpState = getState(); switch (tmpState) { @@ -297,6 +310,7 @@ void NCI::deActivate(NciRfDeAcivationMode theMode) uint8_t payloadData[] = { (uint8_t)NciRfDeAcivationMode::IdleMode }; // in RfWaitForHostSelect, the deactivation type is ignored by the NFCC sendMessage(MsgTypeCommand, GroupIdRfManagement, RF_DEACTIVATE_CMD, payloadData, 1); // setTimeOut(10); // we should get a RESPONSE within 10 ms + theTagsStatus = TagsPresentStatus::unknown; theState = NciState::RfDeActivate1Wfr; // move to next state, waiting for response } break; @@ -306,6 +320,7 @@ void NCI::deActivate(NciRfDeAcivationMode theMode) uint8_t payloadData[] = { (uint8_t)theMode }; sendMessage(MsgTypeCommand, GroupIdRfManagement, RF_DEACTIVATE_CMD, payloadData, 1); // setTimeOut(10); // we should get a RESPONSE within 10 ms + theTagsStatus = TagsPresentStatus::unknown; theState = NciState::RfDeActivate2Wfr; // move to next state, waiting for response } break; @@ -315,29 +330,44 @@ void NCI::deActivate(NciRfDeAcivationMode theMode) } } -NciState NCI::getState() +NciState NCI::getState() const { return theState; } +TagsPresentStatus NCI::getTagsPresentStatus() const + { + return theTagsStatus; + } + +uint8_t NCI::getNmbrOfTags() const + { + return nmbrOfTags; + } + +Tag* NCI::getTag(uint8_t index) + { + return &theTags[index]; + } + void NCI::sendMessage(uint8_t messageType, uint8_t groupId, uint8_t opcodeId) { txBuffer[0] = (messageType | groupId) & 0xEF; // put messageType and groupId in first byte, Packet Boundary Flag is always 0 txBuffer[1] = opcodeId & 0x3F; // put opcodeId in second byte, clear Reserved for Future Use (RFU) bits txBuffer[2] = 0x00; // payloadLength goes in third byte - theHardwareInterface.write(txBuffer, 3); + (void) theHardwareInterface.write(txBuffer, 3); // TODO : could make this more robust by checking the return value and go into error is write did not succees } void NCI::sendMessage(uint8_t messageType, uint8_t groupId, uint8_t opcodeId, uint8_t payloadData[], uint8_t payloadLength) { - txBuffer[0] = (messageType | groupId) & 0xEF; // put messageType and groupId in first byte, Packet Boundary Flag is always 0 - txBuffer[1] = opcodeId & 0x3F; // put opcodeId in second byte, clear Reserved for Future Use (RFU) bits - txBuffer[2] = payloadLength; // payloadLength goes in third byte - for (uint32_t index = 0; index < payloadLength; index++) // copy the payload + txBuffer[0] = (messageType | groupId) & 0xEF; // put messageType and groupId in first byte, Packet Boundary Flag is always 0 + txBuffer[1] = opcodeId & 0x3F; // put opcodeId in second byte, clear Reserved for Future Use (RFU) bits + txBuffer[2] = payloadLength; // payloadLength goes in third byte + for (uint32_t index = 0; index < payloadLength; index++) // copy the payload { txBuffer[index + 3] = payloadData[index]; } - theHardwareInterface.write(txBuffer, 3 + payloadLength); + (void) theHardwareInterface.write(txBuffer, 3 + payloadLength); // TODO : could make this more robust by checking the return value and go into error is write did not succees } void NCI::getMessage() @@ -345,12 +375,12 @@ void NCI::getMessage() rxMessageLength = theHardwareInterface.read(rxBuffer); } -bool NCI::isMessageType(uint8_t messageType, uint8_t groupId, uint8_t opcodeId) +bool NCI::isMessageType(uint8_t messageType, uint8_t groupId, uint8_t opcodeId) const { return (((messageType | groupId) & 0xEF) == rxBuffer[0]) && ((opcodeId & 0x3F) == rxBuffer[1]); } -bool NCI::isTimeOut() +bool NCI::isTimeOut() const { return ((millis() - timeOutStartTime) >= timeOut); } @@ -361,3 +391,43 @@ void NCI::setTimeOut(unsigned long theTimeOut) timeOut = theTimeOut; } +void NCI::saveTag(uint8_t msgType) + { + // Store the properties of detected TAGs in the Tag array. + // Tag info can come in two different NCI messages : RF_DISCOVER_NTF and RF_INTF_ACTIVATED_NTF and the Tag properties are in slightly different location inside these messages + + if (nmbrOfTags < maxNmbrTags) + { + uint8_t offSet; // Offset in the NCI message where we can find the UniqueID + switch (msgType) + { + case RF_INTF_ACTIVATED_NTF: + offSet = 12; + break; + + case RF_DISCOVER_NTF: + offSet = 9; + break; + + default: + return; // unknown type of msg sent here ?? we just ignore it.. + break; + } + + uint8_t NfcId1Length = rxBuffer[offSet]; + if (NfcId1Length > 10) + { + NfcId1Length = 10; // limit the length to 10, so in case of whatever error we don't write beyond the boundaries of the array + } + uint8_t newTagIndex = nmbrOfTags; // index to the array item where we will store the info + + theTags[newTagIndex].uniqueIdLength = NfcId1Length; // copy the length of the unique ID, is 4, 7 or 10 + for (uint8_t index = 0; index < NfcId1Length; index++) // copy all bytes of the unique ID + { + theTags[newTagIndex].uniqueId[index] = rxBuffer[offSet + 1 + index]; + } + theTags[newTagIndex].detectionTimestamp = millis(); + + nmbrOfTags++; // one more tag in the array now + } + } diff --git a/src/NCI.h b/src/NCI.h index 5211cd4..f9f82a5 100644 --- a/src/NCI.h +++ b/src/NCI.h @@ -19,14 +19,14 @@ // This library implements the NFC Controller Interface (NCI), which is a communication protocol between NFC hardware and a controlling Device Host // It was written as part of a NXP PN7150 driver // -// Initially I want to simply read NFC cards, using the PN7150, so the NCI implementation will only support a subset needed for this purpose +// Initially I want to simply read NFC cards Unique IDs, using the PN7150, so the NCI implementation will only support a subset needed for this purpose // Upon request, the library may be extended to support more advanced use of the PN7150 // #include // Gives us access to all typical Arduino types and functions +#include "Tag.h" // #include "PN7150Interface.h" // NCI protocol runs over a hardware interface. -class NFCReaderWriter; -//#include "NFCReaderWriter.h" // NCI driver needs to report back to an application, eg. a ReaderWriter + // --------------------------------------------------------------------- // NCI Packet Header Definitions. NCI Specification V1.0 - section 3.4.1 @@ -129,7 +129,6 @@ class NFCReaderWriter; #define ResetClearConfig 0x01 - // --------------------------------------------------- // NCI Status Codes. NCI Specification V1.0 - Table 94 // --------------------------------------------------- @@ -169,7 +168,6 @@ class NFCReaderWriter; // Proprietary Status Codes : 0xE0 - 0xFF - // ------------------------------------------------------ // NCI RF Technologies. NCI Specification V1.0 - Table 95 // ------------------------------------------------------ @@ -183,7 +181,6 @@ class NFCReaderWriter; // 0xFF RFU - // ------------------------------------------------------------- // NCI RF Technology and Mode. NCI Specification V1.0 - Table 96 // ------------------------------------------------------------- @@ -208,7 +205,6 @@ class NFCReaderWriter; // 0xF0 – 0xFF Reserved for Proprietary Technologies in Listen Mode - // --------------------------------------------------- // NCI RF Protocols. NCI Specification V1.0 - Table 98 // --------------------------------------------------- @@ -237,7 +233,6 @@ class NFCReaderWriter; // 0xFF RFU - // --------------------------------------------------------------- // NFCEE Protocol / Interfaces. NCI Specification V1.0 - Table 100 // --------------------------------------------------------------- @@ -291,7 +286,6 @@ enum class NciRfDeAcivationMode : uint8_t // 0x04 – 0xFF RFU - /* Discovery Types/Detected Technology and Mode */ #define NCI_DISCOVERY_TYPE_POLL_A 0x00 #define NCI_DISCOVERY_TYPE_POLL_B 0x01 @@ -321,14 +315,14 @@ enum class notificationType : uint8_t // PeerToPeer = 0x04 // For Future Use, not yet implemented // }; -enum class CardType : uint8_t - { - T1T, - T2T, - T3T, - ISO_DEP, - NFC_DEP - }; +//enum class CardType : uint8_t +// { +// T1T, +// T2T, +// T3T, +// ISO_DEP, +// NFC_DEP +// }; // ------------------------------------------------------------------------------------------ @@ -338,8 +332,9 @@ enum class CardType : uint8_t // ------------------------------------------------------------------------------------------ enum class NciState : uint8_t -// Note : Rfc means "Ready For Command" : a state where you would typically send and NCI command from the DH to the NFC -// Wfr means "Waiting For Response" : a state where you would typically wait for a response from the NFC to the DH +// Note : Rfc means "Ready For Command" : a state where you would (typically) send an NCI command from the DH to the NFC +// Wfr means "Waiting For Response" : a state where you would wait for a response from the NFC to the DH +// Wfn means "Waiting For Notification" : a state where you would wait for a notification from the NFC to the DH { HwResetRfc, // you start in this state after a hardware reset of the PN7150, then you send the CORE_RESET_CMD HwResetWfr, // waiting for CORE_RESET_RSP @@ -356,49 +351,67 @@ enum class NciState : uint8_t RfPollActive, // detected 1 card/tag, and activated it for reading/writing RfDeActivate1Wfr, // waiting for deactivation response, no notification will come (dactivation in RfWaitForHostSelect) - RfDeActivate2Wfr, // waiting for deactivation response, additionally a notification will come (deactivation in RfPollActive) - RfDeActivate2Wfn, // waiting for deactivation notifiation - Error, + RfDeActivate2Wfr, // waiting for deactivation response, additionally a notification will come (deactivation in RfPollActive) + RfDeActivate2Wfn, // waiting for deactivation notifiation + Error, End }; +enum class NciError : uint8_t + { + responseNOK, // we received a response with somethin wrong in it, eg Status_NOK + responseTimeout, // we did not receive a response in time + none + }; + +enum class TagsPresentStatus : uint8_t + { + unknown, + noTagsPresent, + singleTagPresent, + multipleTagsPresent + }; + class NCI { public: - NCI(PN7150Interface &theHardwareInterface); // Constructor, with mode default set to CardReadwrite - void initialize(); // See NCI specification V1.0, section 4.1 & 4.2 - void registrate(NFCReaderWriter *aReaderWriter); - void run(); // runs the NCI stateMachine - void deActivate(NciRfDeAcivationMode theMode); // moves the StateMachine from PollActive or WaitingForHostSelect back into Idle - NciState getState(); // find out in which state the NCI stateMachine is + NCI(PN7150Interface &theHardwareInterface); // Constructor, with mode default set to CardReadwrite + void initialize(); // See NCI specification V1.0, section 4.1 & 4.2 + void run(); // runs the NCI stateMachine + void activate(); // moves the StateMachine from Idle to Discover and starts the polling + void deActivate(NciRfDeAcivationMode theMode); // moves the StateMachine from PollActive or WaitingForHostSelect back into Idle + NciState getState() const; // find out in which state the NCI stateMachine is + TagsPresentStatus getTagsPresentStatus() const; // read-only get function for the (private) property + uint8_t getNmbrOfTags() const; + Tag* getTag(uint8_t index); // TODO : improve this with 'const' so the Tag properties are read-only private: - PN7150Interface &theHardwareInterface; // reference to the object handling the hardware interface - NFCReaderWriter *theReaderWriter = nullptr; // pointer to the object implementing the ReaderWriter application - -// NFCTagEmulator &theTagEmulator = nullptr; // For future extension -// NFCPeer2Peer &thePeer2Peer = nullptr; // For future extension - - NciState theState; // keeps track of the state of the NCI stateMachine - FSM - unsigned long timeOut; // keeps track of time-outs when waiting for responses from the NFC device - unsigned long timeOutStartTime; // keeps track of time-outs when waiting for responses from the NFC device - uint8_t rxBuffer[MaxPayloadSize + MsgHeaderSize]; // buffer where we store bytes received until they form a complete message - uint32_t rxMessageLength; // length of the last message received. As these are not 0x00 terminated, we need to remember the length - uint8_t txBuffer[MaxPayloadSize + MsgHeaderSize]; // buffer where we store the message to be transmitted - void sendMessage(uint8_t messageType, uint8_t groupId, uint8_t opcodeId, uint8_t payloadData[], uint8_t payloadLength); - void sendMessage(uint8_t messageType, uint8_t groupId, uint8_t opcodeId); // Variant for msg with no payload - void getMessage(); // read message from I2C into rxBuffer - bool isMessageType(uint8_t messageType, uint8_t groupId, uint8_t opcodeId); // Is the msg in the rxBuffer of this type ? - void setTimeOut(unsigned long); - bool isTimeOut(); + PN7150Interface &theHardwareInterface; // reference to the object handling the hardware interface - public: -// void test001(); -// void test002(); + NciState theState; // keeps track of the state of the NCI stateMachine - FSM + TagsPresentStatus theTagsStatus; // how many Tag/Cards are currently present - }; + unsigned long timeOut; // keeps track of time-outs when waiting for responses from the NFC device + unsigned long timeOutStartTime; // keeps track of time-outs when waiting for responses from the NFC device + + uint8_t rxBuffer[MaxPayloadSize + MsgHeaderSize]; // buffer where we store bytes received until they form a complete message + uint32_t rxMessageLength; // length of the last message received. As these are not 0x00 terminated, we need to remember the length + uint8_t txBuffer[MaxPayloadSize + MsgHeaderSize]; // buffer where we store the message to be transmitted + void sendMessage(uint8_t messageType, uint8_t groupId, uint8_t opcodeId, uint8_t payloadData[], uint8_t payloadLength); + void sendMessage(uint8_t messageType, uint8_t groupId, uint8_t opcodeId); // Variant for msg with no payload + void getMessage(); // read message from I2C into rxBuffer + bool isMessageType(uint8_t messageType, uint8_t groupId, uint8_t opcodeId) const; // Is the msg in the rxBuffer of this type ? + void setTimeOut(unsigned long); // set a timeOut for an expected next event, eg reception of Response after sending a Command + bool isTimeOut() const; // Chech if we have exceeded the timeOut + + static constexpr unsigned long scanPeriod = 1000; // lenght of a scan for tags cycle, in milliseconds. Note : setting this to very short times, eg. < 100 ms will not work, because the NFC discovery loop has a certain minumum constrained by the HW protocols + static constexpr uint8_t maxNmbrTags = 3; // maximum number of (simultaneously present) tags we can keep track of. PN7150 is limited to 3 + Tag theTags[maxNmbrTags]; // array to store the data of a number of currently present tags. When uniqueIdLenght == 0 it means invalid data in this position of the array + uint8_t nmbrOfTags = 0; // how many tags are actually in the array + void saveTag(uint8_t msgType); + }; #endif // End Header Guard diff --git a/src/NFCReaderWriter.cpp b/src/NFCReaderWriter.cpp index 5539396..8316edb 100644 --- a/src/NFCReaderWriter.cpp +++ b/src/NFCReaderWriter.cpp @@ -8,67 +8,144 @@ // ### ### // ############################################################################# + #include "NFCReaderWriter.h" #include "NCI.h" -NFCReaderWriter::NFCReaderWriter(NCI &theNCI) : theNCI(theNCI) + +NFCReaderWriter::NFCReaderWriter(NCI &theNCI) : theNCI(theNCI), theState(ReaderWriterState::initializing) { } void NFCReaderWriter::initialize() { - theNCI.initialize(); // initialize the NCI stateMachine and other. Will in its turn initialize the HW interface - theNCI.registrate(this); // registrate this NFCReaderWriter instance, so the NCI can callback in case of events.. - - // further initialize the reader/writer application if needed - for (uint8_t index = 0; index < maxNmbrTags; index++) - { - theTags[index].uniqueIdLength = 0; - } + theNCI.initialize(); // initialize the NCI stateMachine and other. Will in its turn initialize the HW interface + theState = ReaderWriterState::initializing; + Serial.println("NFC ReaderWriter initialised"); } void NFCReaderWriter::run() { theNCI.run(); - } - -void NFCReaderWriter::reportTagProperties(uint8_t msgBuffer[], uint8_t msgType) - { - // Store the properties of detected TAGs in the Tag array. - // Tag info can come in two different NCI messages : RF_DISCOVER_NTF and RF_INTF_ACTIVATED_NTF - - if (nmbrOfTags < maxNmbrTags) + switch (theState) { - uint8_t offSet; // Offset in the NCI message where we can find the UniqueID - switch (msgType) + case ReaderWriterState::initializing: { - case RF_INTF_ACTIVATED_NTF: - offSet = 12; + switch (theNCI.getState()) + { + case NciState::RfIdleCmd: + { + theNCI.activate(); + theState = ReaderWriterState::noTagPresent; + Serial.println("Polling activated"); + } break; + default: + break; + } + } + break; + + case ReaderWriterState::noTagPresent: + { + switch (theNCI.getState()) + { + case NciState::RfWaitForHostSelect: + { + theState = ReaderWriterState::multipleTagsPresent; - case RF_DISCOVER_NTF: - offSet = 9; + Tag* tmpTag = nullptr; + uint8_t nmbrTags = theNCI.getNmbrOfTags(); + Serial.print(nmbrTags); + Serial.println(" Tags detected :"); + + for (uint8_t index = 0; index < nmbrTags; index++) + { + Serial.print(" Tag["); + Serial.print(index); + Serial.print("] : "); + tmpTag = theNCI.getTag(index); + tmpTag->print(); + Serial.println(""); + } + } break; + case NciState::RfPollActive: + { + theState = ReaderWriterState::singleTagPresent; + Serial.println("Single Tag detected :"); - default: - return; // unknown type of msg sent here ?? we just ignore it.. + Tag* tmpTag = nullptr; + Serial.print(" Tag[0] : "); + tmpTag = theNCI.getTag(0); + tmpTag->print(); + Serial.println(""); + } break; + } } + break; - uint8_t NfcId1Length = msgBuffer[offSet]; - if (NfcId1Length > 10) + case ReaderWriterState::singleTagPresent: { - NfcId1Length = 10; // limit the length to 10, so in case of whatever error we don't write beyond the boundaries of the array + switch (theNCI.getState()) + { + case NciState::RfIdleCmd: + theNCI.activate(); + break; + + case NciState::RfDiscovery: + if (theNCI.getTagsPresentStatus() == TagsPresentStatus::noTagsPresent) + { + theState = ReaderWriterState::noTagPresent; + Serial.println("Tag removed"); + } + break; + + case NciState::RfWaitForHostSelect: + theNCI.deActivate(NciRfDeAcivationMode::IdleMode); + break; + + case NciState::RfPollActive: + theNCI.deActivate(NciRfDeAcivationMode::IdleMode); + break; + } } - uint8_t newTagIndex = nmbrOfTags; // index to the array item where we will store the info + break; - theTags[newTagIndex].uniqueIdLength = NfcId1Length; // copy the length of the unique ID, is 4, 7 or 10 - for (uint8_t index = 0; index < NfcId1Length; index++) // copy all bytes of the unique ID + case ReaderWriterState::multipleTagsPresent: { - theTags[newTagIndex].uniqueId[index] = msgBuffer[offSet + 1 +index]; + switch (theNCI.getState()) + { + case NciState::RfIdleCmd: + theNCI.activate(); + break; + + case NciState::RfDiscovery: + if (theNCI.getTagsPresentStatus() == TagsPresentStatus::noTagsPresent) + { + theState = ReaderWriterState::noTagPresent; + Serial.println("Tags removed"); + } + break; + + case NciState::RfWaitForHostSelect: + theNCI.deActivate(NciRfDeAcivationMode::IdleMode); + break; + + case NciState::RfPollActive: + theNCI.deActivate(NciRfDeAcivationMode::IdleMode); + break; + } } - theTags[newTagIndex].detectionTimestamp = millis(); + break; + } - nmbrOfTags++; // one more tag in the array now + if (theNCI.getState() == NciState::Error) + { + Serial.println("Error : Re-initializing NCI"); + theNCI.initialize(); // initialize the NCI stateMachine and other. Will in its turn initialize the HW interface } } + + diff --git a/src/NFCReaderWriter.h b/src/NFCReaderWriter.h index 1dc4ac3..6475ad0 100644 --- a/src/NFCReaderWriter.h +++ b/src/NFCReaderWriter.h @@ -11,39 +11,31 @@ #ifndef NFCReaderWriter_h // Header Guard #define NFCReaderWriter_h + #include // Gives us access to all typical Arduino types and functions -class NCI; -//#include "NCI.h" -// Class to hold the data of an NFC tag -class Tag +class NCI; // Forward declaration + +enum class ReaderWriterState : uint8_t { - public: - uint8_t uniqueIdLength = 0; // How long is the NFCID1 of the tag. Can be 4, 7 or 10 bytes. Typically 4 or 7. - uint8_t uniqueId[10]; // array to store the NFCID1. Maximum length is 10 bytes at this time.. - unsigned long detectionTimestamp; // remembers the time at which the tag was detected + initializing, + noTagPresent, + singleTagPresent, + multipleTagsPresent }; - class NFCReaderWriter { - public: - static constexpr unsigned long scanPeriod = 1000; // lenght of a scan for tags cycle, in milliseconds. Note : setting this to very short times, eg. < 100 ms will not work, because the NFC discovery loop has a certain minumum constrained by the HW protocols - static constexpr uint8_t maxNmbrTags = 4; // maximum number of (simultaneously present) tags we can keep track of. PN7150 is limited to 3 - Tag theTags[maxNmbrTags]; // array to store the data of a number of currently present tags. When uniqueIdLenght == 0 it means invalid data in this position of the array - uint8_t nmbrOfTags = 0; // how many tags are actually in the array - private: - NCI &theNCI; // reference to the NCI instance being used to talk to the NFC device + NCI &theNCI; // reference to the NCI instance being used to talk to the NFC device + ReaderWriterState theState; public: - NFCReaderWriter(NCI &theNCI); // constructor - void initialize(); // initialize the Reader/Writer application. Will in its turn initialize the NCI layer and the HW interface on which the application relies + NFCReaderWriter(NCI &theNCI); // constructor + void initialize(); // initialize the Reader/Writer application. Will in its turn initialize the NCI layer and the HW interface on which the application relies void run(); - void reportTagProperties(uint8_t msgBuffer[], uint8_t msgType); // takes the notification message in the msgBuffer, interprete it, extracts the TAG info from it and store this info in theTags[] array. }; - #endif diff --git a/src/PN7150Interface.cpp b/src/PN7150Interface.cpp index 95bbf96..5b6cb71 100644 --- a/src/PN7150Interface.cpp +++ b/src/PN7150Interface.cpp @@ -21,27 +21,26 @@ PN7150Interface::PN7150Interface(uint8_t IRQpin, uint8_t VENpin, uint8_t I2Caddr // Constructor, initializing IRQpin and VENpin and initializing I2Caddress to a custom value } - void PN7150Interface::initialize(void) { pinMode(IRQpin, INPUT); // IRQ goes from PN7150 to DeviceHost, so is an input pinMode(VENpin, OUTPUT); // VEN controls the PN7150's mode, so is an output - // PN7150 Reset procedure : see datasheet 12.6.1 and 12.6.2.2 - digitalWrite(VENpin, LOW); // drive VEN LOW for at least 0.5 ms after power came up : datasheet table 16.2.3 - delay(100); // don't know how long this should be, so I take 100ms and TODO : search for timing in PN7150 datasheet + // PN7150 Reset procedure : see PN7150 datasheet 12.6.1, 12.6.2.2, Fig 18 and 16.2.2 + digitalWrite(VENpin, LOW); // drive VEN LOW... + delay(1); // ...for at least 10us digitalWrite(VENpin, HIGH); // then VEN HIGH again, and wait for 2.5 ms for the device to boot and allow communication delay(3); Wire.begin(); // Start I2C interface } -bool PN7150Interface::hasMessage() +bool PN7150Interface::hasMessage() const { return (HIGH == digitalRead(IRQpin)); // PN7150 indicates it has data by driving IRQ signal HIGH } -uint8_t PN7150Interface::write(uint8_t txBuffer[], uint32_t txBufferLevel) +uint8_t PN7150Interface::write(uint8_t txBuffer[], uint32_t txBufferLevel) const { Wire.beginTransmission(I2Caddress); // Setup I2C to transmit uint32_t nmbrBytesWritten = 0; @@ -58,7 +57,7 @@ uint8_t PN7150Interface::write(uint8_t txBuffer[], uint32_t txBufferLevel) } } -uint32_t PN7150Interface::read(uint8_t rxBuffer[]) +uint32_t PN7150Interface::read(uint8_t rxBuffer[]) const { uint32_t bytesReceived; // keeps track of how many bytes we actually received if (hasMessage()) // only try to read something if the PN7150 indicates it has something diff --git a/src/PN7150Interface.h b/src/PN7150Interface.h index 4d54761..121a858 100644 --- a/src/PN7150Interface.h +++ b/src/PN7150Interface.h @@ -11,21 +11,28 @@ // ### ### // ############################################################################# - // Summary : // This library implements the hardware interface for the NXP PN7150 NFC device. // The PN7150 uses I2C plus 2 additional data signals : IRQ and VEN -// IRQ : -// VEN : +// IRQ : output of the PN7150, input for the MCU. Through this signal the PN7150 signals it has data to be read by the MCU +// VEN : input of the PN7150, output for the MCU. Through this signal the MCU can RESET the PN7150 // // The library provides : // * init() : Initialization of the interface -// * read() -// * write() -// * hasMessage() +// * read() : Read message from PN7150 over I2C +// * write() : Write message to PN7150 over I2C +// * hasMessage() : Check if PN7150 has message waiting for MCU #include // Gives us access to all typical Arduino types and functions -#include // the HW interface between The PN7150 and the DeviceHost is I2C, so we need the I2C library. As I'm developing this on Teensy3.2, i use that I2C library + // The HW interface between The PN7150 and the DeviceHost is I2C, so we need the I2C library.library +#if defined(TEENSYDUINO) && defined(KINETISK) // Teensy 3.0, 3.1, 3.2, 3.5, 3.6 : Special, more optimized I2C library for Teensy boards +#include // Credits Brian "nox771" : see https://forum.pjrc.com/threads/21680-New-I2C-library-for-Teensy3 +#else +#include // Otherwise, just use the more standard Wire.h - For ESP32 this will link in a version dedicated for this MCU + // TODO : i2c_t3.h ensures a maximum I2C message of 259, which is sufficient. Other I2C implementations have shorter buffers (32 bytes) + // See : https://github.com/Strooom/PN7150/issues/7 +#endif + class PN7150Interface { @@ -33,9 +40,9 @@ class PN7150Interface PN7150Interface(uint8_t IRQpin, uint8_t VENpin); // Constructor with default I2C address PN7150Interface(uint8_t IRQpin, uint8_t VENpin, uint8_t I2Caddress); // Constructor with custom I2C address void initialize(void); // Initialize the HW interface at the Device Host - uint8_t write(uint8_t data[], uint32_t dataLength); // write data from DeviceHost to PN7150. Returns success (0) or Fail (> 0) - uint32_t read(uint8_t data[]); // read data from PN7150 - bool hasMessage(); // does the PN7150 indicate it has data for the DeviceHost to be read + uint8_t write(uint8_t data[], uint32_t dataLength) const; // write data from DeviceHost to PN7150. Returns success (0) or Fail (> 0) + uint32_t read(uint8_t data[]) const; // read data from PN7150, returns the amount of bytes read + bool hasMessage() const; // does the PN7150 indicate it has data for the DeviceHost to be read private: uint8_t IRQpin; // MCU pin to which IRQ is connected diff --git a/src/Tag.cpp b/src/Tag.cpp new file mode 100644 index 0000000..a3f4c9e --- /dev/null +++ b/src/Tag.cpp @@ -0,0 +1,32 @@ +// ############################################################################# +// ### ### +// ### NXP PN7150 Driver ### +// ### ### +// ### https://github.com/Strooom/PN7150 ### +// ### Author(s) : Pascal Roobrouck - @strooom ### +// ### License : https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode ### +// ### ### +// ############################################################################# + + +#include "Tag.h" + + +void Tag::print() const + { + Serial.print("UniqueID = "); + for (uint8_t index = 0; index < uniqueIdLength; index++) + { + uint8_t theValue = uniqueId[index]; + if (theValue < 16) + { + Serial.print("0x0"); + } + else + { + Serial.print("0x"); + } + Serial.print(theValue, HEX); + Serial.print(" "); + } + } diff --git a/src/Tag.h b/src/Tag.h new file mode 100644 index 0000000..bff933a --- /dev/null +++ b/src/Tag.h @@ -0,0 +1,34 @@ +#ifndef Tag_h // Header Guard +#define Tag_h + +// ############################################################################# +// ### ### +// ### NXP PN7150 Driver ### +// ### ### +// ### https://github.com/Strooom/PN7150 ### +// ### Author(s) : Pascal Roobrouck - @strooom ### +// ### License : https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode ### +// ### ### +// ### Credits : Thomas Buhot, for his PN7120 library, the predecessor of ### +// ### the PN7150. Due to the NFC Specs not being available for free, I used ### +// ### his library to reverse-engineer some of the missing information links ### +// ### ### +// ############################################################################# + + +#include // Gives us access to all typical Arduino types and functions + + +class Tag + { + public: + uint8_t uniqueIdLength = 0; // How long is the NFCID1 of the tag. Can be 4, 7 or 10 bytes. Typically 4 or 7. + uint8_t uniqueId[10]; // array to store the NFCID1. Maximum length is 10 bytes at this time.. + unsigned long detectionTimestamp; // remembers the time at which the tag was detected + + public: + void print() const; // prints all properties of the tag to Serial + }; + + +#endif // End Header Guard