diff --git a/build-and-flash.sh b/build-and-flash.sh index 819fde3..a8838af 100755 --- a/build-and-flash.sh +++ b/build-and-flash.sh @@ -29,6 +29,12 @@ RTT_ENABLED=1 DO_GBL_ONLY=0 DO_CLEAN=0 DO_MASS_ERASE="${DO_MASS_ERASE:-0}" +NFC_BOOT_PROBE=0 +NFC_BOOT_PROBE_CLI=0 +NFC_WRITE_NDEF=0 +NFC_WRITE_NDEF_CLI=0 +NFC_NDEF_TEXT="" +NFC_NDEF_TEXT_CLI=0 usage() { cat < Configure + build (RTT enabled: $RTT_ENABLED)" + if [[ "$NFC_BOOT_PROBE" -eq 0 ]]; then + NFC_WRITE_NDEF=0 + fi + echo "==> Configure + build (RTT enabled: $RTT_ENABLED, TNB132M boot probe: $NFC_BOOT_PROBE, write ndef: $NFC_WRITE_NDEF${NFC_NDEF_TEXT:+ text=\"$NFC_NDEF_TEXT\"})" echo " Directory: $CMAKE_DIR" cfg=(--preset project) [[ "$RTT_ENABLED" -eq 0 ]] && cfg+=(-DOD_ENABLE_RTT=OFF) + if [[ "$NFC_BOOT_PROBE" -eq 1 ]]; then + cfg+=(-DOD_TNB132M_BOOT_PROBE=ON) + else + cfg+=(-DOD_TNB132M_BOOT_PROBE=OFF) + fi + if [[ "$NFC_WRITE_NDEF" -eq 1 ]]; then + cfg+=(-DOD_TNB132M_WRITE_NDEF=ON) + else + cfg+=(-DOD_TNB132M_WRITE_NDEF=OFF) + fi + if [[ -n "$NFC_NDEF_TEXT" ]]; then + cfg+=(-DOD_TNB132M_NDEF_TEXT="$NFC_NDEF_TEXT") + fi [[ -n "${OPENDISPLAY_BUILD_ID:-}" ]] && cfg+=(-DOPENDISPLAY_BUILD_ID="${OPENDISPLAY_BUILD_ID}") [[ -n "${OD_APP_VERSION:-}" ]] && cfg+=(-DOD_APP_VERSION="${OD_APP_VERSION}") [[ -n "${OD_SL_APPLICATION_VERSION:-}" ]] && cfg+=(-DOD_SL_APPLICATION_VERSION="${OD_SL_APPLICATION_VERSION}") diff --git a/cmake_gcc/CMakeLists.txt b/cmake_gcc/CMakeLists.txt index 0b1aeee..0f3204f 100644 --- a/cmake_gcc/CMakeLists.txt +++ b/cmake_gcc/CMakeLists.txt @@ -10,6 +10,9 @@ project( option(OD_ENABLE_RTT "Enable SEGGER RTT logging backend" ON) option(OD_GENERATE_GBL_OTA "After link, run Simplicity Commander to create OTA .gbl next to .s37" ON) +option(OD_TNB132M_BOOT_PROBE "At boot read TNB132M NDEF via Type-3 AI@0x48 + data@0x40 (log over RTT)" OFF) +option(OD_TNB132M_WRITE_NDEF "With BOOT_PROBE: before the read, write OD_TNB132M_NDEF_TEXT as a Text record" OFF) +set(OD_TNB132M_NDEF_TEXT "OpenDisplay" CACHE STRING "UTF-8 text (lang=en) written when OD_TNB132M_WRITE_NDEF=ON; max 25 chars") set(OD_APP_VERSION "0x0019" CACHE STRING "16-bit BLE app version (hex, high byte=major, low=minor)") set(OPENDISPLAY_BUILD_ID "bg22-dev" CACHE STRING "Firmware version string (GATT 0x0043 / build id)") @@ -22,6 +25,9 @@ include(opendisplay-bg22.cmake) # main.c and other app sources live in target `slc`; they must see OD_ENABLE_RTT when compiled. target_compile_definitions(slc PUBLIC OD_ENABLE_RTT=$,1,0> + OD_TNB132M_BOOT_PROBE=$,1,0> + OD_TNB132M_WRITE_NDEF=$,1,0> + "OD_TNB132M_NDEF_TEXT=\"${OD_TNB132M_NDEF_TEXT}\"" SL_APP_PROPERTIES=1 "OD_APP_VERSION=${OD_APP_VERSION}" "OPENDISPLAY_BUILD_ID=\"${OPENDISPLAY_BUILD_ID}\"" diff --git a/opendisplay_ble.c b/opendisplay_ble.c index a529c63..a630be6 100644 --- a/opendisplay_ble.c +++ b/opendisplay_ble.c @@ -7,6 +7,7 @@ #include "app_assert.h" #include "gatt_db.h" #include "em_cmu.h" +#include "em_core.h" #include "em_emu.h" #include "em_gpio.h" #include "em_iadc.h" @@ -37,12 +38,35 @@ #define PRESS_COUNT_MASK 0x0Fu #define PRESS_COUNT_SHIFT 3u #define BUTTON_STATE_SHIFT 7u +#define OD_MAX_CONNECTION_MS 300000u /* When system_config.pwr_pin is 0xFF, drive this pin HIGH as display rail enable (PA0). Set to 0xFF to disable. */ #ifndef OD_FALLBACK_DISPLAY_PWR_PIN #define OD_FALLBACK_DISPLAY_PWR_PIN 0x00u #endif +/* OD_TNB132M_BOOT_PROBE=1 (e.g. -DOD_TNB132M_BOOT_PROBE=1): read the phone-visible + * NDEF via TNB132M at boot and log it over RTT. Type-3 AI lives at I2C 0x48 sub=0; + * NDEF data blocks are byte-offset reads at I2C 0x40 sub=0x10/0x20/... The tag + * caches only AI+Nbr blocks per anchor (Nbr=2 here), so each data block read is + * preceded by a fresh 0x48 sub=0 anchor. + * + * OD_TNB132M_WRITE_NDEF=1 (requires BOOT_PROBE): before the read, write a Well-Known + * Text record containing OD_TNB132M_NDEF_TEXT (UTF-8, lang=en). Max 25 chars so the + * record fits in AI + 2 data blocks (Nbr=2 cache). Leaves WriteFlag=0x00 and RWFlag + * untouched so the NFC Forum "read/write" bit keeps its factory value. + * + * Power is still removed at end of od_nfc_init_sequence(). */ +#ifndef OD_TNB132M_BOOT_PROBE +#define OD_TNB132M_BOOT_PROBE 0 +#endif +#ifndef OD_TNB132M_WRITE_NDEF +#define OD_TNB132M_WRITE_NDEF 0 +#endif +#ifndef OD_TNB132M_NDEF_TEXT +#define OD_TNB132M_NDEF_TEXT "OpenDisplay" +#endif + static uint8_t msd_payload[MSD_PAYLOAD_LEN]; static uint8_t msd_loop_counter; static uint8_t dynamic_return[11]; @@ -58,12 +82,57 @@ static struct GlobalConfig s_od_global_config; static uint32_t s_last_msd_refresh_ms; static bool s_pending_dfu; static bool s_pending_deep_sleep; +static uint32_t s_connection_open_ms; +static bool s_connection_timeout_close_requested; static uint32_t s_last_batt_measure_ms; static uint16_t s_batt_voltage_mv_cache; + +static GPIO_Port_TypeDef s_od_flash_mosi_port = gpioPortA; +static uint8_t s_od_flash_mosi_pin = 0u; +static GPIO_Port_TypeDef s_od_flash_sck_port = gpioPortA; +static uint8_t s_od_flash_sck_pin = 0u; +static GPIO_Port_TypeDef s_od_flash_cs_port = gpioPortA; +static uint8_t s_od_flash_cs_pin = 0u; +static bool s_od_flash_enabled; + +/* NFC / bit-bang I2C lines: initialized only from explicit NFC config packet. */ +static GPIO_Port_TypeDef s_od_nfc_scl_port = gpioPortA; +static uint8_t s_od_nfc_scl_pin = 0u; +static GPIO_Port_TypeDef s_od_nfc_sda_port = gpioPortA; +static uint8_t s_od_nfc_sda_pin = 0u; +static GPIO_Port_TypeDef s_od_nfc_pwr_port = gpioPortA; +static uint8_t s_od_nfc_pwr_pin = 0u; +static bool s_od_nfc_has_pwr_pin; +static bool s_od_nfc_enabled; +static uint8_t s_od_nfc_ic_type; +static uint8_t s_od_nfc_power_on_delay_ms = 40u; + +typedef struct { + bool enabled; + uint8_t pin_cfg; + uint8_t mode; + bool active_high; + uint8_t debounce_ms; + uint8_t adv_button_byte_index; + uint8_t adv_button_id; + uint8_t state; + uint32_t last_change_ms; + int32_t int_no; + int32_t em4_int_no; + volatile bool irq_pending; +} od_nfc_field_detect_t; +static od_nfc_field_detect_t s_od_nfc_field_detect; +static void od_nfc_field_detect_irq_callback(uint8_t int_no, void *context); +static uint32_t s_od_nfc_field_scan_counter; +static uint8_t s_od_nfc_write_blocks[512]; +static uint8_t s_od_nfc_read_data[128]; +static uint8_t s_od_nfc_read_throwaway[16]; + typedef struct { bool active; uint8_t pin_cfg; int32_t int_no; + int32_t em4_int_no; uint8_t button_id; uint8_t byte_index; bool inverted; @@ -162,6 +231,10 @@ static void od_buttons_deinit_interrupts(void) (void)sl_gpio_deconfigure_external_interrupt(st->int_no); st->int_no = -1; } + if (st->active && st->em4_int_no >= 0) { + (void)sl_gpio_deconfigure_wakeup_em4_interrupt(st->em4_int_no); + st->em4_int_no = -1; + } } } @@ -221,6 +294,7 @@ static void od_buttons_init_from_config(void) st->byte_index = input->button_data_byte_index; st->inverted = (input->invert & (uint8_t)(1u << bi)) != 0u; st->int_no = -1; + st->em4_int_no = -1; pin_state_raw = od_read_button_pin(pin_cfg); pressed = st->inverted ? (pin_state_raw == 0u) : (pin_state_raw != 0u); st->current_state = pressed ? 1u : 0u; @@ -301,34 +375,37 @@ static bool od_process_button_event(uint32_t now_ms) return true; } -static void od_park_display_pins_from_config(void) +static void od_buttons_arm_em4_wakeup(void) { - const struct DisplayConfig *d; - GPIO_Port_TypeDef port; - uint8_t pin; - - if (s_od_global_config.display_count == 0u) { - return; - } - d = &s_od_global_config.displays[0]; + uint8_t i; + for (i = 0u; i < s_button_count; i++) { + od_button_state_t *st = &s_buttons[i]; + sl_gpio_t gpio; + int32_t em4_int_no = SL_GPIO_INTERRUPT_UNAVAILABLE; + sl_status_t sc; + bool wake_polarity_high; - if (od_pin_decode(d->cs_pin, &port, &pin)) { - GPIO_PinModeSet(port, pin, gpioModePushPull, 1); - } - if (od_pin_decode(d->data_pin, &port, &pin)) { - GPIO_PinModeSet(port, pin, gpioModeInputPull, 0); - } - if (od_pin_decode(d->clk_pin, &port, &pin)) { - GPIO_PinModeSet(port, pin, gpioModeInputPull, 0); - } - if (od_pin_decode(d->dc_pin, &port, &pin)) { - GPIO_PinModeSet(port, pin, gpioModeInputPull, 0); - } - if (od_pin_decode(d->reset_pin, &port, &pin)) { - GPIO_PinModeSet(port, pin, gpioModeInputPull, 1); - } - if (od_pin_decode(d->busy_pin, &port, &pin)) { - GPIO_PinModeSet(port, pin, gpioModeInputPull, 0); + if (!st->active) { + continue; + } + gpio.port = (sl_gpio_port_t)(st->pin_cfg >> 4); + gpio.pin = (st->pin_cfg & 0x0Fu); + wake_polarity_high = !st->inverted; + sc = sl_gpio_configure_wakeup_em4_interrupt(&gpio, + &em4_int_no, + wake_polarity_high, + od_button_irq_callback, + st); + if (sc == SL_STATUS_OK) { + st->em4_int_no = em4_int_no; + printf("[OD][BTN] EM4 wake armed pin=0x%02X em4_int=%ld polarity=%s\r\n", + (unsigned)st->pin_cfg, + (long)em4_int_no, + wake_polarity_high ? "high" : "low"); + } else { + printf("[OD][BTN] EM4 wake arm failed pin=0x%02X sc=0x%04lX\r\n", + (unsigned)st->pin_cfg, (unsigned long)sc); + } } } @@ -411,115 +488,1014 @@ static uint8_t od_nfc_i2c_read_byte(GPIO_Port_TypeDef scl_port, uint8_t scl_pin, return value; } -static void od_nfc_init_sequence(void) +static bool od_nfc_type3_paged_block_read16(GPIO_Port_TypeDef scl_port, uint8_t scl_pin, + GPIO_Port_TypeDef sda_port, uint8_t sda_pin, + uint8_t dev7, uint8_t sub, uint8_t *out16) +{ + bool a; + if (out16 == NULL) { + return false; + } + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + a = od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)(dev7 << 1)); + if (!a) { + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return false; + } + a = od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, sub); + if (!a) { + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return false; + } + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + a = od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)((dev7 << 1u) | 1u)); + if (!a) { + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return false; + } + for (uint8_t i = 0; i < 16u; i++) { + out16[i] = od_nfc_i2c_read_byte(scl_port, scl_pin, sda_port, sda_pin, i < 15u); + } + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return true; +} + +#if OD_TNB132M_BOOT_PROBE + +/* Type-3 NDEF read: 0x48 sub=0 returns the NFC Forum Attribute Information + * (Ver|Nbr|Nbw|Nmaxb|rsv[4]|WriteFlag|RWFlag|Ln[3]|Sum[2]); NDEF data blocks are + * byte-offset reads at dev=0x40 (sub=0x10 for blk1, 0x20 for blk2, ..). TNB132M + * caches only AI + Nbr data blocks (Nbr=2 here) per anchor, so re-anchor via + * 0x48 sub=0 before each data read so block 3+ still lands in the cache window. + * Decodes the first record's TNF/type/payload so RTT matches phone output. */ +static void od_nfc_boot_dump_ndef_t3(GPIO_Port_TypeDef scl_port, uint8_t scl_pin, + GPIO_Port_TypeDef sda_port, uint8_t sda_pin) +{ + uint8_t ai[16]; + uint8_t data[64]; + uint32_t ln; + uint16_t sum_calc, sum_read; + unsigned need_blocks; + + if (!od_nfc_type3_paged_block_read16(scl_port, scl_pin, sda_port, sda_pin, 0x48u, 0x00u, ai)) { + printf("[OD] NFC ndef AI @0x48 sub=0: NACK\r\n"); + return; + } + if (ai[0] != 0x10u) { + printf("[OD] NFC ndef AI ver=%02x (not 1.x) — skip decode\r\n", ai[0]); + return; + } + ln = ((uint32_t)ai[11] << 16) | ((uint32_t)ai[12] << 8) | (uint32_t)ai[13]; + sum_read = (uint16_t)(((uint16_t)ai[14] << 8) | (uint16_t)ai[15]); + sum_calc = 0; + for (unsigned i = 0; i < 14u; i++) { + sum_calc = (uint16_t)(sum_calc + ai[i]); + } + printf("[OD] NFC ndef AI Ver=%u.%u Nbr=%u Nbw=%u Nmaxb=%u RWFlag=%02x Ln=%lu Sum=%04x (calc %04x %s)\r\n", + (unsigned)(ai[0] >> 4), (unsigned)(ai[0] & 0x0Fu), (unsigned)ai[1], (unsigned)ai[2], + (unsigned)(((uint16_t)ai[3] << 8) | ai[4]), ai[10], (unsigned long)ln, (unsigned)sum_read, + (unsigned)sum_calc, sum_calc == sum_read ? "OK" : "BAD"); + if (ln == 0u || ln > sizeof(data)) { + printf("[OD] NFC ndef Ln=%lu out of range (max %u)\r\n", + (unsigned long)ln, (unsigned)sizeof(data)); + return; + } + need_blocks = (unsigned)((ln + 15u) / 16u); + for (unsigned b = 0; b < need_blocks; b++) { + uint8_t byte_off = (uint8_t)(0x10u + b * 0x10u); + uint8_t throwaway[16]; + (void)od_nfc_type3_paged_block_read16(scl_port, scl_pin, sda_port, sda_pin, 0x48u, 0x00u, + throwaway); + sl_udelay_wait(500); + if (!od_nfc_type3_paged_block_read16(scl_port, scl_pin, sda_port, sda_pin, 0x40u, + byte_off, &data[b * 16u])) { + printf("[OD] NFC ndef blk%u @0x40 sub=%02x: NACK\r\n", b + 1u, (unsigned)byte_off); + return; + } + printf("[OD] NFC ndef blk%u %04x:", b + 1u, b * 16u); + for (unsigned j = 0; j < 16u; j++) { + printf(" %02x", data[b * 16u + j]); + } + printf("\r\n"); + sl_udelay_wait(200); + } + { + uint8_t tnf0 = data[0] & 0x07u; + if (ln < 4u || tnf0 == 0u || tnf0 >= 6u) { + printf("[OD] NFC ndef record too short or bad TNF (ln=%lu hdr=%02x tnf=%u)\r\n", + (unsigned long)ln, data[0], tnf0); + return; + } + } + { + uint8_t hdr = data[0]; + uint8_t tnf = hdr & 0x07u; + bool sr = (hdr & 0x10u) != 0; + bool il = (hdr & 0x08u) != 0; + unsigned tlen = data[1]; + unsigned plen; + unsigned off; + unsigned ilen = 0; + if (sr) { + plen = data[2]; + off = 3u; + } else { + if (ln < 6u) { + printf("[OD] NFC ndef long-format truncated\r\n"); + return; + } + plen = ((unsigned)data[2] << 24) | ((unsigned)data[3] << 16) | ((unsigned)data[4] << 8) | data[5]; + off = 6u; + } + if (il) { + if (ln < off + 1u) { + printf("[OD] NFC ndef IL truncated\r\n"); + return; + } + ilen = data[off]; + off += 1u; + } + if (off + tlen + ilen + plen > ln) { + printf("[OD] NFC ndef fields exceed Ln (off=%u tlen=%u il=%u plen=%u ln=%lu)\r\n", + off, tlen, ilen, plen, (unsigned long)ln); + return; + } + printf("[OD] NFC ndef rec TNF=%u SR=%u IL=%u tlen=%u plen=%u type=\"%.*s\"\r\n", + tnf, (unsigned)sr, (unsigned)il, tlen, plen, tlen, (const char *)&data[off]); + off += tlen + ilen; + if (tnf == 0x01u && tlen == 1u && data[off - tlen - ilen] == 'T' && plen >= 1u) { + unsigned stat = data[off]; + unsigned lang_len = stat & 0x3Fu; + if (1u + lang_len <= plen) { + printf("[OD] NFC ndef Text lang=\"%.*s\" utf%u text=\"%.*s\"\r\n", + lang_len, (const char *)&data[off + 1u], (stat & 0x80u) ? 16u : 8u, + (unsigned)(plen - 1u - lang_len), (const char *)&data[off + 1u + lang_len]); + } + } + } +} + +#endif /* OD_TNB132M_BOOT_PROBE */ + +/* Type-3 16-byte block write: START, dev+W, sub, 16 data bytes, STOP. Mirrors the + * paged read on the write side — AI goes to dev=0x48 sub=0, NDEF data blocks go to + * dev=0x40 sub=0x10/0x20/... (same byte-offset mapping as the read path). */ +static bool od_nfc_type3_paged_block_write16(GPIO_Port_TypeDef scl_port, uint8_t scl_pin, + GPIO_Port_TypeDef sda_port, uint8_t sda_pin, + uint8_t dev7, uint8_t sub, const uint8_t *in16) +{ + bool a; + if (in16 == NULL) { + return false; + } + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + a = od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)(dev7 << 1)); + if (!a) { + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return false; + } + a = od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, sub); + if (!a) { + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return false; + } + for (uint8_t i = 0; i < 16u; i++) { + a = od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, in16[i]); + if (!a) { + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return false; + } + } + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + return true; +} + +#if OD_TNB132M_BOOT_PROBE +#if OD_TNB132M_WRITE_NDEF +/* Replace the NDEF with a UTF-8 Text record (lang="en"). Max 25 chars so the whole + * record fits in AI + 2 data blocks (TNB132M Nbr=2 cache limit). Reads the current + * AI first to preserve RWFlag so the NFC Forum read/write bit keeps its factory + * value — we only modify Ln and checksum. */ +static bool od_nfc_write_ndef_text_en(GPIO_Port_TypeDef scl_port, uint8_t scl_pin, + GPIO_Port_TypeDef sda_port, uint8_t sda_pin, + const char *text) +{ + uint8_t ai[16] = { 0 }; + uint8_t blocks[32] = { 0 }; + uint8_t cur_ai[16]; + size_t tlen = (text != NULL) ? strlen(text) : 0u; + unsigned plen; + unsigned recln; + uint16_t sum; + unsigned i; + unsigned need_blocks; + + if (tlen == 0u || tlen > 25u) { + printf("[OD] NFC write: text len %u out of range (1..25)\r\n", (unsigned)tlen); + return false; + } + plen = 1u + 2u + (unsigned)tlen; + recln = 4u + plen; + + blocks[0] = 0xD1u; + blocks[1] = 0x01u; + blocks[2] = (uint8_t)plen; + blocks[3] = 0x54u; + blocks[4] = 0x02u; + blocks[5] = (uint8_t)'e'; + blocks[6] = (uint8_t)'n'; + for (i = 0; i < tlen; i++) { + blocks[7u + i] = (uint8_t)text[i]; + } + + ai[0] = 0x10u; + ai[1] = 0x02u; + ai[2] = 0x01u; + ai[3] = 0x00u; + ai[4] = 0x3Cu; + ai[11] = (uint8_t)((recln >> 16) & 0xFFu); + ai[12] = (uint8_t)((recln >> 8) & 0xFFu); + ai[13] = (uint8_t)(recln & 0xFFu); + if (od_nfc_type3_paged_block_read16(scl_port, scl_pin, sda_port, sda_pin, 0x48u, 0x00u, cur_ai) + && cur_ai[0] == 0x10u) { + ai[10] = cur_ai[10]; + } + sum = 0; + for (i = 0; i < 14u; i++) { + sum = (uint16_t)(sum + ai[i]); + } + ai[14] = (uint8_t)(sum >> 8); + ai[15] = (uint8_t)(sum & 0xFFu); + + need_blocks = (recln + 15u) / 16u; + printf("[OD] NFC write: Ln=%u RWFlag=%02x text=\"%s\" (%u blk)\r\n", + recln, ai[10], text, need_blocks); + if (!od_nfc_type3_paged_block_write16(scl_port, scl_pin, sda_port, sda_pin, 0x48u, 0x00u, ai)) { + printf("[OD] NFC write: AI @0x48 sub=0 NACK\r\n"); + return false; + } + sl_udelay_wait(10000); + for (i = 0; i < need_blocks; i++) { + uint8_t byte_off = (uint8_t)(0x10u + i * 0x10u); + if (!od_nfc_type3_paged_block_write16(scl_port, scl_pin, sda_port, sda_pin, 0x40u, byte_off, + &blocks[i * 16u])) { + printf("[OD] NFC write: blk%u @0x40 sub=%02x NACK\r\n", i + 1u, (unsigned)byte_off); + return false; + } + sl_udelay_wait(10000); + } + printf("[OD] NFC write: OK\r\n"); + return true; +} +#endif /* OD_TNB132M_WRITE_NDEF */ +#endif /* OD_TNB132M_BOOT_PROBE */ + +/* Wake TNB132M host I2C + open the byte-offset Type-3 data window at 0x40. + * After EEPROM/block writes via 0x48/0x40, callers must run this again or + * 0x48 sub=0 still returns updated AI while 0x40 sub=0x10 reads all 0xFF (RF / + * tag RAM cache repopulates; MCU window must be re-opened). */ +static void od_nfc_tnb132m_prime_type3(GPIO_Port_TypeDef scl_port, uint8_t scl_pin, + GPIO_Port_TypeDef sda_port, uint8_t sda_pin) { - enum { - NFC_PWR_PORT = gpioPortD, NFC_PWR_PIN = 0, - NFC_SCL_PORT = gpioPortD, NFC_SCL_PIN = 1, - NFC_SDA_PORT = gpioPortD, NFC_SDA_PIN = 3 - }; uint8_t sink = 0; - GPIO_PinModeSet(NFC_SCL_PORT, NFC_SCL_PIN, gpioModeWiredAndFilter, 0); - GPIO_PinModeSet(NFC_SDA_PORT, NFC_SDA_PIN, gpioModeWiredAndFilter, 0); - GPIO_PinModeSet(NFC_PWR_PORT, NFC_PWR_PIN, gpioModeWiredOrPullDown, 1); - sl_udelay_wait(40000); - - od_nfc_i2c_start(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, (uint8_t)(0x30u << 1)); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, 0x21u); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, 0x04u); - od_nfc_i2c_stop(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); - - od_nfc_i2c_start(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, (uint8_t)(0x30u << 1)); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, 0x25u); - od_nfc_i2c_start(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, (uint8_t)((0x30u << 1) | 1u)); - sink = od_nfc_i2c_read_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, false); + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)(0x30u << 1)); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, 0x21u); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, 0x04u); + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); + + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)(0x30u << 1)); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, 0x25u); + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)((0x30u << 1) | 1u)); + sink = od_nfc_i2c_read_byte(scl_port, scl_pin, sda_port, sda_pin, false); (void)sink; - od_nfc_i2c_stop(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); sl_udelay_wait(20000); - od_nfc_i2c_start(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, (uint8_t)(0x43u << 1)); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, 0x30u); - od_nfc_i2c_start(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, (uint8_t)((0x43u << 1) | 1u)); + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)(0x43u << 1)); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, 0x30u); + od_nfc_i2c_start(scl_port, scl_pin, sda_port, sda_pin); + (void)od_nfc_i2c_write_byte(scl_port, scl_pin, sda_port, sda_pin, (uint8_t)((0x43u << 1) | 1u)); for (uint8_t i = 0; i < 16u; i++) { - sink = od_nfc_i2c_read_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, i < 15u); + sink = od_nfc_i2c_read_byte(scl_port, scl_pin, sda_port, sda_pin, i < 15u); } (void)sink; - od_nfc_i2c_stop(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); + od_nfc_i2c_stop(scl_port, scl_pin, sda_port, sda_pin); +} + +static void od_nfc_field_detect_init_from_config(const struct NfcConfig *nfc_cfg) +{ + memset(&s_od_nfc_field_detect, 0, sizeof(s_od_nfc_field_detect)); + s_od_nfc_field_detect.int_no = -1; + s_od_nfc_field_detect.em4_int_no = -1; + s_od_nfc_field_scan_counter = 0u; + if (nfc_cfg == NULL) { + return; + } + if (nfc_cfg->field_detect_mode == 0u || nfc_cfg->field_detect_pin == GPIO_PIN_UNUSED) { + return; + } + s_od_nfc_field_detect.enabled = true; + s_od_nfc_field_detect.pin_cfg = nfc_cfg->field_detect_pin; + s_od_nfc_field_detect.mode = nfc_cfg->field_detect_mode; + s_od_nfc_field_detect.active_high = (nfc_cfg->field_detect_active != 0u); + s_od_nfc_field_detect.debounce_ms = nfc_cfg->field_detect_debounce_ms; + s_od_nfc_field_detect.adv_button_byte_index = nfc_cfg->adv_button_byte_index; + s_od_nfc_field_detect.adv_button_id = (uint8_t)(nfc_cfg->adv_button_button_id & BUTTON_ID_MASK); + od_setup_button_pin(s_od_nfc_field_detect.pin_cfg, false, false); + if (s_od_nfc_field_detect.mode == 2u) { + sl_gpio_t gpio = { + .port = (sl_gpio_port_t)(s_od_nfc_field_detect.pin_cfg >> 4), + .pin = (s_od_nfc_field_detect.pin_cfg & 0x0Fu) + }; + if (sl_gpio_configure_external_interrupt(&gpio, + &s_od_nfc_field_detect.int_no, + SL_GPIO_INTERRUPT_RISING_FALLING_EDGE, + od_nfc_field_detect_irq_callback, + &s_od_nfc_field_detect) == SL_STATUS_OK) { + printf("[OD][NFC] field-detect IRQ enabled int_no=%ld\r\n", (long)s_od_nfc_field_detect.int_no); + } else { + s_od_nfc_field_detect.int_no = -1; + printf("[OD][NFC] field-detect IRQ setup failed; falling back to polling\r\n"); + } + } + { + uint8_t pin_state = od_read_button_pin(s_od_nfc_field_detect.pin_cfg); + uint8_t logical = (s_od_nfc_field_detect.active_high ? (pin_state != 0u) : (pin_state == 0u)) ? 1u : 0u; + /* Start from 0 so the first post-init sample can create a 0->1 scan edge. */ + s_od_nfc_field_detect.state = 0u; + if (s_od_nfc_field_detect.adv_button_byte_index < sizeof(dynamic_return)) { + dynamic_return[s_od_nfc_field_detect.adv_button_byte_index] = + (uint8_t)((s_od_nfc_field_detect.state & 0x01u) + | (((s_od_nfc_field_scan_counter & 0x7Fu) << 1) & 0xFEu)); + } + (void)logical; + } + printf("[OD][NFC] field-detect pin=0x%02X state=%u byte_index=%u button_id=%u\r\n", + (unsigned)s_od_nfc_field_detect.pin_cfg, + (unsigned)s_od_nfc_field_detect.state, + (unsigned)s_od_nfc_field_detect.adv_button_byte_index, + (unsigned)s_od_nfc_field_detect.adv_button_id); +} + +static bool od_nfc_field_detect_refresh(uint32_t now_ms, bool force_log) +{ + uint8_t pin_state; + uint8_t logical; + uint8_t prev_state; + bool scan_event = false; + + if (!s_od_nfc_field_detect.enabled) { + return false; + } + if (s_od_nfc_field_detect.adv_button_byte_index >= sizeof(dynamic_return)) { + return false; + } + + pin_state = od_read_button_pin(s_od_nfc_field_detect.pin_cfg); + logical = (s_od_nfc_field_detect.active_high ? (pin_state != 0u) : (pin_state == 0u)) ? 1u : 0u; + prev_state = s_od_nfc_field_detect.state; + + if (!force_log && logical == prev_state) { + return false; + } + if (!force_log + && s_od_nfc_field_detect.debounce_ms != 0u + && (now_ms - s_od_nfc_field_detect.last_change_ms) < s_od_nfc_field_detect.debounce_ms) { + return false; + } + + s_od_nfc_field_detect.last_change_ms = now_ms; + s_od_nfc_field_detect.state = logical; + if (logical != 0u && prev_state == 0u) { + s_od_nfc_field_scan_counter = (uint32_t)((s_od_nfc_field_scan_counter + 1u) & 0x7Fu); + scan_event = true; + } + dynamic_return[s_od_nfc_field_detect.adv_button_byte_index] = + (uint8_t)((logical & 0x01u) + | (((s_od_nfc_field_scan_counter & 0x7Fu) << 1) & 0xFEu)); + + if (force_log) { + printf("[OD][NFC] field-detect sample pin=0x%02X raw=%u state=%u ctr7=%lu adv=0x%02X (active_%s)\r\n", + (unsigned)s_od_nfc_field_detect.pin_cfg, + (unsigned)pin_state, + (unsigned)logical, + (unsigned long)s_od_nfc_field_scan_counter, + (unsigned)dynamic_return[s_od_nfc_field_detect.adv_button_byte_index], + s_od_nfc_field_detect.active_high ? "high" : "low"); + } else { + printf("[OD][NFC] field-detect pin=0x%02X raw=%u state %u->%u ctr7=%lu adv=0x%02X%s (active_%s)\r\n", + (unsigned)s_od_nfc_field_detect.pin_cfg, + (unsigned)pin_state, + (unsigned)prev_state, + (unsigned)logical, + (unsigned long)s_od_nfc_field_scan_counter, + (unsigned)dynamic_return[s_od_nfc_field_detect.adv_button_byte_index], + scan_event ? " scan++" : "", + s_od_nfc_field_detect.active_high ? "high" : "low"); + } + return true; +} + +static void od_nfc_field_detect_irq_callback(uint8_t int_no, void *context) +{ + od_nfc_field_detect_t *st = (od_nfc_field_detect_t *)context; + (void)int_no; + if (st == NULL || !st->enabled) { + return; + } + st->irq_pending = true; +} + +static void od_nfc_field_detect_arm_em4_wakeup(void) +{ + sl_gpio_t gpio; + sl_status_t sc; + int32_t em4_int_no = SL_GPIO_INTERRUPT_UNAVAILABLE; + + if (!s_od_nfc_field_detect.enabled) { + return; + } + + gpio.port = (sl_gpio_port_t)(s_od_nfc_field_detect.pin_cfg >> 4); + gpio.pin = (s_od_nfc_field_detect.pin_cfg & 0x0Fu); + sc = sl_gpio_configure_wakeup_em4_interrupt(&gpio, + &em4_int_no, + s_od_nfc_field_detect.active_high, + od_nfc_field_detect_irq_callback, + &s_od_nfc_field_detect); + if (sc == SL_STATUS_OK) { + s_od_nfc_field_detect.em4_int_no = em4_int_no; + printf("[OD][NFC] EM4 wake armed on field-detect pin=0x%02X em4_int=%ld polarity=%s\r\n", + (unsigned)s_od_nfc_field_detect.pin_cfg, + (long)em4_int_no, + s_od_nfc_field_detect.active_high ? "high" : "low"); + } else { + printf("[OD][NFC] EM4 wake arm failed pin=0x%02X sc=0x%04lX\r\n", + (unsigned)s_od_nfc_field_detect.pin_cfg, (unsigned long)sc); + } +} + +static bool od_nfc_field_detect_process(uint32_t now_ms) +{ + if (!s_od_nfc_field_detect.enabled) { + return false; + } + if (s_od_nfc_field_detect.mode == 2u) { + if (!s_od_nfc_field_detect.irq_pending) { + return false; + } + s_od_nfc_field_detect.irq_pending = false; + return od_nfc_field_detect_refresh(now_ms, false); + } + return od_nfc_field_detect_refresh(now_ms, false); +} + +static void od_nfc_autoinit_from_config(const struct GlobalConfig *cfg) +{ + const struct NfcConfig *nfc_cfg = NULL; + const struct DataBus *bus = NULL; + uint8_t i; + GPIO_Port_TypeDef pwr_port; + uint8_t pwr_pin; + + s_od_nfc_enabled = false; + s_od_nfc_has_pwr_pin = false; + s_od_nfc_ic_type = OD_NFC_IC_AUTO; + s_od_nfc_power_on_delay_ms = 40u; + memset(&s_od_nfc_field_detect, 0, sizeof(s_od_nfc_field_detect)); + s_od_nfc_field_detect.int_no = -1; + + if (cfg == NULL || !cfg->loaded || cfg->nfc_config_count == 0u) { + printf("[OD][NFC] no NFC config packet (0x2A); NFC init disabled\r\n"); + return; + } + + for (i = 0u; i < cfg->nfc_config_count; i++) { + if ((cfg->nfc_configs[i].flags & 0x01u) != 0u) { + nfc_cfg = &cfg->nfc_configs[i]; + break; + } + } + if (nfc_cfg == NULL) { + printf("[OD][NFC] NFC configs present, but none enabled; NFC init disabled\r\n"); + return; + } + + for (i = 0u; i < cfg->data_bus_count; i++) { + if (cfg->data_buses[i].instance_number == nfc_cfg->bus_instance) { + bus = &cfg->data_buses[i]; + break; + } + } + if (bus == NULL || bus->bus_type != OD_BUS_TYPE_I2C) { + printf("[OD][NFC] bus instance=%u missing/non-I2C; NFC init disabled\r\n", (unsigned)nfc_cfg->bus_instance); + return; + } + if (!od_pin_decode(bus->pin_1, &s_od_nfc_scl_port, &s_od_nfc_scl_pin) + || !od_pin_decode(bus->pin_2, &s_od_nfc_sda_port, &s_od_nfc_sda_pin)) { + printf("[OD][NFC] bad bus pins for instance=%u; NFC init disabled\r\n", (unsigned)nfc_cfg->bus_instance); + return; + } + + s_od_nfc_ic_type = nfc_cfg->nfc_ic_type; + s_od_nfc_power_on_delay_ms = (nfc_cfg->power_on_delay_ms == 0u) ? 40u : nfc_cfg->power_on_delay_ms; + + if (nfc_cfg->power_pin != GPIO_PIN_UNUSED && od_pin_decode(nfc_cfg->power_pin, &pwr_port, &pwr_pin)) { + s_od_nfc_has_pwr_pin = true; + s_od_nfc_pwr_port = pwr_port; + s_od_nfc_pwr_pin = pwr_pin; + } else if (od_pin_decode(bus->pin_3, &pwr_port, &pwr_pin)) { + s_od_nfc_has_pwr_pin = true; + s_od_nfc_pwr_port = pwr_port; + s_od_nfc_pwr_pin = pwr_pin; + } + + if (s_od_nfc_ic_type != OD_NFC_IC_AUTO && s_od_nfc_ic_type != OD_NFC_IC_TNB132M) { + printf("[OD][NFC] ic_type=%u unsupported yet; NFC init disabled\r\n", (unsigned)s_od_nfc_ic_type); + return; + } + s_od_nfc_enabled = true; + od_nfc_field_detect_init_from_config(nfc_cfg); + printf("[OD][NFC] enabled: bus=%u ic_type=%u scl=0x%02X sda=0x%02X pwr=%s\r\n", + (unsigned)nfc_cfg->bus_instance, + (unsigned)s_od_nfc_ic_type, + (unsigned)bus->pin_1, + (unsigned)bus->pin_2, + s_od_nfc_has_pwr_pin ? "yes" : "no"); +} + +static void od_nfc_init_sequence(void) +{ + GPIO_Port_TypeDef sp = s_od_nfc_scl_port; + uint8_t sn = s_od_nfc_scl_pin; + GPIO_Port_TypeDef dp = s_od_nfc_sda_port; + uint8_t dn = s_od_nfc_sda_pin; + + if (!s_od_nfc_enabled) { + return; + } + + GPIO_PinModeSet(sp, sn, gpioModeWiredAndFilter, 0); + GPIO_PinModeSet(dp, dn, gpioModeWiredAndFilter, 0); + if (s_od_nfc_has_pwr_pin) { + GPIO_PinModeSet(s_od_nfc_pwr_port, s_od_nfc_pwr_pin, gpioModePushPull, 1); + sl_udelay_wait((uint32_t)s_od_nfc_power_on_delay_ms * 1000u); + } else { + sl_udelay_wait(10000); + } + + od_nfc_tnb132m_prime_type3(sp, sn, dp, dn); + +#if OD_TNB132M_BOOT_PROBE + sl_udelay_wait(2000); +#if OD_TNB132M_WRITE_NDEF + (void)od_nfc_write_ndef_text_en(sp, sn, dp, dn, OD_TNB132M_NDEF_TEXT); + sl_udelay_wait(50000); + od_nfc_tnb132m_prime_type3(sp, sn, dp, dn); + sl_udelay_wait(2000); +#endif + od_nfc_boot_dump_ndef_t3(sp, sn, dp, dn); + sl_udelay_wait(1000); +#endif sl_udelay_wait(20000); - od_nfc_i2c_start(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, (uint8_t)(0x30u << 1)); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, 0x21u); - (void)od_nfc_i2c_write_byte(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN, 0x01u); - od_nfc_i2c_stop(NFC_SCL_PORT, NFC_SCL_PIN, NFC_SDA_PORT, NFC_SDA_PIN); + od_nfc_i2c_start(sp, sn, dp, dn); + (void)od_nfc_i2c_write_byte(sp, sn, dp, dn, (uint8_t)(0x30u << 1)); + (void)od_nfc_i2c_write_byte(sp, sn, dp, dn, 0x21u); + (void)od_nfc_i2c_write_byte(sp, sn, dp, dn, 0x01u); + od_nfc_i2c_stop(sp, sn, dp, dn); sl_udelay_wait(14000); + (void)od_nfc_field_detect_refresh(sl_sleeptimer_tick_to_ms(sl_sleeptimer_get_tick_count()), true); - GPIO_PinOutClear(NFC_PWR_PORT, NFC_PWR_PIN); - GPIO_PinModeSet(NFC_SCL_PORT, NFC_SCL_PIN, gpioModeInput, 1); - GPIO_PinModeSet(NFC_SDA_PORT, NFC_SDA_PIN, gpioModeInput, 1); - GPIO_PinModeSet(NFC_PWR_PORT, NFC_PWR_PIN, gpioModeInput, 1); + if (s_od_nfc_has_pwr_pin) { + GPIO_PinOutClear(s_od_nfc_pwr_port, s_od_nfc_pwr_pin); + GPIO_PinModeSet(s_od_nfc_pwr_port, s_od_nfc_pwr_pin, gpioModeInput, 1); + } + GPIO_PinModeSet(sp, sn, gpioModeInput, 1); + GPIO_PinModeSet(dp, dn, gpioModeInput, 1); +} + +static bool od_nfc_read_record_raw(uint8_t *type_out, uint8_t *out, uint16_t *io_len, uint16_t out_max) +{ + uint8_t ai[16]; + uint8_t *data = s_od_nfc_read_data; + uint32_t ln; + uint16_t off; + uint16_t tlen; + uint16_t plen; + uint16_t copy_len; + bool sr; + CORE_DECLARE_IRQ_STATE; + + if (type_out == NULL || io_len == NULL || out == NULL) { + return false; + } + + CORE_ENTER_CRITICAL(); + + GPIO_PinModeSet(s_od_nfc_scl_port, s_od_nfc_scl_pin, gpioModeWiredAndFilter, 0); + GPIO_PinModeSet(s_od_nfc_sda_port, s_od_nfc_sda_pin, gpioModeWiredAndFilter, 0); + if (s_od_nfc_has_pwr_pin) { + GPIO_PinModeSet(s_od_nfc_pwr_port, s_od_nfc_pwr_pin, gpioModePushPull, 1); + sl_udelay_wait(40000); + } else { + sl_udelay_wait(10000); + } + + od_nfc_tnb132m_prime_type3(s_od_nfc_scl_port, s_od_nfc_scl_pin, s_od_nfc_sda_port, s_od_nfc_sda_pin); + sl_udelay_wait(2000); + if (!od_nfc_type3_paged_block_read16(s_od_nfc_scl_port, s_od_nfc_scl_pin, + s_od_nfc_sda_port, s_od_nfc_sda_pin, + 0x48u, 0x00u, ai)) { + CORE_EXIT_CRITICAL(); + return false; + } + if ((ai[0] & 0xF0u) != 0x10u) { + CORE_EXIT_CRITICAL(); + return false; + } + + ln = ((uint32_t)ai[11] << 16) | ((uint32_t)ai[12] << 8) | (uint32_t)ai[13]; + if (ln == 0u || ln > sizeof(s_od_nfc_read_data)) { + CORE_EXIT_CRITICAL(); + return false; + } + for (uint16_t b = 0; b < (uint16_t)((ln + 15u) / 16u); b++) { + uint8_t byte_off = (uint8_t)(0x10u + b * 0x10u); + (void)od_nfc_type3_paged_block_read16(s_od_nfc_scl_port, s_od_nfc_scl_pin, + s_od_nfc_sda_port, s_od_nfc_sda_pin, + 0x48u, 0x00u, s_od_nfc_read_throwaway); + sl_udelay_wait(500); + if (!od_nfc_type3_paged_block_read16(s_od_nfc_scl_port, s_od_nfc_scl_pin, + s_od_nfc_sda_port, s_od_nfc_sda_pin, + 0x40u, byte_off, &data[b * 16u])) { + CORE_EXIT_CRITICAL(); + return false; + } + } + + CORE_EXIT_CRITICAL(); + + if (ln < 3u) { + return false; + } + sr = (data[0] & 0x10u) != 0u; + if (!sr) { + return false; + } + /* IL (payload ID length field present): unsupported — return verbatim NDEF. */ + if ((data[0] & 0x08u) != 0u) { + if (ln > out_max) { + *io_len = out_max; + } else { + *io_len = (uint16_t)ln; + } + *type_out = OD_NFC_REC_RAW_NDEF; + memcpy(out, data, *io_len); + return true; + } + + tlen = data[1]; + plen = data[2]; + off = 3u; + if (((uint32_t)off + (uint32_t)tlen + (uint32_t)plen) > ln) { + return false; + } + + switch (data[0] & 7u) { + case 2u: /* NDEF MIME (Media-type): host payload = type string + MIME body byte-for-byte */ + { + uint16_t out_pack; + *type_out = OD_NFC_REC_MIME; + if (plen > 255u || tlen == 0u) { + return false; + } + out_pack = (uint16_t)(1u + tlen + plen); + if (out_pack > out_max) { + return false; + } + out[0] = tlen; + memcpy(&out[1], &data[off], tlen); + memcpy(&out[1u + tlen], &data[(uint16_t)(off + tlen)], plen); + *io_len = out_pack; + return true; + } + + case 1u: + break; + + default: /* TNF ≠ Well-known/MIME SR: verbatim NDEF (future-proof passthrough). */ + if (ln > out_max) { + *io_len = out_max; + } else { + *io_len = (uint16_t)ln; + } + *type_out = OD_NFC_REC_RAW_NDEF; + memcpy(out, data, *io_len); + return true; + } + + /* Well-known SR (already bounds-checked above). */ + + if (tlen == 1u && data[off] == 'T') { + uint8_t status; + uint8_t lang_len; + *type_out = OD_NFC_REC_TEXT; + off = (uint16_t)(off + 1u); + if (plen < 1u) { + return false; + } + status = data[off]; + lang_len = status & 0x3Fu; + if ((uint16_t)(1u + lang_len) > plen) { + return false; + } + off = (uint16_t)(off + 1u + lang_len); + copy_len = (uint16_t)(plen - 1u - lang_len); + } else if (tlen == 1u && data[off] == 'U') { + uint8_t uri_prefix; + *type_out = OD_NFC_REC_URI; + off = (uint16_t)(off + 1u); + if (plen < 1u) { + return false; + } + uri_prefix = data[off]; + if (uri_prefix != 0x00u) { + return false; + } + off = (uint16_t)(off + 1u); + copy_len = (uint16_t)(plen - 1u); + } else { + uint16_t raw_len; + *type_out = OD_NFC_REC_WELL_KNOWN_RAW; + raw_len = (uint16_t)(1u + tlen + plen); + if (raw_len > out_max) { + raw_len = out_max; + } + if (raw_len == 0u) { + *io_len = 0u; + return true; + } + out[0] = tlen; + copy_len = (uint16_t)(raw_len - 1u); + memcpy(&out[1], &data[off], copy_len); + *io_len = raw_len; + return true; + } + + if (copy_len > out_max) { + copy_len = out_max; + } + memcpy(out, &data[off], copy_len); + *io_len = copy_len; + return true; +} + +static bool od_nfc_write_record_raw(uint8_t rec_type, const uint8_t *data, uint16_t data_len) +{ + uint8_t ai[16] = { 0 }; + uint8_t cur_ai[16]; + uint8_t *blocks = s_od_nfc_write_blocks; + uint16_t sum; + uint16_t record_len; + uint16_t payload_len; + uint8_t need_blocks; + uint8_t i; + CORE_DECLARE_IRQ_STATE; + + if (data == NULL || data_len == 0u) { + return false; + } + memset(blocks, 0, sizeof(s_od_nfc_write_blocks)); + + if (rec_type == OD_NFC_REC_TEXT) { + payload_len = (uint16_t)(1u + 2u + data_len); + if (payload_len > 255u || payload_len > (uint16_t)(sizeof(s_od_nfc_write_blocks) - 4u)) { + return false; + } + record_len = (uint16_t)(4u + payload_len); + blocks[0] = 0xD1u; + blocks[1] = 0x01u; + blocks[2] = (uint8_t)payload_len; + blocks[3] = 0x54u; + blocks[4] = 0x02u; + blocks[5] = (uint8_t)'e'; + blocks[6] = (uint8_t)'n'; + memcpy(&blocks[7], data, data_len); + } else if (rec_type == OD_NFC_REC_URI) { + payload_len = (uint16_t)(1u + data_len); + if (payload_len > 255u || payload_len > (uint16_t)(sizeof(s_od_nfc_write_blocks) - 4u)) { + return false; + } + record_len = (uint16_t)(4u + payload_len); + blocks[0] = 0xD1u; + blocks[1] = 0x01u; + blocks[2] = (uint8_t)payload_len; + blocks[3] = 0x55u; + blocks[4] = 0x00u; + memcpy(&blocks[5], data, data_len); + } else if (rec_type == OD_NFC_REC_WELL_KNOWN_RAW) { + uint8_t type_len; + uint16_t raw_payload_len; + if (data_len < 2u) { + return false; + } + type_len = data[0]; + if (type_len == 0u || (uint16_t)(1u + type_len) > data_len) { + return false; + } + raw_payload_len = (uint16_t)(data_len - 1u - type_len); + if (raw_payload_len > 255u) { + return false; + } + record_len = (uint16_t)(3u + type_len + raw_payload_len); + if (record_len > sizeof(s_od_nfc_write_blocks)) { + return false; + } + blocks[0] = 0xD1u; + blocks[1] = type_len; + blocks[2] = (uint8_t)raw_payload_len; + memcpy(&blocks[3], &data[1], type_len); + if (raw_payload_len > 0u) { + memcpy(&blocks[3u + type_len], &data[1u + type_len], raw_payload_len); + } + } else if (rec_type == OD_NFC_REC_MIME) { + uint8_t mime_tl; + uint16_t body_len; + + if (data_len < 3u) { + return false; + } + mime_tl = data[0]; + if (mime_tl == 0u || (uint16_t)(1u + mime_tl) > data_len) { + return false; + } + body_len = (uint16_t)(data_len - 1u - mime_tl); + if (body_len > 255u) { + return false; + } + record_len = (uint16_t)(3u + mime_tl + body_len); + if (record_len > sizeof(s_od_nfc_write_blocks)) { + return false; + } + blocks[0] = 0xD2u; /* MB | ME | SR ; TNF = MIME */ + blocks[1] = mime_tl; + blocks[2] = (uint8_t)body_len; + memcpy(&blocks[3], &data[1], mime_tl); + if (body_len > 0u) { + memcpy(&blocks[3u + mime_tl], &data[1u + mime_tl], body_len); + } + } else if (rec_type == OD_NFC_REC_RAW_NDEF) { + record_len = data_len; + if (record_len == 0u || record_len > sizeof(s_od_nfc_write_blocks)) { + return false; + } + memcpy(blocks, data, record_len); + } else { + return false; + } + + CORE_ENTER_CRITICAL(); + + GPIO_PinModeSet(s_od_nfc_scl_port, s_od_nfc_scl_pin, gpioModeWiredAndFilter, 0); + GPIO_PinModeSet(s_od_nfc_sda_port, s_od_nfc_sda_pin, gpioModeWiredAndFilter, 0); + if (s_od_nfc_has_pwr_pin) { + GPIO_PinModeSet(s_od_nfc_pwr_port, s_od_nfc_pwr_pin, gpioModePushPull, 1); + sl_udelay_wait(40000); + } else { + sl_udelay_wait(10000); + } + + od_nfc_tnb132m_prime_type3(s_od_nfc_scl_port, s_od_nfc_scl_pin, s_od_nfc_sda_port, s_od_nfc_sda_pin); + sl_udelay_wait(2000); + + ai[0] = 0x10u; + ai[1] = 0x02u; + ai[2] = 0x01u; + ai[3] = 0x00u; + ai[4] = 0x3Cu; + ai[11] = (uint8_t)((record_len >> 16) & 0xFFu); + ai[12] = (uint8_t)((record_len >> 8) & 0xFFu); + ai[13] = (uint8_t)(record_len & 0xFFu); + + if (od_nfc_type3_paged_block_read16(s_od_nfc_scl_port, s_od_nfc_scl_pin, s_od_nfc_sda_port, + s_od_nfc_sda_pin, 0x48u, 0x00u, cur_ai) + && (cur_ai[0] & 0xF0u) == 0x10u) { + ai[10] = cur_ai[10]; + } + sum = 0u; + for (i = 0u; i < 14u; i++) { + sum = (uint16_t)(sum + ai[i]); + } + ai[14] = (uint8_t)(sum >> 8); + ai[15] = (uint8_t)(sum & 0xFFu); + + if (!od_nfc_type3_paged_block_write16(s_od_nfc_scl_port, s_od_nfc_scl_pin, s_od_nfc_sda_port, + s_od_nfc_sda_pin, 0x48u, 0x00u, ai)) { + CORE_EXIT_CRITICAL(); + return false; + } + sl_udelay_wait(10000); + need_blocks = (uint8_t)((record_len + 15u) / 16u); + for (i = 0u; i < need_blocks; i++) { + uint8_t byte_off = (uint8_t)(0x10u + i * 0x10u); + if (!od_nfc_type3_paged_block_write16(s_od_nfc_scl_port, s_od_nfc_scl_pin, s_od_nfc_sda_port, + s_od_nfc_sda_pin, 0x40u, byte_off, &blocks[i * 16u])) { + CORE_EXIT_CRITICAL(); + return false; + } + sl_udelay_wait(10000); + } + + CORE_EXIT_CRITICAL(); + return true; +} + +static void od_flash_autoinit_from_config(const struct GlobalConfig *cfg) +{ + const struct FlashConfig *flash_cfg = NULL; + uint8_t i; + + s_od_flash_enabled = false; + if (cfg == NULL || !cfg->loaded || cfg->flash_config_count == 0u) { + printf("[OD][FLASH] no flash config packet (0x2B); flash deep-sleep config disabled\r\n"); + return; + } + for (i = 0u; i < cfg->flash_config_count; i++) { + if ((cfg->flash_configs[i].flags & 0x01u) != 0u) { + flash_cfg = &cfg->flash_configs[i]; + break; + } + } + if (flash_cfg == NULL) { + printf("[OD][FLASH] flash configs present, but none enabled\r\n"); + return; + } + if (!od_pin_decode(flash_cfg->mosi_pin, &s_od_flash_mosi_port, &s_od_flash_mosi_pin) + || !od_pin_decode(flash_cfg->sck_pin, &s_od_flash_sck_port, &s_od_flash_sck_pin) + || !od_pin_decode(flash_cfg->cs_pin, &s_od_flash_cs_port, &s_od_flash_cs_pin)) { + printf("[OD][FLASH] bad MOSI/SCK/CS pins in flash config; flash deep-sleep config disabled\r\n"); + return; + } + s_od_flash_enabled = true; + printf("[OD][FLASH] enabled mosi=0x%02X sck=0x%02X cs=0x%02X\r\n", + (unsigned)flash_cfg->mosi_pin, (unsigned)flash_cfg->sck_pin, (unsigned)flash_cfg->cs_pin); } static void od_flash_enter_deep_sleep(void) { - enum { - FLASH_MOSI_PORT = gpioPortC, FLASH_MOSI_PIN = 1, - FLASH_SCK_PORT = gpioPortC, FLASH_SCK_PIN = 2, - FLASH_CS_PORT = gpioPortC, FLASH_CS_PIN = 3 - }; uint8_t cmd = 0xB9u; - GPIO_PinModeSet(FLASH_MOSI_PORT, FLASH_MOSI_PIN, gpioModePushPull, 0); - GPIO_PinModeSet(FLASH_SCK_PORT, FLASH_SCK_PIN, gpioModePushPull, 0); - GPIO_PinModeSet(FLASH_CS_PORT, FLASH_CS_PIN, gpioModePushPull, 1); + if (!s_od_flash_enabled) { + return; + } + + GPIO_PinModeSet(s_od_flash_mosi_port, s_od_flash_mosi_pin, gpioModePushPull, 0); + GPIO_PinModeSet(s_od_flash_sck_port, s_od_flash_sck_pin, gpioModePushPull, 0); + GPIO_PinModeSet(s_od_flash_cs_port, s_od_flash_cs_pin, gpioModePushPull, 1); - GPIO_PinOutClear(FLASH_CS_PORT, FLASH_CS_PIN); + GPIO_PinOutClear(s_od_flash_cs_port, s_od_flash_cs_pin); for (uint8_t bit = 0; bit < 8u; bit++) { bool one = (cmd & 0x80u) != 0u; if (one) { - GPIO_PinOutSet(FLASH_MOSI_PORT, FLASH_MOSI_PIN); + GPIO_PinOutSet(s_od_flash_mosi_port, s_od_flash_mosi_pin); } else { - GPIO_PinOutClear(FLASH_MOSI_PORT, FLASH_MOSI_PIN); + GPIO_PinOutClear(s_od_flash_mosi_port, s_od_flash_mosi_pin); } cmd <<= 1; sl_udelay_wait(1); - GPIO_PinOutSet(FLASH_SCK_PORT, FLASH_SCK_PIN); + GPIO_PinOutSet(s_od_flash_sck_port, s_od_flash_sck_pin); sl_udelay_wait(1); - GPIO_PinOutClear(FLASH_SCK_PORT, FLASH_SCK_PIN); + GPIO_PinOutClear(s_od_flash_sck_port, s_od_flash_sck_pin); } - GPIO_PinOutSet(FLASH_CS_PORT, FLASH_CS_PIN); + GPIO_PinOutSet(s_od_flash_cs_port, s_od_flash_cs_pin); sl_udelay_wait(30); } static void od_init_aux_peripherals(void) { - /* Hardcoded board wiring used for low-power pin parking at boot. */ - enum { - NFC_PWR_PORT = gpioPortD, NFC_PWR_PIN = 0, - NFC_SCL_PORT = gpioPortD, NFC_SCL_PIN = 1, - FLASH_CS_PORT = gpioPortC, FLASH_CS_PIN = 3, - NFC_SDA_PORT = gpioPortD, NFC_SDA_PIN = 3 - }; - CMU_ClockEnable(cmuClock_GPIO, true); + od_nfc_autoinit_from_config(&s_od_global_config); od_nfc_init_sequence(); + od_flash_autoinit_from_config(&s_od_global_config); od_flash_enter_deep_sleep(); - /* Park external flash SPI lines in passive states. */ - GPIO_PinModeSet(gpioPortC, 1, gpioModeInputPull, 1); - GPIO_PinModeSet(gpioPortC, 2, gpioModeInputPull, 0); - GPIO_PinModeSet(FLASH_CS_PORT, FLASH_CS_PIN, gpioModeInputPull, 1); + if (s_od_flash_enabled) { + /* Park external flash SPI lines in passive states. */ + GPIO_PinModeSet(s_od_flash_mosi_port, s_od_flash_mosi_pin, gpioModeInputPull, 1); + GPIO_PinModeSet(s_od_flash_sck_port, s_od_flash_sck_pin, gpioModeInputPull, 0); + GPIO_PinModeSet(s_od_flash_cs_port, s_od_flash_cs_pin, gpioModeInputPull, 1); + } } const struct GlobalConfig *opendisplay_get_global_config(void) @@ -533,7 +1509,7 @@ void opendisplay_ble_reload_config_from_nvm(void) printf("[OD] config: reload after save failed\r\n"); } od_buttons_init_from_config(); - od_park_display_pins_from_config(); + opendisplay_display_park_pins(); opendisplay_led_init(); opendisplay_display_boot_apply(); } @@ -552,24 +1528,6 @@ static void od_apply_idle_advertising_timing(uint8_t adv_handle) app_assert_status(sc); } -/* Undirected connectable legacy adv stops when a central connects; restart so other scanners still see us. */ -static void restart_connectable_advertising(void) -{ - sl_status_t sc; - - if (s_adv_handle == 0xFFu) { - return; - } - od_apply_idle_advertising_timing(s_adv_handle); - sc = sl_bt_legacy_advertiser_start(s_adv_handle, sl_bt_legacy_advertiser_connectable); - if (sc == SL_STATUS_OK) { - printf("[OD] advertising restarted (stack pauses adv while connected)\r\n"); - } else { - printf("[OD] adv restart after connect sc=0x%04lX (expected if no free conn slots)\r\n", - (unsigned long)sc); - } -} - static void chip_id_hex6(char out[7]) { uint64_t u = SYSTEM_GetUnique(); @@ -796,6 +1754,19 @@ void opendisplay_ble_on_boot(uint8_t advertising_set_handle) char hex[7]; sl_status_t sc; + if (loadGlobalConfig(&s_od_global_config)) { + printf("[OD] config: loaded displays=%u leds=%u buses=%u nfc=%u flash=%u ver=%u.%u\r\n", + (unsigned)s_od_global_config.display_count, + (unsigned)s_od_global_config.led_count, + (unsigned)s_od_global_config.data_bus_count, + (unsigned)s_od_global_config.nfc_config_count, + (unsigned)s_od_global_config.flash_config_count, + (unsigned)s_od_global_config.version, + (unsigned)s_od_global_config.minor_version); + } else { + printf("[OD] config: none or invalid (defaults)\r\n"); + } + od_init_aux_peripherals(); s_adv_handle = advertising_set_handle; @@ -825,17 +1796,8 @@ void opendisplay_ble_on_boot(uint8_t advertising_set_handle) (unsigned)g_od_pipe_char, (unsigned)g_od_appver_char); opendisplay_pipe_set_characteristic(g_od_pipe_char); - if (loadGlobalConfig(&s_od_global_config)) { - printf("[OD] config: loaded displays=%u leds=%u ver=%u.%u\r\n", - (unsigned)s_od_global_config.display_count, - (unsigned)s_od_global_config.led_count, - (unsigned)s_od_global_config.version, - (unsigned)s_od_global_config.minor_version); - } else { - printf("[OD] config: none or invalid (defaults)\r\n"); - } od_buttons_init_from_config(); - od_park_display_pins_from_config(); + opendisplay_display_park_pins(); opendisplay_led_init(); opendisplay_display_boot_apply(); @@ -867,15 +1829,30 @@ void opendisplay_ble_on_event(sl_bt_msg_t *evt) switch (SL_BT_MSG_ID(evt->header)) { case sl_bt_evt_connection_opened_id: g_connection = evt->data.evt_connection_opened.connection; + s_connection_open_ms = sl_sleeptimer_tick_to_ms(sl_sleeptimer_get_tick_count()); + s_connection_timeout_close_requested = false; reboot_flag = 0u; printf("[OD] connection opened handle=%u\r\n", (unsigned)g_connection); - restart_connectable_advertising(); + if (s_adv_handle != 0xFFu) { + sl_status_t sc = sl_bt_advertiser_stop(s_adv_handle); + if (sc == SL_STATUS_OK) { + printf("[OD] advertising stopped while connected (single-client mode)\r\n"); + } else { + printf("[OD] advertiser_stop sc=0x%04lX\r\n", (unsigned long)sc); + } + } break; case sl_bt_evt_connection_closed_id: g_connection = 0xFF; + s_connection_open_ms = 0u; + s_connection_timeout_close_requested = false; opendisplay_pipe_on_connection_closed(); printf("[OD] connection closed reason=0x%02X\r\n", (unsigned)evt->data.evt_connection_closed.reason); + if (s_adv_handle != 0xFFu) { + opendisplay_ble_restart_advertising(s_adv_handle); + printf("[OD] advertising resumed after disconnect\r\n"); + } break; default: (void)g_od_pipe_char; @@ -896,15 +1873,30 @@ void opendisplay_ble_schedule_deep_sleep(void) void opendisplay_ble_process(void) { uint32_t now_ms = sl_sleeptimer_tick_to_ms(sl_sleeptimer_get_tick_count()); - if (od_process_button_event(now_ms) && s_adv_handle != 0xFFu && g_connection == 0xFFu) { + if (od_process_button_event(now_ms) && s_adv_handle != 0xFFu) { + build_and_apply_adv(s_adv_handle, s_dev_name); + } + if (od_nfc_field_detect_process(now_ms) && s_adv_handle != 0xFFu) { build_and_apply_adv(s_adv_handle, s_dev_name); } if ((now_ms - s_last_msd_refresh_ms) >= OD_MSD_UPDATE_INTERVAL_MS) { s_last_msd_refresh_ms = now_ms; - if (s_adv_handle != 0xFFu && g_connection == 0xFFu) { + if (s_adv_handle != 0xFFu) { build_and_apply_adv(s_adv_handle, s_dev_name); } } + if (g_connection != 0xFFu + && !s_connection_timeout_close_requested + && (now_ms - s_connection_open_ms) >= OD_MAX_CONNECTION_MS) { + sl_status_t sc = sl_bt_connection_close(g_connection); + if (sc == SL_STATUS_OK) { + s_connection_timeout_close_requested = true; + printf("[OD] connection timeout (%lu ms): closing handle=%u\r\n", + (unsigned long)OD_MAX_CONNECTION_MS, (unsigned)g_connection); + } else { + printf("[OD] connection timeout close failed sc=0x%04lX\r\n", (unsigned long)sc); + } + } if (s_pending_dfu || s_pending_deep_sleep) { if (g_connection != 0xFFu) { @@ -919,6 +1911,9 @@ void opendisplay_ble_process(void) } if (s_pending_deep_sleep) { s_pending_deep_sleep = false; + opendisplay_display_power_off(); + od_buttons_arm_em4_wakeup(); + od_nfc_field_detect_arm_em4_wakeup(); EMU_EnterEM4(); } } @@ -933,3 +1928,39 @@ void opendisplay_ble_copy_msd_bytes(uint8_t out[16]) { memcpy(out, msd_payload, 16); } + +bool opendisplay_ble_nfc_read(uint8_t *type_out, uint8_t *data_out, uint16_t *data_len_io, uint16_t max_len) +{ + bool ok; + + if (!s_od_nfc_enabled) { + return false; + } + ok = od_nfc_read_record_raw(type_out, data_out, data_len_io, max_len); + + if (s_od_nfc_has_pwr_pin) { + GPIO_PinOutClear(s_od_nfc_pwr_port, s_od_nfc_pwr_pin); + GPIO_PinModeSet(s_od_nfc_pwr_port, s_od_nfc_pwr_pin, gpioModeInput, 1); + } + GPIO_PinModeSet(s_od_nfc_scl_port, s_od_nfc_scl_pin, gpioModeInput, 1); + GPIO_PinModeSet(s_od_nfc_sda_port, s_od_nfc_sda_pin, gpioModeInput, 1); + return ok; +} + +bool opendisplay_ble_nfc_write(uint8_t type, const uint8_t *data, uint16_t data_len) +{ + bool ok; + + if (!s_od_nfc_enabled) { + return false; + } + ok = od_nfc_write_record_raw(type, data, data_len); + + if (s_od_nfc_has_pwr_pin) { + GPIO_PinOutClear(s_od_nfc_pwr_port, s_od_nfc_pwr_pin); + GPIO_PinModeSet(s_od_nfc_pwr_port, s_od_nfc_pwr_pin, gpioModeInput, 1); + } + GPIO_PinModeSet(s_od_nfc_scl_port, s_od_nfc_scl_pin, gpioModeInput, 1); + GPIO_PinModeSet(s_od_nfc_sda_port, s_od_nfc_sda_pin, gpioModeInput, 1); + return ok; +} diff --git a/opendisplay_ble.h b/opendisplay_ble.h index 1e5be79..637be0d 100644 --- a/opendisplay_ble.h +++ b/opendisplay_ble.h @@ -2,6 +2,7 @@ #define OPENDISPLAY_BLE_H #include "sl_bt_api.h" +#include #include #ifdef __cplusplus @@ -27,6 +28,9 @@ uint16_t opendisplay_ble_get_app_version(void); void opendisplay_ble_copy_msd_bytes(uint8_t out[16]); +bool opendisplay_ble_nfc_read(uint8_t *type_out, uint8_t *data_out, uint16_t *data_len_io, uint16_t max_len); +bool opendisplay_ble_nfc_write(uint8_t type, const uint8_t *data, uint16_t data_len); + #ifdef __cplusplus } #endif diff --git a/opendisplay_config_parser.c b/opendisplay_config_parser.c index 9959ad2..713b41e 100644 --- a/opendisplay_config_parser.c +++ b/opendisplay_config_parser.c @@ -365,6 +365,64 @@ bool parseConfigBytes(uint8_t* configData, uint32_t configLen, struct GlobalConf offset = configLen - 2; } break; + + case CONFIG_PKT_NFC: // nfc_config (0x2A) + if (offset > configLen) { + printf("Offset overflow before nfc_config\r\n"); + globalConfig->loaded = false; + return false; + } + if (globalConfig->nfc_config_count < 2 && offset + sizeof(struct NfcConfig) <= configLen - 2) { + memcpy(&globalConfig->nfc_configs[globalConfig->nfc_config_count], &configData[offset], sizeof(struct NfcConfig)); + offset += sizeof(struct NfcConfig); + if (offset > configLen) { + printf("Offset overflow after nfc_config\r\n"); + globalConfig->loaded = false; + return false; + } + globalConfig->nfc_config_count++; + } else if (globalConfig->nfc_config_count >= 2) { + offset += sizeof(struct NfcConfig); + if (offset > configLen) { + printf("Offset overflow after nfc_config (skipped)\r\n"); + globalConfig->loaded = false; + return false; + } + } else { + printf("nfc_config: need %zu, have %u\r\n", sizeof(struct NfcConfig), (unsigned)(configLen - 2 - offset)); + globalConfig->loaded = false; + return false; + } + break; + + case CONFIG_PKT_FLASH: // flash_config (0x2B) + if (offset > configLen) { + printf("Offset overflow before flash_config\r\n"); + globalConfig->loaded = false; + return false; + } + if (globalConfig->flash_config_count < 2 && offset + sizeof(struct FlashConfig) <= configLen - 2) { + memcpy(&globalConfig->flash_configs[globalConfig->flash_config_count], &configData[offset], sizeof(struct FlashConfig)); + offset += sizeof(struct FlashConfig); + if (offset > configLen) { + printf("Offset overflow after flash_config\r\n"); + globalConfig->loaded = false; + return false; + } + globalConfig->flash_config_count++; + } else if (globalConfig->flash_config_count >= 2) { + offset += sizeof(struct FlashConfig); + if (offset > configLen) { + printf("Offset overflow after flash_config (skipped)\r\n"); + globalConfig->loaded = false; + return false; + } + } else { + printf("flash_config: need %zu, have %u\r\n", sizeof(struct FlashConfig), (unsigned)(configLen - 2 - offset)); + globalConfig->loaded = false; + return false; + } + break; default: printf("Unknown pkt 0x%02X @%u\r\n", packetId, (unsigned)(offset - 2)); @@ -384,9 +442,10 @@ bool parseConfigBytes(uint8_t* configData, uint32_t configLen, struct GlobalConf } globalConfig->loaded = true; - printf("Config parsed successfully: version=%d, displays=%d, leds=%d, sensors=%d, data_buses=%d, binary_inputs=%d\r\n", + printf("Config parsed successfully: version=%d, displays=%d, leds=%d, sensors=%d, data_buses=%d, binary_inputs=%d, nfc=%d, flash=%d\r\n", globalConfig->version, globalConfig->display_count, globalConfig->led_count, - globalConfig->sensor_count, globalConfig->data_bus_count, globalConfig->binary_input_count); + globalConfig->sensor_count, globalConfig->data_bus_count, globalConfig->binary_input_count, + globalConfig->nfc_config_count, globalConfig->flash_config_count); return true; } diff --git a/opendisplay_constants.h b/opendisplay_constants.h index 00db358..f3da0a3 100644 --- a/opendisplay_constants.h +++ b/opendisplay_constants.h @@ -11,6 +11,8 @@ #define CONFIG_PKT_BINARY_INPUT 0x25 #define CONFIG_PKT_WIFI 0x26 #define CONFIG_PKT_SECURITY 0x27 +#define CONFIG_PKT_NFC 0x2A +#define CONFIG_PKT_FLASH 0x2B #define CONFIG_CHUNK_SIZE 200 #define CONFIG_CHUNK_SIZE_WITH_PREFIX 202 @@ -19,4 +21,17 @@ #define GPIO_PIN_UNUSED 0xFF +/* OpenDisplay config struct DataBus.bus_type (matches toolbox presets, e.g. 0x01 = I2C). */ +#define OD_BUS_TYPE_I2C 1u + +#define OD_NFC_IC_AUTO 0u +#define OD_NFC_IC_TNB132M 1u + +#define OD_NFC_REC_TEXT 0u +#define OD_NFC_REC_URI 1u +#define OD_NFC_REC_WELL_KNOWN_RAW 2u +/* BLE payload: [mime_type_len][mime_type ascii][mime body UTF-8] -> NDEF MIME (SR). */ +#define OD_NFC_REC_MIME 3u +#define OD_NFC_REC_RAW_NDEF 4u + #endif diff --git a/opendisplay_display.cpp b/opendisplay_display.cpp index 20b64dd..acac622 100644 --- a/opendisplay_display.cpp +++ b/opendisplay_display.cpp @@ -5,6 +5,7 @@ #include "opendisplay_epd_map.h" #include "opendisplay_structs.h" #include "bb_epaper.h" +#include "em_cmu.h" #include "em_gpio.h" #include "em_system.h" #include "qr/qrcode.h" @@ -61,6 +62,33 @@ static bool decode_pin(uint8_t v, GPIO_Port_TypeDef *port_out, uint8_t *pin_out) return true; } +static void display_park_signal_pin(uint8_t pin_cfg) +{ + GPIO_Port_TypeDef port; + uint8_t pin; + + if (!decode_pin(pin_cfg, &port, &pin)) { + return; + } + GPIO_PinModeSet(port, pin, gpioModeDisabled, 0); +} + +void opendisplay_display_park_pins(void) +{ + const struct DisplayConfig *d = display_cfg(); + + if (d == nullptr) { + return; + } + CMU_ClockEnable(cmuClock_GPIO, true); + display_park_signal_pin(d->cs_pin); + display_park_signal_pin(d->data_pin); + display_park_signal_pin(d->clk_pin); + display_park_signal_pin(d->dc_pin); + display_park_signal_pin(d->reset_pin); + display_park_signal_pin(d->busy_pin); +} + static void display_power_set(bool on) { const struct GlobalConfig *cfg = opendisplay_get_global_config(); @@ -70,6 +98,10 @@ static void display_power_set(bool on) if (cfg == nullptr) { return; } + CMU_ClockEnable(cmuClock_GPIO, true); + if (!on) { + opendisplay_display_park_pins(); + } p = cfg->system_config.pwr_pin; if (p == 0xFFu) { p = OD_FALLBACK_DISPLAY_PWR_PIN; @@ -80,13 +112,19 @@ static void display_power_set(bool on) GPIO_PinModeSet(port, pin, gpioModePushPull, on ? 1u : 0u); } +void opendisplay_display_power_off(void) +{ + display_power_set(false); +} + typedef struct { char c; uint8_t col[5]; } Glyph5x7; static const Glyph5x7 s_font5x7[] = { - { ' ', { 0, 0, 0, 0, 0 } }, { '.', { 0, 0, 0x40, 0, 0 } }, { '0', { 0x3E, 0x51, 0x49, 0x45, 0x3E } }, + { ' ', { 0, 0, 0, 0, 0 } }, { '.', { 0, 0, 0x40, 0, 0 } }, { ':', { 0, 0x24, 0, 0, 0 } }, + { '0', { 0x3E, 0x51, 0x49, 0x45, 0x3E } }, { '1', { 0x00, 0x42, 0x7F, 0x40, 0x00 } }, { '2', { 0x62, 0x51, 0x49, 0x49, 0x46 } }, { '3', { 0x22, 0x49, 0x49, 0x49, 0x36 } }, { '4', { 0x18, 0x14, 0x12, 0x7F, 0x10 } }, { '5', { 0x2F, 0x49, 0x49, 0x49, 0x31 } }, { '6', { 0x3E, 0x49, 0x49, 0x49, 0x32 } }, @@ -122,7 +160,7 @@ static void set_pixel_row(uint8_t *row, int x, bool is4clr) } } -static void draw_text_row(uint8_t *row, int y, int x0, int y0, const char *s, int scale, bool is4clr) +static void draw_text_row(uint8_t *row, int y, int x0, int y0, const char *s, int scale, bool is4clr, int max_x) { int cursor = x0; const char *p; @@ -145,7 +183,7 @@ static void draw_text_row(uint8_t *row, int y, int x0, int y0, const char *s, in px = cursor + col * scale; for (sx = 0; sx < scale; sx++) { int rx = px + sx; - if (rx >= 0) { + if (rx >= 0 && (max_x < 0 || rx < max_x)) { set_pixel_row(row, rx, is4clr); } } @@ -163,6 +201,86 @@ static uint16_t text_width_px(const char *s, int scale) return (uint16_t)(strlen(s) * (size_t)(6 * scale)); } +static int boot_line_step(int scale) +{ + return scale * 10; +} + +static int boot_block_h(int scale) +{ + return 4 * boot_line_step(scale) + 7 * scale; +} + +static uint16_t boot_max_text_width(const char *const *lines, unsigned n, int scale) +{ + uint16_t max_w = 0u; + unsigned i; + for (i = 0u; i < n; i++) { + uint16_t tw = text_width_px(lines[i], scale); + if (tw > max_w) { + max_w = tw; + } + } + return max_w; +} + +static bool boot_layout_fit(uint16_t w, uint16_t h, int scale, int pad, int qr_modules, int *module_px_out, + int *qr_px_out, bool *qr_right_out, int *qr_x_out, int *qr_y_out, int *avail_w_out, + int *text_y_out, uint16_t max_text_w) +{ + int block_h = boot_block_h(scale); + int text_gap = pad; + int side_gap = pad; + int module_px; + int qr_px; + int min_dim = (int)((w < h) ? w : h); + int module_ideal = (min_dim - pad * 2) / (int)qr_modules; + if (module_ideal < 1) { + module_ideal = 1; + } + if (module_ideal > 6) { + module_ideal = 6; + } + + for (module_px = module_ideal; module_px >= 1; module_px--) { + qr_px = module_px * (int)qr_modules; + if (qr_px > (int)w - pad * 2) { + continue; + } + if ((int)w >= pad * 2 + (int)max_text_w + side_gap + qr_px + && (int)h >= pad * 2 + (block_h > qr_px ? block_h : qr_px)) { + int avail_w = (int)w - pad * 2 - qr_px - side_gap; + if ((int)max_text_w <= avail_w) { + int qr_x = (int)w - pad - qr_px; + int qr_y = pad; + int text_y = pad; + if (block_h < qr_px) { + text_y = pad + (qr_px - block_h) / 2; + } + *module_px_out = module_px; + *qr_px_out = qr_px; + *qr_right_out = true; + *qr_x_out = qr_x; + *qr_y_out = qr_y; + *avail_w_out = avail_w; + *text_y_out = text_y; + return true; + } + } + if ((int)w >= pad * 2 + (int)max_text_w && (int)h >= pad * 2 + block_h + text_gap + qr_px) { + *module_px_out = module_px; + *qr_px_out = qr_px; + *qr_right_out = false; + *qr_x_out = ((int)w - qr_px) / 2; + *qr_y_out = (int)h - pad - qr_px; + *avail_w_out = (int)w - pad * 2; + *text_y_out = pad; + return true; + } + } + return false; +} + static void bytes_to_hex(const uint8_t *in, uint16_t len, char *out, uint16_t out_size) { static const char *H = "0123456789ABCDEF"; @@ -240,10 +358,11 @@ static bool render_boot_screen(BBEPAPER &epd, const struct GlobalConfig *cfg) bool qr_right; int qr_x, qr_y, avail_w, text_y; char name_line[16]; + char fw_line[16]; const char *domain_line = "OPENDISPLAY.ORG"; char key_hex[33]; char k1[17], k2[17]; - uint16_t dW, nW, k1W, k2W; + uint16_t dW, nW, fwW, k1W, k2W; if (cfg == nullptr || cfg->display_count == 0u) { return false; @@ -285,53 +404,90 @@ static bool render_boot_screen(BBEPAPER &epd, const struct GlobalConfig *cfg) qr_size = qr.size; qr_modules = (uint16_t)(qr_size + 8u); - scale_text = (w >= 400u && h >= 300u) ? 2 : 1; - pad = 6 * scale_text; - module_px = ((int)((w < h) ? w : h) - pad * 2) / (int)qr_modules; - if (module_px < 1) { - module_px = 1; - } - if (module_px > 6) { - module_px = 6; - } - qr_px = module_px * (int)qr_modules; - qr_right = (w >= (uint16_t)(qr_px + 160)); - qr_x = qr_right ? ((int)w - pad - qr_px) : (((int)w - qr_px) / 2); - qr_y = qr_right ? pad : ((int)h - pad - qr_px); - avail_w = qr_right ? qr_x : (int)w; - text_y = pad; - if (qr_right) { - int block_h = 4 * (scale_text * 10) + 7 * scale_text; - if (qr_px > block_h) { - text_y = qr_y + (qr_px - block_h) / 2; - } else { - text_y = qr_y; - } - } - (void)snprintf(name_line, sizeof(name_line), "OD%06lX", (unsigned long)last3); + { + uint16_t ver = opendisplay_ble_get_app_version(); + (void)snprintf(fw_line, sizeof(fw_line), "FW: %u.%u", + (unsigned)((ver >> 8) & 0xFFu), (unsigned)(ver & 0xFFu)); + } bytes_to_hex(key, sizeof(key), key_hex, sizeof(key_hex)); memcpy(k1, key_hex, 16); k1[16] = '\0'; memcpy(k2, key_hex + 16, 16); k2[16] = '\0'; + { + static const char *boot_lines[] = { + domain_line, name_line, fw_line, k1, k2, + }; + uint16_t max_text_w; + bool layout_ok = false; + int try_scale; + + for (try_scale = (w >= 400u && h >= 300u) ? 2 : 1; try_scale >= 1 && !layout_ok; try_scale--) { + scale_text = try_scale; + pad = 6 * scale_text; + max_text_w = boot_max_text_width(boot_lines, 5u, scale_text); + layout_ok = boot_layout_fit(w, h, scale_text, pad, (int)qr_modules, &module_px, &qr_px, &qr_right, &qr_x, + &qr_y, &avail_w, &text_y, max_text_w); + } + if (!layout_ok) { + scale_text = 1; + pad = 6; + max_text_w = boot_max_text_width(boot_lines, 5u, scale_text); + module_px = 1; + qr_px = module_px * (int)qr_modules; + qr_right = false; + qr_x = ((int)w - qr_px) / 2; + qr_y = (int)h - pad - qr_px; + if (qr_y < pad) { + qr_y = pad; + } + avail_w = (int)w - pad * 2; + text_y = pad; + } + } + { static uint8_t s_boot_row[256]; bool is4clr = (epd._bbep.iFlags & BBEP_4COLOR) != 0; bool is3clr = (epd._bbep.iFlags & BBEP_3COLOR) != 0; int pitch = is4clr ? ((int)w + 3) / 4 : ((int)w + 7) / 8; uint8_t white_byte = is4clr ? 0x55u : 0xFFu; - int domX, nameX, k1X, k2X, y; + int domX, nameX, fwX, k1X, k2X, y; + int text_max_x; + int text_origin_x; dW = text_width_px(domain_line, scale_text); nW = text_width_px(name_line, scale_text); + fwW = text_width_px(fw_line, scale_text); k1W = text_width_px(k1, scale_text); k2W = text_width_px(k2, scale_text); - domX = (dW < (uint16_t)avail_w) ? ((avail_w - (int)dW) / 2) : pad; - nameX = (nW < (uint16_t)avail_w) ? ((avail_w - (int)nW) / 2) : pad; - k1X = (k1W < (uint16_t)avail_w) ? ((avail_w - (int)k1W) / 2) : pad; - k2X = (k2W < (uint16_t)avail_w) ? ((avail_w - (int)k2W) / 2) : pad; + text_origin_x = qr_right ? pad : ((int)w - (int)avail_w) / 2; + if (text_origin_x < pad) { + text_origin_x = pad; + } + text_max_x = qr_right ? (qr_x - pad) : (int)w; + domX = text_origin_x + ((avail_w - (int)dW) / 2); + nameX = text_origin_x + ((avail_w - (int)nW) / 2); + fwX = text_origin_x + ((avail_w - (int)fwW) / 2); + k1X = text_origin_x + ((avail_w - (int)k1W) / 2); + k2X = text_origin_x + ((avail_w - (int)k2W) / 2); + if (domX < pad) { + domX = pad; + } + if (nameX < pad) { + nameX = pad; + } + if (fwX < pad) { + fwX = pad; + } + if (k1X < pad) { + k1X = pad; + } + if (k2X < pad) { + k2X = pad; + } epd.setAddrWindow(0, 0, (int)w, (int)h); epd.startWrite(is4clr ? PLANE_1 : PLANE_0); @@ -339,10 +495,13 @@ static bool render_boot_screen(BBEPAPER &epd, const struct GlobalConfig *cfg) for (y = 0; y < (int)h; y++) { memset(s_boot_row, white_byte, (size_t)pitch); - draw_text_row(s_boot_row, y, domX, text_y, domain_line, scale_text, is4clr); - draw_text_row(s_boot_row, y, nameX, text_y + scale_text * 10, name_line, scale_text, is4clr); - draw_text_row(s_boot_row, y, k1X, text_y + scale_text * 30, k1, scale_text, is4clr); - draw_text_row(s_boot_row, y, k2X, text_y + scale_text * 40, k2, scale_text, is4clr); + draw_text_row(s_boot_row, y, domX, text_y, domain_line, scale_text, is4clr, text_max_x); + draw_text_row(s_boot_row, y, nameX, text_y + boot_line_step(scale_text), name_line, scale_text, is4clr, + text_max_x); + draw_text_row(s_boot_row, y, fwX, text_y + boot_line_step(scale_text) * 2, fw_line, scale_text, is4clr, + text_max_x); + draw_text_row(s_boot_row, y, k1X, text_y + boot_line_step(scale_text) * 3, k1, scale_text, is4clr, text_max_x); + draw_text_row(s_boot_row, y, k2X, text_y + boot_line_step(scale_text) * 4, k2, scale_text, is4clr, text_max_x); if (y >= qr_y && y < qr_y + qr_px) { int local_y = y - qr_y; diff --git a/opendisplay_display.h b/opendisplay_display.h index 4f28189..749b9c1 100644 --- a/opendisplay_display.h +++ b/opendisplay_display.h @@ -13,6 +13,8 @@ int opendisplay_display_direct_write_data(const uint8_t *payload, uint16_t paylo int opendisplay_display_direct_write_end(const uint8_t *payload, uint16_t payload_len, bool *refresh_ok); void opendisplay_display_abort(void); void opendisplay_display_boot_apply(void); +void opendisplay_display_park_pins(void); +void opendisplay_display_power_off(void); #ifdef __cplusplus } diff --git a/opendisplay_pipe.c b/opendisplay_pipe.c index 1384e4a..95a23ca 100644 --- a/opendisplay_pipe.c +++ b/opendisplay_pipe.c @@ -39,6 +39,17 @@ static uint8_t s_long_write_conn = 0xFFu; static bool s_crypto_ready; static uint8_t s_plain_buf[512]; static uint8_t s_crypto_payload_buf[513]; +static uint8_t s_nfc_rsp_buf[OD_PIPE_MAX_PAYLOAD]; +typedef struct { + bool active; + uint8_t connection; + uint8_t rec_type; + uint16_t total_len; + uint16_t received_len; + /* Match OD_NFC write staging (long MIME / vCard). */ + uint8_t data[512]; +} od_nfc_write_chunk_t; +static od_nfc_write_chunk_t s_nfc_write_chunk; #ifndef OD_ALLOW_PLAINTEXT_WITH_SECURITY #define OD_ALLOW_PLAINTEXT_WITH_SECURITY 0 @@ -51,6 +62,18 @@ static void cfg_chunk_reset(void) memset(&s_cfg_chunk, 0, sizeof(s_cfg_chunk)); } +static void nfc_write_chunk_reset(void) +{ + memset(&s_nfc_write_chunk, 0, sizeof(s_nfc_write_chunk)); + s_nfc_write_chunk.connection = 0xFFu; +} + +static bool nfc_rec_type_valid(uint8_t rec_type) +{ + return rec_type == OD_NFC_REC_TEXT || rec_type == OD_NFC_REC_URI || rec_type == OD_NFC_REC_WELL_KNOWN_RAW + || rec_type == OD_NFC_REC_MIME || rec_type == OD_NFC_REC_RAW_NDEF; +} + static uint32_t od_now_ms(void) { uint32_t ticks = sl_sleeptimer_get_tick_count(); @@ -815,6 +838,161 @@ static void handle_config_chunk(uint8_t connection, const uint8_t *data, uint16_ } } +static void handle_nfc_endpoint(uint8_t connection, const uint8_t *payload, uint16_t payload_len) +{ + uint16_t out_len; + uint8_t rec_type; + + if (payload == NULL || payload_len < 1u) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x01u }; + pipe_send(connection, err, sizeof(err)); + return; + } + + if (payload[0] == 0x00u) { + uint16_t max_out = (uint16_t)(OD_PIPE_MAX_PAYLOAD - 6u); + out_len = max_out; + if (!opendisplay_ble_nfc_read(&rec_type, &s_nfc_rsp_buf[6], &out_len, max_out)) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x02u }; + pipe_send(connection, err, sizeof(err)); + return; + } + s_nfc_rsp_buf[0] = 0x00u; + s_nfc_rsp_buf[1] = RESP_NFC_ENDPOINT; + s_nfc_rsp_buf[2] = 0x80u; + s_nfc_rsp_buf[3] = rec_type; + s_nfc_rsp_buf[4] = (uint8_t)((out_len >> 8) & 0xFFu); + s_nfc_rsp_buf[5] = (uint8_t)(out_len & 0xFFu); + pipe_send(connection, s_nfc_rsp_buf, (uint16_t)(6u + out_len)); + return; + } + + if (payload[0] == 0x01u) { + uint16_t text_len; + if (payload_len < 4u) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x01u }; + pipe_send(connection, err, sizeof(err)); + return; + } + rec_type = payload[1]; + text_len = (uint16_t)(((uint16_t)payload[2] << 8) | payload[3]); + if ((uint16_t)(4u + text_len) > payload_len) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x01u }; + pipe_send(connection, err, sizeof(err)); + return; + } + if (!nfc_rec_type_valid(rec_type)) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x05u }; + pipe_send(connection, err, sizeof(err)); + return; + } + if (!opendisplay_ble_nfc_write(rec_type, &payload[4], text_len)) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x03u }; + pipe_send(connection, err, sizeof(err)); + return; + } + { + uint8_t ok[] = { 0x00u, RESP_NFC_ENDPOINT, 0x81u }; + pipe_send(connection, ok, sizeof(ok)); + } + return; + } + + if (payload[0] == 0x10u) { + uint16_t total_len; + if (payload_len < 4u) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x01u }; + pipe_send(connection, err, sizeof(err)); + return; + } + rec_type = payload[1]; + total_len = (uint16_t)(((uint16_t)payload[2] << 8) | payload[3]); + if (!nfc_rec_type_valid(rec_type)) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x05u }; + pipe_send(connection, err, sizeof(err)); + return; + } + if (total_len == 0u || total_len > sizeof(s_nfc_write_chunk.data)) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x06u }; + pipe_send(connection, err, sizeof(err)); + return; + } + nfc_write_chunk_reset(); + s_nfc_write_chunk.active = true; + s_nfc_write_chunk.connection = connection; + s_nfc_write_chunk.rec_type = rec_type; + s_nfc_write_chunk.total_len = total_len; + { + uint8_t ok[] = { 0x00u, RESP_NFC_ENDPOINT, 0x82u }; + pipe_send(connection, ok, sizeof(ok)); + } + return; + } + + if (payload[0] == 0x11u) { + uint16_t chunk_len; + if (!s_nfc_write_chunk.active || s_nfc_write_chunk.connection != connection) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x07u }; + pipe_send(connection, err, sizeof(err)); + return; + } + if (payload_len < 2u) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x01u }; + pipe_send(connection, err, sizeof(err)); + return; + } + chunk_len = (uint16_t)(payload_len - 1u); + if ((uint16_t)(s_nfc_write_chunk.received_len + chunk_len) > s_nfc_write_chunk.total_len) { + nfc_write_chunk_reset(); + { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x08u }; + pipe_send(connection, err, sizeof(err)); + } + return; + } + memcpy(&s_nfc_write_chunk.data[s_nfc_write_chunk.received_len], &payload[1], chunk_len); + s_nfc_write_chunk.received_len = (uint16_t)(s_nfc_write_chunk.received_len + chunk_len); + { + uint8_t ok[] = { 0x00u, RESP_NFC_ENDPOINT, 0x82u }; + pipe_send(connection, ok, sizeof(ok)); + } + return; + } + + if (payload[0] == 0x12u) { + if (!s_nfc_write_chunk.active || s_nfc_write_chunk.connection != connection) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x07u }; + pipe_send(connection, err, sizeof(err)); + return; + } + if (s_nfc_write_chunk.received_len != s_nfc_write_chunk.total_len) { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x09u }; + pipe_send(connection, err, sizeof(err)); + return; + } + if (!opendisplay_ble_nfc_write(s_nfc_write_chunk.rec_type, s_nfc_write_chunk.data, + s_nfc_write_chunk.total_len)) { + nfc_write_chunk_reset(); + { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x03u }; + pipe_send(connection, err, sizeof(err)); + } + return; + } + nfc_write_chunk_reset(); + { + uint8_t ok[] = { 0x00u, RESP_NFC_ENDPOINT, 0x81u }; + pipe_send(connection, ok, sizeof(ok)); + } + return; + } + + { + uint8_t err[] = { 0xFFu, RESP_NFC_ENDPOINT, 0xFFu, 0x04u }; + pipe_send(connection, err, sizeof(err)); + } +} + static void dispatch(uint8_t connection, uint16_t cmd, const uint8_t *payload, uint16_t payload_len) { uint8_t auth_rsp[32]; @@ -883,6 +1061,9 @@ static void dispatch(uint8_t connection, uint16_t cmd, const uint8_t *payload, u pipe_send(connection, ok, sizeof(ok)); break; } + case CMD_NFC_ENDPOINT: + handle_nfc_endpoint(connection, payload, payload_len); + break; case CMD_DIRECT_WRITE_START: handle_direct_write_start(connection, payload, payload_len); break; @@ -971,6 +1152,7 @@ void opendisplay_pipe_on_connection_closed(void) s_long_write_len = 0; s_long_write_conn = 0xFFu; cfg_chunk_reset(); + nfc_write_chunk_reset(); opendisplay_display_abort(); } diff --git a/opendisplay_protocol.h b/opendisplay_protocol.h index ba60a00..2cce2a9 100644 --- a/opendisplay_protocol.h +++ b/opendisplay_protocol.h @@ -12,6 +12,8 @@ #define CMD_DIRECT_WRITE_DATA 0x0071u #define CMD_DIRECT_WRITE_END 0x0072u #define CMD_LED_ACTIVATE 0x0073u +/* NFC: sub byte0 0=read, 1=write (+rec_type,len16,payload), 0x10/0x11/0x12=chunked write; rec_type OD_NFC_REC_* */ +#define CMD_NFC_ENDPOINT 0x0082u #define CMD_REBOOT 0x000Fu #define CMD_AUTHENTICATE 0x0050u #define CMD_ENTER_DFU 0x0051u @@ -34,6 +36,7 @@ #define RESP_MSD_READ 0x44u #define RESP_AUTHENTICATE 0x50u #define RESP_LED_ACTIVATE_ACK 0x73u +#define RESP_NFC_ENDPOINT 0x82u #define RESP_ENTER_DFU 0x51u #define RESP_DEEP_SLEEP 0x52u diff --git a/opendisplay_structs.h b/opendisplay_structs.h index aa5c72d..5853c26 100644 --- a/opendisplay_structs.h +++ b/opendisplay_structs.h @@ -119,6 +119,42 @@ struct BinaryInputs { uint8_t reserved[14]; } __attribute__((packed)); +struct NfcConfig { + uint8_t instance_number; + uint8_t nfc_ic_type; + uint8_t bus_instance; + uint8_t flags; + uint8_t field_detect_pin; + uint8_t field_detect_mode; + uint8_t field_detect_active; + uint8_t field_detect_debounce_ms; + uint8_t power_pin; + uint8_t power_active; + uint8_t power_on_delay_ms; + uint8_t power_off_delay_ms; + uint8_t adv_button_byte_index; + uint8_t adv_button_button_id; + uint8_t reserved_pin_1; + uint8_t reserved_pin_2; + uint8_t reserved[16]; +} __attribute__((packed)); + +struct FlashConfig { + uint8_t instance_number; + uint8_t flash_ic_type; + uint8_t bus_instance; + uint8_t flags; + uint8_t mosi_pin; + uint8_t sck_pin; + uint8_t cs_pin; + uint8_t power_pin; + uint8_t power_active; + uint8_t power_on_delay_ms; + uint8_t power_off_delay_ms; + uint8_t mode; + uint8_t reserved[20]; +} __attribute__((packed)); + struct GlobalConfig { struct SystemConfig system_config; struct ManufacturerData manufacturer_data; @@ -133,6 +169,10 @@ struct GlobalConfig { uint8_t data_bus_count; struct BinaryInputs binary_inputs[4]; uint8_t binary_input_count; + struct NfcConfig nfc_configs[2]; + uint8_t nfc_config_count; + struct FlashConfig flash_configs[2]; + uint8_t flash_config_count; uint8_t version; uint8_t minor_version; bool loaded; diff --git a/third_party/bb_epaper/src/bb_ep.inl b/third_party/bb_epaper/src/bb_ep.inl index 295b42f..0d43705 100644 --- a/third_party/bb_epaper/src/bb_ep.inl +++ b/third_party/bb_epaper/src/bb_ep.inl @@ -1105,20 +1105,26 @@ const uint8_t epd35yr_init_fast[] PROGMEM = const uint8_t epd42yr_init_full[] PROGMEM = { - 0x02, 0x4d, 0x78, - 0x03, 0x00, 0x0f, 0x29, - 0x08, 0x06, 0x0d, 0x12, 0x24, 0x25, 0x12, 0x29, 0x10, // BTST - 0x02, 0x30, 0x08, - 0x02, 0x50, 0x37, - 0x05, UC8151_TRES, 0x01, 144, 0x01, 44, // resolution (400x300) - 0x02, 0xae, 0xcf, - 0x02, 0xb0, 0x13, - 0x02, 0xbd, 0x07, - 0x02, 0xbe, 0xfe, - 0x02, 0xe9, 0x01, - 0x01, 0x04, // power on - BUSY_WAIT, - 0 + 0x02, 0x4d, 0x78, + 0x03, 0x00, 0x0f, 0x09, + 0x07, 0x01, 0x07, 0x00, 0x22, 0x78, 0x0a, 0x22, + 0x04, 0x03, 0x10, 54, 0x44, + 0x08, 0x06, 0x0f, 0x0a, 0x2f, 0x25 ,0x22, 0x2e, 0x21, + 0x02, 0x30, 0x08, + 0x02, 0x41, 0x00, + 0x02, 0x50, 0x37, + 0x03, 0x60, 0x02, 0x02, + 0x05, UC8151_TRES, 0x01, 144, 0x01, 44, // resolution (400x300) + 0x05, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xe7, 0x1c, + 0x02, 0xe3, 0x22, + 0x02, 0xe0, 0x00, + 0x02, 0xb4, 0xd0, + 0x02, 0xb5, 0x03, + 0x02, 0xe9, 0x01, + 0x01, 0x04, // power on + BUSY_WAIT, + 0 }; const uint8_t epd42yr_init_fast[] PROGMEM = @@ -3264,7 +3270,7 @@ const EPD_PANEL panelDefs[] PROGMEM = { {200, 200, 0, epd154yr_init_full, epd154yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP154YR_200x200 {184, 360, 0, epd266yr_init_full, epd266yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP266YR2_184x360 // 60 - {400, 300, 0, epd42yr_init_full, epd42yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP42YR_400x300 + {400, 300, 0, epd42yr_init_full, epd42yr_init_fast, NULL, BBEP_4COLOR | BBEP_SKIP_BUSY_WAIT, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP42YR_400x300 // {792, 272, 0, epd579yr_init_full, epd579yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP579YR_792x272 {160, 296, 0, epd215yr_init_full, epd215yr_init_full, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP215YR_160x296 {680, 480, 0, epd1085_init_full, NULL, NULL, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP1085_1360x480 @@ -3445,6 +3451,15 @@ bool bbepIsBusy(BBEPDISP *pBBEP) // // Toggle the reset line to wake up the eink from deep sleep // +static void bbep_wait_busy_panel(BBEPDISP *pBBEP) +{ + if (pBBEP != NULL && (pBBEP->iFlags & BBEP_SKIP_BUSY_WAIT) != 0) { + delay(200); + } else { + bbepWaitBusy(pBBEP); + } +} + void bbepWakeUp(BBEPDISP *pBBEP) { if (!pBBEP) return; @@ -3454,7 +3469,7 @@ void bbepWakeUp(BBEPDISP *pBBEP) delay(10); digitalWrite(pBBEP->iRSTPin, HIGH); delay(20); - bbepWaitBusy(pBBEP); + bbep_wait_busy_panel(pBBEP); } /* bbepWakeUp() */ // // Set the memory window for future writes into panel memory @@ -3568,7 +3583,7 @@ void bbepSleep(BBEPDISP *pBBEP, int bDeep) } } else if (pBBEP->iFlags & BBEP_4COLOR) { bbepCMD2(pBBEP, 0x02, 0x00); // power off - bbepWaitBusy(pBBEP); + bbep_wait_busy_panel(pBBEP); if (bDeep) { bbepCMD2(pBBEP, 0x07, 0xa5); // deep sleep } @@ -3661,7 +3676,7 @@ void bbepSendCMDSequence(BBEPDISP *pBBEP, const uint8_t *pSeq) if (iLen == MAKE_LUTS) { bbepMakeLUTs(pBBEP); } else if (iLen == BUSY_WAIT) { - bbepWaitBusy(pBBEP); + bbep_wait_busy_panel(pBBEP); } else if (iLen == EPD_RESET) { bbepWakeUp(pBBEP); } else { diff --git a/third_party/bb_epaper/src/bb_epaper.h b/third_party/bb_epaper/src/bb_epaper.h index f38e163..1395c19 100644 --- a/third_party/bb_epaper/src/bb_epaper.h +++ b/third_party/bb_epaper/src/bb_epaper.h @@ -295,6 +295,7 @@ enum { #define BBEP_SPLIT_BUFFER 0x0400 #define BBEP_HAS_SECOND_PLANE 0x0800 #define BBEP_NEEDS_EXTRA_INIT 0x1000 +#define BBEP_SKIP_BUSY_WAIT 0x2000 /* skip BUSY_WAIT in init/wake/sleep; refresh still polls busy */ #define BBEP_BLACK 0 #define BBEP_WHITE 1