Skip to content

Commit 10a66fc

Browse files
committed
feat(nfc): add HAL implementation
1 parent 0ec24a0 commit 10a66fc

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
2+
/**
3+
* [FIX-v3] Replaced spi_device_queue_trans + spi_device_get_trans_result
4+
* with spi_device_transmit (blocking, atomic).
5+
*
6+
* Problem with the previous approach (queue + get with timeout):
7+
*
8+
* 1. queue_trans() enqueues DMA and starts on the SPI bus
9+
* 2. get_trans_result(10ms) occasionally times out
10+
* 3. drain(portMAX_DELAY) saves from crash, BUT the transaction already
11+
* happened on the bus: the chip read REG_TARGET_INT and CLEARED
12+
* the IRQ register in hardware.
13+
* 4. We drop the WU_A / SDD_C values silently; software never sees
14+
* the phone even though the chip is working.
15+
*
16+
* spi_device_transmit() fixes this completely:
17+
* - Blocking with portMAX_DELAY internally never loses data.
18+
* - No timeout window between queue and get.
19+
* - Eliminates "spi wait fail" warnings.
20+
* - Simpler code, no intermediate states.
21+
*/
22+
#include "hb_nfc_spi.h"
23+
24+
#include <string.h>
25+
#include "driver/spi_master.h"
26+
#include "freertos/FreeRTOS.h"
27+
#include "esp_log.h"
28+
#include "esp_err.h"
29+
30+
static const char* TAG = "hb_spi";
31+
static spi_device_handle_t s_spi = NULL;
32+
static bool s_init = false;
33+
34+
static hb_nfc_err_t hb_spi_transmit(spi_transaction_t* t)
35+
{
36+
if (!s_spi || !t) return HB_NFC_ERR_SPI_XFER;
37+
38+
esp_err_t ret = spi_device_transmit(s_spi, t);
39+
if (ret != ESP_OK) {
40+
ESP_LOGE(TAG, "spi transmit fail: %s", esp_err_to_name(ret));
41+
return HB_NFC_ERR_SPI_XFER;
42+
}
43+
44+
return HB_NFC_OK;
45+
}
46+
47+
hb_nfc_err_t hb_spi_init(int spi_host, int mosi, int miso, int sclk,
48+
int cs, int mode, uint32_t clock_hz)
49+
{
50+
if (s_init) return HB_NFC_OK;
51+
52+
spi_bus_config_t bus = {
53+
.mosi_io_num = mosi,
54+
.miso_io_num = miso,
55+
.sclk_io_num = sclk,
56+
.quadwp_io_num = -1,
57+
.quadhd_io_num = -1,
58+
};
59+
esp_err_t ret = spi_bus_initialize(spi_host, &bus, SPI_DMA_CH_AUTO);
60+
if (ret != ESP_OK) {
61+
ESP_LOGE(TAG, "bus init fail: %s", esp_err_to_name(ret));
62+
return HB_NFC_ERR_SPI_INIT;
63+
}
64+
65+
spi_device_interface_config_t dev = {
66+
.clock_speed_hz = clock_hz,
67+
.mode = mode,
68+
.spics_io_num = cs,
69+
.queue_size = 1,
70+
.cs_ena_pretrans = 1,
71+
.cs_ena_posttrans = 1,
72+
};
73+
ret = spi_bus_add_device(spi_host, &dev, &s_spi);
74+
if (ret != ESP_OK) {
75+
ESP_LOGE(TAG, "add device fail: %s", esp_err_to_name(ret));
76+
spi_bus_free(spi_host);
77+
return HB_NFC_ERR_SPI_INIT;
78+
}
79+
80+
s_init = true;
81+
ESP_LOGI(TAG, "SPI OK: mode=%d clk=%lu cs=%d", mode, (unsigned long)clock_hz, cs);
82+
return HB_NFC_OK;
83+
}
84+
85+
void hb_spi_deinit(void)
86+
{
87+
if (!s_init) return;
88+
if (s_spi) {
89+
spi_bus_remove_device(s_spi);
90+
s_spi = NULL;
91+
}
92+
s_init = false;
93+
}
94+
95+
hb_nfc_err_t hb_spi_reg_read(uint8_t addr, uint8_t* value)
96+
{
97+
uint8_t tx[2] = { (uint8_t)(0x40 | (addr & 0x3F)), 0x00 };
98+
uint8_t rx[2] = { 0 };
99+
spi_transaction_t t = {
100+
.length = 16,
101+
.tx_buffer = tx,
102+
.rx_buffer = rx,
103+
};
104+
hb_nfc_err_t err = hb_spi_transmit(&t);
105+
if (err != HB_NFC_OK) {
106+
*value = 0;
107+
return err;
108+
}
109+
*value = rx[1];
110+
return HB_NFC_OK;
111+
}
112+
113+
hb_nfc_err_t hb_spi_reg_write(uint8_t addr, uint8_t value)
114+
{
115+
uint8_t tx[2] = { (uint8_t)(addr & 0x3F), value };
116+
spi_transaction_t t = {
117+
.length = 16,
118+
.tx_buffer = tx,
119+
};
120+
return hb_spi_transmit(&t);
121+
}
122+
123+
hb_nfc_err_t hb_spi_reg_modify(uint8_t addr, uint8_t mask, uint8_t value)
124+
{
125+
uint8_t cur;
126+
hb_nfc_err_t err = hb_spi_reg_read(addr, &cur);
127+
if (err != HB_NFC_OK) return err;
128+
uint8_t nv = (cur & (uint8_t)~mask) | (value & mask);
129+
return hb_spi_reg_write(addr, nv);
130+
}
131+
132+
hb_nfc_err_t hb_spi_fifo_load(const uint8_t* data, size_t len)
133+
{
134+
if (!data || len == 0 || len > 512) return HB_NFC_ERR_PARAM;
135+
136+
/* Static buffer: ST25R3916 FIFO is 512 bytes. Not re-entrant, but NFC
137+
* is single-threaded and DMA requires a stable (non-stack) pointer. */
138+
static uint8_t tx[1 + 512];
139+
tx[0] = 0x80;
140+
memcpy(&tx[1], data, len);
141+
142+
spi_transaction_t t = {
143+
.length = (uint32_t)((len + 1) * 8),
144+
.tx_buffer = tx,
145+
};
146+
return hb_spi_transmit(&t);
147+
}
148+
149+
hb_nfc_err_t hb_spi_fifo_read(uint8_t* data, size_t len)
150+
{
151+
if (!data || len == 0 || len > 512) return HB_NFC_ERR_PARAM;
152+
153+
static uint8_t tx[1 + 512];
154+
static uint8_t rx[1 + 512];
155+
memset(tx, 0, len + 1);
156+
tx[0] = 0x9F;
157+
158+
spi_transaction_t t = {
159+
.length = (uint32_t)((len + 1) * 8),
160+
.tx_buffer = tx,
161+
.rx_buffer = rx,
162+
};
163+
hb_nfc_err_t err = hb_spi_transmit(&t);
164+
if (err != HB_NFC_OK) return err;
165+
memcpy(data, &rx[1], len);
166+
return HB_NFC_OK;
167+
}
168+
169+
hb_nfc_err_t hb_spi_direct_cmd(uint8_t cmd)
170+
{
171+
spi_transaction_t t = {
172+
.length = 8,
173+
.tx_buffer = &cmd,
174+
};
175+
return hb_spi_transmit(&t);
176+
}
177+
178+
/**
179+
* PT Memory write ST25R3916 SPI protocol:
180+
* TX: [prefix_byte] [data0] [data1] ... [dataN]
181+
*
182+
* Prefix bytes:
183+
* 0xA0 = PT_MEM_A (NFC-A: ATQA/UID/SAK, 15 bytes max)
184+
* 0xA8 = PT_MEM_F (NFC-F: SC + SENSF_RES, 19 bytes total)
185+
* 0xAC = PT_MEM_TSN (12 bytes max)
186+
*/
187+
hb_nfc_err_t hb_spi_pt_mem_write(uint8_t prefix, const uint8_t* data, size_t len)
188+
{
189+
if (!data || len == 0 || len > 19) return HB_NFC_ERR_PARAM;
190+
191+
uint8_t tx[1 + 19];
192+
tx[0] = prefix;
193+
memcpy(&tx[1], data, len);
194+
195+
spi_transaction_t t = {
196+
.length = (uint32_t)((len + 1) * 8),
197+
.tx_buffer = tx,
198+
};
199+
return hb_spi_transmit(&t);
200+
}
201+
202+
/**
203+
* PT Memory read ST25R3916 SPI protocol:
204+
* TX: [0xBF] [0x00] [0x00] ... (len + 2 bytes total)
205+
* RX: [xx] [xx] [d0] ... (data starts at rx[2])
206+
*
207+
* The ST25R3916 inserts two bytes before the real data on MISO:
208+
* a command byte and a turnaround byte.
209+
*/
210+
hb_nfc_err_t hb_spi_pt_mem_read(uint8_t* data, size_t len)
211+
{
212+
if (!data || len == 0 || len > 19) return HB_NFC_ERR_PARAM;
213+
214+
uint8_t tx[2 + 19] = { 0 };
215+
uint8_t rx[2 + 19] = { 0 };
216+
tx[0] = 0xBF;
217+
218+
spi_transaction_t t = {
219+
.length = (uint32_t)((len + 2) * 8),
220+
.tx_buffer = tx,
221+
.rx_buffer = rx,
222+
};
223+
hb_nfc_err_t err = hb_spi_transmit(&t);
224+
if (err != HB_NFC_OK) return err;
225+
memcpy(data, &rx[2], len);
226+
return HB_NFC_OK;
227+
}
228+
229+
/**
230+
* Raw SPI transfer for arbitrary length transactions.
231+
*/
232+
hb_nfc_err_t hb_spi_raw_xfer(const uint8_t* tx, uint8_t* rx, size_t len)
233+
{
234+
if (len == 0 || len > 64) return HB_NFC_ERR_PARAM;
235+
236+
spi_transaction_t t = {
237+
.length = (uint32_t)(len * 8),
238+
.tx_buffer = tx,
239+
.rx_buffer = rx,
240+
};
241+
return hb_spi_transmit(&t);
242+
}
243+
244+
#include "hb_nfc_gpio.h"
245+
246+
#include "driver/gpio.h"
247+
#include "esp_log.h"
248+
#include "esp_rom_sys.h"
249+
250+
static const char* TAG_GPIO = "hb_gpio";
251+
static int s_pin_irq = -1;
252+
253+
hb_nfc_err_t hb_gpio_init(int pin_irq)
254+
{
255+
s_pin_irq = pin_irq;
256+
257+
gpio_config_t cfg = {
258+
.pin_bit_mask = 1ULL << pin_irq,
259+
.mode = GPIO_MODE_INPUT,
260+
.pull_up_en = GPIO_PULLUP_DISABLE,
261+
.pull_down_en = GPIO_PULLDOWN_DISABLE,
262+
.intr_type = GPIO_INTR_DISABLE,
263+
};
264+
esp_err_t ret = gpio_config(&cfg);
265+
if (ret != ESP_OK) {
266+
ESP_LOGE(TAG_GPIO, "IRQ pin %d config fail", pin_irq);
267+
return HB_NFC_ERR_GPIO;
268+
}
269+
270+
ESP_LOGI(TAG_GPIO, "IRQ pin %d OK, level=%d", pin_irq, gpio_get_level(pin_irq));
271+
return HB_NFC_OK;
272+
}
273+
274+
void hb_gpio_deinit(void)
275+
{
276+
if (s_pin_irq >= 0) {
277+
gpio_reset_pin(s_pin_irq);
278+
s_pin_irq = -1;
279+
}
280+
}
281+
282+
int hb_gpio_irq_level(void)
283+
{
284+
if (s_pin_irq < 0) return 0;
285+
return gpio_get_level(s_pin_irq);
286+
}
287+
288+
bool hb_gpio_irq_wait(uint32_t timeout_ms)
289+
{
290+
for (uint32_t i = 0; i < timeout_ms; i++) {
291+
if (gpio_get_level(s_pin_irq)) return true;
292+
esp_rom_delay_us(1000);
293+
}
294+
return false;
295+
}
296+
297+
#include "hb_nfc_timer.h"
298+
#include "esp_rom_sys.h"
299+
#include "freertos/task.h"
300+
301+
void hb_delay_us(uint32_t us)
302+
{
303+
esp_rom_delay_us(us);
304+
}
305+
306+
void hb_delay_ms(uint32_t ms)
307+
{
308+
/* Yield to the scheduler for multi-ms delays instead of busy-spinning.
309+
* Must be called from a FreeRTOS task context (not ISR). */
310+
vTaskDelay(pdMS_TO_TICKS(ms ? ms : 1));
311+
}

0 commit comments

Comments
 (0)