/*
* James Harold, 2025, Scion
*
* This program measures resistance on two separate analog channels
* and writes the results to an NFC tag for wireless reading.
*
*/
// 1. Library Imports
#include "board.h"
#include "ndeft2t/ndeft2t.h"
#include <string.h>
#include "pmu_nss.h"
#include "gpio_nss.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 2. Defines
#define CHANNEL_0 0 // Capacitive & resistive channel
#define CHANNEL_1 1 // Resistive channel
#define NUM_CHANNELS 2 // Number of total measurement channels
// 3. Global Variables
// Buffers for NDEF message creation (for NFC)
static uint8_t g_ndeft2tInstanceBuffer[NDEFT2T_INSTANCE_SIZE] __attribute__((aligned (4)));
static uint8_t g_nfcMessageBuffer[NFC_SHARED_MEM_BYTE_SIZE] __attribute__((aligned (4)));
// Array to store the results for each measurement channel
volatile int32_t measuredResistance[NUM_CHANNELS] = {0, 0};
// 4. NFC Callbacks (Required by the library, but unused)
void NDEFT2T_FieldStatus_Cb(bool status) { (void)status; }
void NDEFT2T_MsgAvailable_Cb(void) { /* Unused */ }
// 5. Function Prototypes
void SetupPowerStabilisation(void);
void SetupResistiveMeasurement(int channel);
int32_t PerformAndCalculateResistance(int channel);
void WriteMeasurementsToNFC(int32_t res1, int32_t res2);
void WriteHelloMessageToNFC(void);
// 6. Main Code
int main(void) {
// Step 1: Perform minimal board and power setup
Board_Init();
SetupPowerStabilisation(); // Must be performed early for energy harvesting from NFC tag
// --- Enable Clocks and Power for peripherals that will be used ---
Chip_Clock_Peripheral_EnableClock(CLOCK_PERIPHERAL_ADCDAC | CLOCK_PERIPHERAL_I2D);
Chip_SysCon_Peripheral_EnablePower(SYSCON_PERIPHERAL_POWER_ADCDAC | SYSCON_PERIPHERAL_POWER_I2D);
// A small delay for power and clocks to stabilise
Chip_Clock_System_BusyWait_us(100);
// --- Resistance Measurement Initialisation ---
// Initialise the peripherals and configure the analog pins
Chip_ADCDAC_Init(NSS_ADCDAC0);
Chip_I2D_Init(NSS_I2D);
// Configure analog pins for Channel 0 (ANA0_0, ANA0_1)
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_ANA0_0, IOCON_FUNC_1);
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_ANA0_1, IOCON_FUNC_1);
// Configure analog pins for Channel 1 (ANA0_4, ANA0_5)
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_ANA0_4, IOCON_FUNC_1);
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_ANA0_5, IOCON_FUNC_1);
// Set the ADC to a narrow voltage range (1.0V)
Chip_ADCDAC_SetInputRangeADC(NSS_ADCDAC0, ADCDAC_INPUTRANGE_NARROW);
// Step 2: Initialise NFC communication
Chip_NFC_Init(NSS_NFC);
NDEFT2T_Init();
// Step 3: Perform resistive measurements
SetupResistiveMeasurement(CHANNEL_0);
measuredResistance[CHANNEL_0] = PerformAndCalculateResistance(CHANNEL_0);
SetupResistiveMeasurement(CHANNEL_1);
measuredResistance[CHANNEL_1] = PerformAndCalculateResistance(CHANNEL_1);
// Step 4: Write measurements to the NFC tag
WriteMeasurementsToNFC(measuredResistance[CHANNEL_0], measuredResistance[CHANNEL_1]);
//WriteHelloMessageToNFC();
// --- Power down peripherals after use to save energy ---
Chip_SysCon_Peripheral_DisablePower(SYSCON_PERIPHERAL_POWER_ADCDAC | SYSCON_PERIPHERAL_POWER_I2D);
Chip_Clock_Peripheral_DisableClock(CLOCK_PERIPHERAL_ADCDAC | CLOCK_PERIPHERAL_I2D);
// Step 5: Wait for NFC peripheral interrupt
while(1) {
__WFI();
}
return 0; // Should never be reached
}
// 7. Function Declarations
/**
* @brief Configures hardware for a resistive measurement on a specific channel.
* @param channel : The measurement channel to set up (CHANNEL_0 or CHANNEL_1)
* @return Nothing
*/
void SetupResistiveMeasurement(int channel) {
ADCDAC_IO_T dac_pin;
I2D_INPUT_T i2d_pin;
if (channel == CHANNEL_1) {
dac_pin = ADCDAC_IO_ANA0_4;
i2d_pin = I2D_INPUT_ANA0_5;
}
else {
dac_pin = ADCDAC_IO_ANA0_0;
i2d_pin = I2D_INPUT_ANA0_1;
}
Chip_ADCDAC_SetMuxDAC(NSS_ADCDAC0, dac_pin);
Chip_ADCDAC_WriteOutputDAC(NSS_ADCDAC0, 0xFFF); // Changed from 0xFFF to 0x800 to lower DAC output voltage
Chip_I2D_SetMuxInput(NSS_I2D, i2d_pin);
if (channel == CHANNEL_0) {
Chip_I2D_Setup(NSS_I2D, I2D_SINGLE_SHOT, I2D_SCALER_GAIN_100_1, I2D_CONVERTER_GAIN_LOW, 10); // Changed I2D conversion period from 100 to 10 (power save)
}
else {
Chip_I2D_Setup(NSS_I2D, I2D_SINGLE_SHOT, I2D_SCALER_GAIN_100_1, I2D_CONVERTER_GAIN_LOW, 10); // Changed I2D conversion period from 100 to 10 (power save)
}
// Wait a moment for the voltage and current to stabilise.
Chip_Clock_System_BusyWait_ms(1);
}
/**
* @brief Initialises GPIOs to enable the capacitor bank for power stability.
* @return Nothing
* @note This is the most critical initialisation for passive operation.
*/
void SetupPowerStabilisation(void) {
Chip_IOCON_Init(NSS_IOCON);
Chip_GPIO_Init(NSS_GPIO);
// --- Phase 1: PRECHARGE ---
// Configure pins as INPUTS with internal pull-up resistors enabled.
// This allows the external caps to charge slowly without causing a voltage drop.
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_PIO0_3, IOCON_FUNC_0 | IOCON_RMODE_PULLUP); // Configure pin 3 as GPIO and enable pull-up
Chip_GPIO_SetPinDIRInput(NSS_GPIO, 0, 3); // Configure pin 3 as input
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_PIO0_7, IOCON_FUNC_0 | IOCON_RMODE_PULLUP); // Configure pin 7 as GPIO and enable pull-up
Chip_GPIO_SetPinDIRInput(NSS_GPIO, 0, 7); // Configure pin 7 as input
// Wait for a short time to allow the capacitors to charge.
Chip_Clock_System_BusyWait_ms(60); // Approx. 60ms for 4 time constants (each pin has 200nF attached)
// --- Phase 2: ENGAGE ---
// Now that the caps are charged, we can safely connect them to the power rail
// by setting the pins to OUTPUT and driving them HIGH.
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_PIO0_3, IOCON_FUNC_0 | IOCON_RMODE_INACT); // Configure pin 3 as GPIO and disable pull-up
Chip_GPIO_SetPinDIROutput(NSS_GPIO, 0, 3); // Configure pin 3 as output
Chip_GPIO_SetPinOutHigh(NSS_GPIO, 0, 3); // Connect pin 3 to VDD rail by setting it high
Chip_IOCON_SetPinConfig(NSS_IOCON, IOCON_PIO0_7, IOCON_FUNC_0 | IOCON_RMODE_INACT); // Configure pin 7 as GPIO and disable pull-up
Chip_GPIO_SetPinDIROutput(NSS_GPIO, 0, 7); // Configure pin 7 as output
Chip_GPIO_SetPinOutHigh(NSS_GPIO, 0, 7); // Connect pin 7 to VDD rail by setting it high
}
/**
* @brief Creates an NDEF message with the two resistance values and writes it to memory.
* @param res1 : The raw resistance value from the first channel.
* @param res2 : The raw resistance value from the second channel.
* @return Nothing
*/
void WriteMeasurementsToNFC(int32_t res1, int32_t res2) {
char payloadText[64];
NDEFT2T_CREATE_RECORD_INFO_T createRecordInfo;
uint8_t locale[] = "en";
snprintf(payloadText, sizeof(payloadText), "R1:%ld,R2:%ld", res1, res2);
NDEFT2T_CreateMessage(g_ndeft2tInstanceBuffer, g_nfcMessageBuffer,
NFC_SHARED_MEM_BYTE_SIZE, true);
createRecordInfo.shortRecord = 1;
createRecordInfo.pString = locale;
if (NDEFT2T_CreateTextRecord(g_ndeft2tInstanceBuffer, &createRecordInfo)) {
if (NDEFT2T_WriteRecordPayload(g_ndeft2tInstanceBuffer, (uint8_t*)payloadText,
strlen(payloadText))) {
NDEFT2T_CommitRecord(g_ndeft2tInstanceBuffer);
}
}
NDEFT2T_CommitMessage(g_ndeft2tInstanceBuffer);
}
/**
* @brief Creates a simple NDEF text message "hello" and writes it to NFC memory.
* @return Nothing
*/
void WriteHelloMessageToNFC(void) {
char payloadText[] = "bananas";
NDEFT2T_CREATE_RECORD_INFO_T createRecordInfo;
uint8_t locale[] = "en";
// 1. Start creating a new NDEF message in our buffer.
NDEFT2T_CreateMessage(g_ndeft2tInstanceBuffer, g_nfcMessageBuffer,
NFC_SHARED_MEM_BYTE_SIZE, true /* isFirstMessage */);
// 2. Prepare information for a new NDEF Text Record.
createRecordInfo.shortRecord = 1;
createRecordInfo.pString = locale;
// 3. Create the Text Record structure within the message.
if (NDEFT2T_CreateTextRecord(g_ndeft2tInstanceBuffer, &createRecordInfo)) {
// 4. Write the actual "hello" string into the record's payload.
if (NDEFT2T_WriteRecordPayload(g_ndeft2tInstanceBuffer, (uint8_t*)payloadText,
strlen(payloadText))) {
// 5. Finalise this specific record.
NDEFT2T_CommitRecord(g_ndeft2tInstanceBuffer);
}
}
// 6. Finalize the entire message, making it available to the NFC hardware.
NDEFT2T_CommitMessage(g_ndeft2tInstanceBuffer);
}
/**
* @brief Performs ADC and I2D conversions and calculates the resistance for a specific channel.
* @param channel : The measurement channel to measure (CHANNEL_0 or CHANNEL_1)
* @return A raw, scaled resistance value, or -1 if an error occurs (e.g., open circuit).
* @note Uses low-power __WFE() to wait for conversions, essential for passive mode.
*/
int32_t PerformAndCalculateResistance(int channel) {
int32_t v_drive, v_sense, i2d_val, adc_diff, resistance_result;
ADCDAC_IO_T drive_pin_adc, sense_pin_adc;
/*
* NOTE: Peripheral clocks and power are assumed to be enabled in main().
* If this function were called from multiple places, you might enable/disable
* them here instead. For this application, enabling in main() is more efficient.
*/
if (channel == CHANNEL_1) {
drive_pin_adc = ADCDAC_IO_ANA0_4;
sense_pin_adc = ADCDAC_IO_ANA0_5;
}
else {
drive_pin_adc = ADCDAC_IO_ANA0_0;
sense_pin_adc = ADCDAC_IO_ANA0_1;
}
// --- V_DRIVE ADC Measurement ---
Chip_ADCDAC_SetMuxADC(NSS_ADCDAC0, drive_pin_adc);
Chip_ADCDAC_StartADC(NSS_ADCDAC0);
// NEW, LOW-POWER WAY: Sleep until the ADC conversion is done.
while (!(Chip_ADCDAC_ReadStatus(NSS_ADCDAC0) & ADCDAC_STATUS_ADC_DONE)) {
{ /* wait */ }
}
v_drive = Chip_ADCDAC_GetValueADC(NSS_ADCDAC0);
// --- V_SENSE ADC Measurement ---
Chip_ADCDAC_SetMuxADC(NSS_ADCDAC0, sense_pin_adc);
Chip_ADCDAC_StartADC(NSS_ADCDAC0);
// Sleep until the ADC conversion is done.
while (!(Chip_ADCDAC_ReadStatus(NSS_ADCDAC0) & ADCDAC_STATUS_ADC_DONE)) {
{ /* wait */ }
}
v_sense = Chip_ADCDAC_GetValueADC(NSS_ADCDAC0);
// --- I2D Measurement ---
Chip_I2D_Start(NSS_I2D);
// Sleep until the I2D conversion is done.
while (!(Chip_I2D_ReadStatus(NSS_I2D) & I2D_STATUS_CONVERSION_DONE)) {
{ /* wait */ } // Wait For Event - wakes up on ADC_DONE event --> Replaced __WFE() with busy waits {}
}
i2d_val = Chip_I2D_GetValue(NSS_I2D);
/*
* NOTE: Peripherals are left powered on for the next measurement.
* They will be powered down at the end of main().
*/
// --- Calculation ---
adc_diff = v_drive - v_sense;
if (adc_diff < 1) {
adc_diff = 1;
}
if (i2d_val > 0) {
resistance_result = ((uint32_t)adc_diff * 10000) / i2d_val;
}
else {
resistance_result = -1; // Indicates open circuit or error
}
return resistance_result;
}
ntag2xx_read.ino:
/**************************************************************************/
/*!
@file readntag_continuous_power_and_data.ino
@author KTOWN (Adafruit Industries) / Modified for continuous power and data read
This version keeps a passive NFC tag continuously powered, and on each
loop, it reads and displays the data from a specific range of pages (8-13).
It resets when the tag is removed.
*/
/**************************************************************************/
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>
// If using the breakout with SPI, define the pins for SPI communication.
#define PN532_SCK (SCK)
#define PN532_MISO (MISO)
#define PN532_MOSI (MOSI)
#define PN532_SS (10) // Or whatever SPI CS pin you're using
// If using the breakout or shield with I2C, define the pins for I2C
// and comment out the SPI definitions.
#define PN532_IRQ (2)
#define PN532_RESET (3)
// Use this line for I2C
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);
// Or use this line for SPI
// Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS);
// Global state variables to track if a tag is currently selected
bool tagIsPresent = false;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
uint8_t uidLength;
int pageStart = 8;
int pageEnd = 12;
void setup(void) {
Serial.begin(115200);
while (!Serial) delay(10); // For Leonardo/Micro/Zero
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (!versiondata) {
Serial.print("Didn't find PN53x board");
while (1); // halt
}
Serial.print("Found chip PN5"); Serial.print((versiondata >> 24) & 0xFF, HEX);
Serial.print(", Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC);
Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC);
// Configure the board to listen for ISO14443A cards
nfc.SAMConfig();
Serial.println("Waiting for an ISO14443A Card to power and read...");
}
void loop(void) {
// === STATE 1: NO TAG IS SELECTED ===
// If we don't have a tag, try to find one.
if (!tagIsPresent) {
Serial.print("."); // Print a dot to show we are waiting
// Wait for a card to enter the field. This also provides power.
bool success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500);
if (success) {
tagIsPresent = true;
Serial.println("\n=================================================");
Serial.println("Tag Found and Activated! Preparing to read data.");
Serial.print(" UID Length: "); Serial.print(uidLength, DEC); Serial.println(" bytes");
Serial.print(" UID Value: ");
nfc.PrintHex(uid, uidLength);
// OPTIONAL DELAY: If your tag is a sensor that needs time to
// perform a measurement after power-up, add a delay here.
// 200ms is a good starting point for many sensor tags.
delay(200);
}
// If it fails or times out, the loop will just restart and try again.
return;
}
// === STATE 2: TAG IS SELECTED AND POWERED ===
// If we have a tag, attempt to read the data from pages 8 to 13.
if (tagIsPresent) {
Serial.println("----------------------------------------");
Serial.print("Reading data from pages ");
Serial.print(pageStart);
Serial.print("-");
Serial.print(pageEnd);
Serial.println();
bool all_reads_succeeded = true;
for (int page = pageStart; page <= pageEnd; page++) {
uint8_t page_data[4]; // NTAG pages are 4 bytes long
// Attempt to read the current page
if (nfc.ntag2xx_ReadPage(page, page_data)) {
// Success! Print the data.
Serial.print(" Page ");
if (page < 10) Serial.print("0"); // for nice alignment
Serial.print(page);
Serial.print(": ");
// nfc.PrintHexChar is a great helper function that prints hex and ASCII
nfc.PrintHexChar(page_data, 4);
} else {
// FAILED TO READ! This is our signal that the tag has been removed.
Serial.print(" Failed to read page "); Serial.println(page);
all_reads_succeeded = false;
break; // Exit the for loop immediately
}
}
if (all_reads_succeeded) {
Serial.println("Data read successfully. Tag is still present.");
} else {
// If any read failed, we reset the system state.
Serial.println("\n=================================================");
Serial.println("Tag lost or communication failed. Resetting...");
Serial.println("=================================================");
tagIsPresent = false; // Reset the state
nfc.SAMConfig(); // Re-configure the reader to listen for a new tag
}
// Wait a bit before the next read cycle.
delay(2000);
}
}