Skip to main content

README.md

# ST25R100

[![Hex.pm](https://img.shields.io/hexpm/v/st25r100.svg)](https://hex.pm/packages/st25r100)
[![Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/st25r100)

An Elixir driver for the **ST25R100** NFC reader chip.

This library supports a single, on-demand read of ISO14443A/NFC-A tags (both
4-byte single-size and 7-byte double-size UIDs) using a simple, pure functional
API. The driver state is maintained in a simple struct passed directly into
functions.

It can also read the full memory contents of **Type 2 Tags** (NTAG213/215/216,
Mifare Ultralight, and similar). Other tag types (e.g. Type 4 / ISO-DEP tags)
are not yet supported - support may be added in the future. Feel free to open
an issue if you need these features.

## Installation

The package can be installed by adding `st25r100` to your list of dependencies
in `mix.exs`:

```elixir
def deps do
  [
    {:st25r100, "~> 0.1.0"},
  ]
end
```

## Hardware Connections

The ST25R100 communicates over a 4-wire SPI interface. Connect it to a Raspberry
Pi's primary SPI bus (`spidev0.0` or `spidev0.1`) as follows, plus one free GPIO
for the chip's reset line:

| Raspberry Pi Pin            | ST25R100 Pin |
| --------------------------- | ------------ |
| GPIO10                      | MOSI         |
| GPIO9                       | MISO         |
| GPIO11                      | SCLK         |
| GPIO8 (CE0) / GPIO7 (CE1)   | BSS          |
| Any free GPIO (e.g. GPIO25) | RESET        |

> [!IMPORTANT]
> The SPI mode **must** be `mode: 1` (CPOL = 0, CPHA = 1). Any other mode will
> prevent the driver from communicating with the chip.

## Quick Start Example

You can initialize and configure the ST25R100 chip by creating and configuring a
`%ST25R100{}` struct. This example detects a tag, resolves its UID, and (since
`read_uid/1` only succeeds for Type 2 Tags) reads its full memory contents:

```elixir
# Open the SPI bus (assuming "spidev0.0" on Nerves target)
{:ok, spi} = Circuits.SPI.open("spidev0.0", mode: 1, speed_hz: 1_000_000)

# Instantiate the driver state with an optional reset GPIO pin
driver = ST25R100.new(spi, reset_pin: "GPIO25")

# Initialize the chip clocks and registers
{:ok, driver} = ST25R100.init(driver)

# Turn the RF field ON
{:ok, driver} = ST25R100.set_field(driver, true)

# Poll for an NFC-A tag's presence
case ST25R100.detect_tag(driver) do
  {:ok, atqa} ->
    IO.puts("Tag detected! ATQA: #{inspect(atqa, base: :hex)}")

    # Resolve the tag UID - only succeeds for Type 2 Tags (NTAG21x, Mifare Ultralight, etc)
    case ST25R100.read_uid(driver) do
      {:ok, uid} ->
        IO.puts("Tag UID: #{inspect(uid, base: :hex)}")

        # Read the tag's full memory contents
        case ST25R100.read_data(driver) do
          {:ok, data} ->
            IO.puts("Read #{byte_size(data)} bytes: #{inspect(data, base: :hex)}")

          {:error, reason} ->
            IO.puts("Failed to read tag data: #{inspect(reason)}")
        end

      {:error, :unsupported_tag} ->
        IO.puts("Tag detected, but it isn't a Type 2 Tag - not currently supported.")

      {:error, reason} ->
        IO.puts("Failed to resolve tag UID: #{inspect(reason)}")
    end

  {:error, :timeout} ->
    IO.puts("No tag present in the RF field.")

  {:error, reason} ->
    IO.puts("Failed to detect tag: #{inspect(reason)}")
end

# Turn the RF field OFF when done
{:ok, driver} = ST25R100.set_field(driver, false)

# Cleanly release opened GPIO resources on exit
ST25R100.close(driver)
```

## Hardware Enable and Reset Control

The ST25R100 chip has a dedicated hardware Enable/Reset pin controlling chip
power-down. If a reset pin is provided to `new/2` (highly recommended), the
driver automatically opens and toggles it via Circuits.GPIO.

## License

ST25R100 is released under the MIT License. See [LICENSE](LICENSE) for details.