astra/iphone-encoder-counter

A CR2450-powered MSP430 FRAM counter combines an always-on dual-channel TMR rotation sensor, pushbutton input, and ST25DV NFC tag with an integrated tuned printed antenna and I²C/GPIO interface.

Version
1.0.3
License
unset
Stars
0

FIRMWARE.md

# Firmware integration contract

The portable counting and journal codec is implemented and host-tested. This file specifies the hardware port still required; it is not a claim that an MCU image or iPhone app has been built.

## MCU port

Use MSP430FR2433, internal clock at 1 MHz while active, no external crystal, ordinary LPM4 while idle. Stop the watchdog at startup or deliberately configure it for the chosen low-power operating scheme. Configure unused GPIO as defined outputs, then unlock the FRAM device's default GPIO high-impedance state. Keep analog/reference blocks off unless measuring. Verify actual SVS settings and sleep current together; the 1.5 µA MCU line is an allocation, not a guaranteed datasheet maximum over all conditions.

Read both sensor bits from P1IN in the same operation. SIN=P1.0 and COS=P1.1. Convert to `(SIN<<1)|COS` for counter_step(). Rearm interrupts for the opposite edge based on the current port state; MSP430 edge selection is not automatic both-edge detection. Re-read after updating edge selection/flags to close the race window. Never erase a real intervening edge while clearing flags.

Debounce state changes using two stable observations initially 100 µs apart. Sleep on a timer during the interval if needed. Sensor hysteresis and the transition table reject most boundary chatter, but timer/ISR code must be validated. Limit ISR work; keep NFC transfers interruptible. At the 20 rps target, nominal quadrant dwell is 12.5 ms. Confirm a large latency margin experimentally and verify accepted events' energy. If the state remains unstable, flag it and use a bounded retry/backoff instead of an unbounded current-draining loop.

Use the supplied 48-byte journal record in two dedicated, aligned FRAM slots placed in a NOINIT/persistent linker section excluded from C startup clearing. Keep constants and code away from these slots. On every accepted transition, update the inactive slot with the commit order in counter.h. Serialize commits; take NFC snapshots atomically. A CRC protects detection, not authenticity. Keep hardware brownout behavior enabled and validate power interruption with a real cell removal rig.

On cold boot, recover the newest valid journal, measure battery voltage, then counter_rebase() to the observed phase. Preserve a separate boot/gap indicator for the app. If both journal records are invalid, initialize totals and report the reset condition rather than silently presenting old continuity.

Use the MCU ADC/internal reference to estimate supply voltage without a permanent external resistor divider. Verify the exact ADC channel/reference configuration from the device family manual. Check battery at startup, on NFC requests and during movement at a bounded interval. Set a conservative 2.5 V stop threshold; characterize sag and tolerance. When below threshold, inhibit sensor counting interrupts, persist the status, and retain NFC-read wake capability. A later fresh-cell reboot re-enables counting.

## NFC power management

U3 is the 12-pin CMOS GPO version. Always power VDCG, but leave VCC off between accesses. Configure nonvolatile GPO RF_WRITE and GPO_EN; avoid RF_ACTIVITY/FIELD_CHANGE wake storms. V_EH remains disconnected. Set the MCU's I²C pins to high impedance without pull-ups before powering down; the external pull-ups are tied to the switched rail.

Implement the LPD/VCC sequence in DESIGN.md and ST AN5733. Check NFC_VCC at the tag during two simultaneous low I²C lines; R4 and the MCU output resistance contribute voltage drop. If it violates the NFC supply specification, replace GPIO power with an appropriately low-leakage switch in a later revision rather than bypassing validation.

Use documented I²C addresses/register definitions from the ST25DVxxKC datasheet. Do not hard-code addresses inferred from an unrelated ST25DV generation. Provision the NFC memory layout and GPO settings once at assembly, with a read-back check.

## Proposed EEPROM protocol (512-byte tag)

- Bytes 0–127: provisioned CC/NDEF data and application identification. Exact encoding/URI is selected with the app; no invented website is required.
- Bytes 128–143: RF-written request: four-byte magic, version/flags, 32-bit nonce, CRC32. Write the request-complete marker last. RF access must be allowed here.
- Bytes 160–223: snapshot slot A, 64 bytes.
- Bytes 224–287: snapshot slot B, 64 bytes.
- Remaining bytes: reserved. Configure appropriate RF write protection for response/configuration regions when implementing provisioning.

After a complete, valid request arrives, publish a snapshot into the inactive slot: protocol version, matching request nonce, monotonic sequence, CW, CCW and button-press uint64 totals, diagnostic count, battery millivolts, status, CRC32 and commit marker. Specify exact serialized offsets in the app/driver implementation and use little-endian integers. Clear commit first, write payload+CRC, wait for EEPROM programming completion, then commit last and wait again before removing VCC. Never write the tag EEPROM on every twist; FRAM is the continuous counter store.

The app polls for a committed, CRC-valid slot with the requested nonce. A prior snapshot is explicitly stale. Serialize MCU I²C access against RF access using the datasheet's arbitration behavior; handle NACK/busy/timeout with bounded retries. Do not depend on a fixed delay alone. Allow 500 ms in the UI initially, but target <50 µC battery charge per successful request. After publishing or a bounded timeout, shut the tag's VCC off and return the MCU to sleep. Keep GPO handling resilient to the app's own request writes and do not interpret response writes as new requests.

No arbitrary RF write is a counter reset command. Implement reset only if deliberately added to the product requirements.

## Host test

From this directory's parent:

```sh
cc -std=c11 -Wall -Wextra -Werror firmware/counter.c firmware/test_counter.c -o /tmp/fidget-counter-test
/tmp/fidget-counter-test
```

This checks logic and record formats on the host. It does not compile MSP430 peripherals, emulate interrupts or prove FRAM electrical behavior.

## Independent button counter

P2.4 reads BUTTON_N, active low. External R7=220 kΩ; disable the internal pull-up. C10=1 nF is an RF filter. SW1 pins 1–2 are one contact, 3–4 the other, per the ALPS circuit diagram. The switch's 10 µA minimum rating is met at 2.5 V with 220 kΩ. Configure interrupt wake on press/release, then use a low-power timer to sample during the 20 ms debounce interval. `button_update` must be called when the debounce interval expires even if no further edge arrives; it is not an edge-only API. Re-arm edge interrupts race-safely. Return to sleep while held; no busy polling.

Initialize debounce with the actual pin state. A button held at boot does not increment until released and pressed again. Persist every accepted press using the same serialized journal writer as rotations. Do not let an NFC snapshot interrupt a multiword 64-bit update. FDG2 records include the independent uint64 press count; the old FDG1 format is not accepted. Preserve the recovered press count through `counter_rebase`.