From 7a75940a3ed26bf506981f60578fc036584dd630 Mon Sep 17 00:00:00 2001 From: whowechina Date: Sat, 16 Mar 2024 17:02:52 +0800 Subject: [PATCH] Use aic_pico as a library via submodule --- .gitmodules | 3 + .vscode/settings.json | 9 + Production/Firmware/chu_pico.uf2 | Bin 143360 -> 147968 bytes firmware/CMakeLists.txt | 5 +- firmware/src/CMakeLists.txt | 11 +- firmware/src/aime.c | 712 ------------------------------- firmware/src/aime.h | 17 - firmware/src/commands.c | 66 +-- firmware/src/config.c | 1 + firmware/src/config.h | 1 + firmware/src/main.c | 34 +- firmware/src/pn532.c | 497 --------------------- firmware/src/pn532.h | 31 -- 13 files changed, 92 insertions(+), 1295 deletions(-) create mode 100644 .gitmodules create mode 100644 .vscode/settings.json delete mode 100644 firmware/src/aime.c delete mode 100644 firmware/src/aime.h delete mode 100644 firmware/src/pn532.c delete mode 100644 firmware/src/pn532.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0aadfd0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "aic_pico"] + path = firmware/modules/aic_pico + url = https://github.com/whowechina/aic_pico diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..100b0f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.associations": { + "mpr121.h": "c", + "board_defs.h": "c", + "pn532.h": "c", + "cli.h": "c", + "vl53l0x.h": "c" + } +} \ No newline at end of file diff --git a/Production/Firmware/chu_pico.uf2 b/Production/Firmware/chu_pico.uf2 index 55769af7c5a1bbc6bf6f896bb8238f65164a8f3b..b8c60dca9f7a5393d1ccaba28e4d00da9f40defe 100644 GIT binary patch delta 34352 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�T>yndBVBL_I$1|7U|EwYQ`%AczrGCKm_3ywj z*Ilvnn&>&6C;omRR(?_h_ZN7c|3@$%d?!e=nt*6z(G)BG06+-X|4RPaRBXV)IvR29b0`KWVrZN0Pw>sH1cG8{N(yKZwJ3JzH2 z9CN-B6@#1j=#)L2FEp5;@iC1WMVEtU5JVE+qCu^XKjtIG2bvOvpv1RMqo|V@gsrar zzI$%F+lh&In_s_k@AsYe^PYRYbN*W(++%`P!l=}UW9U5Ijdjpv9{=oM_bm4-!ohTW znEuTTe*P%zz`Y{NlNEMqIxVcbRyFR0@-3H1UA{(TL1U)t1e|>Xb@6f0=e>`+e-H%Yx-3xuF2Jn^nhm1#_ zeOMT@UKl??(T2_2K(_)dz%cHA1+Ly8jPrp<@hn@o`n@!^o=0eb+N3<=^ zMwoH`oy}vH-eJDuo}c{#;NJoLhWnlr#xKB}r-f0?Y5}-zd=6&--1nR?HUm#=fxruz z@eRxsFi!_O0N;=RCNhta?HKNk}<8;A3hw^A?+wP zkO${St83=ODunx-&MP{*uIn_HcXf2Mb#iYKsa$C;WqZrs!l?<1c9Az z3gcB^J#Z-Qtb}~RCYWMm(Jy*mEOJorDf zc`&1afnfT1%=xJ$2^%eliDbuMUgSEp5+bfFMJ zXS@(r27}+yTpg>_G+K*_J_Ezma-a&B2P^`b0BapwPzrZTvwC4r;?yRT9QDbdG>?G^ z?;-eE)FLR4yibRi3FK*`P;2*L5SQ(w@LI$r*X76>mDnf0FA73Yim)>T@Bm<<;phV` z=LRHWTY247kll=|7OVkOqYKjN&Hb#?GYl;TIW&8}>K?{$Mq4WI*JvN}96r8>Hy%Bo z<>lkThrY*l$ULtlu!KsKtihA-(rQ^s&cv(4ttn^?)Td++UG378oFyOOM={HJmGY8f zcYs7nwidpM7w8qGXdooN@X={9#)qgw_8{aWTlTV?kn67LzZnc8$u0ev@kD zhyCZ#g<0vqL>-HXj#%_32qQqEm8N&pf11Cs&^km-ph?XK3}6qGN<*NwGXcNy1<9Q~ zT|*^^%4r~?28rfk?-6Q|8`YIA?0m(#p)S&^E%?x0XCz(hYaVseq`%afJerveqw;jT&|%pr!|oVN8pCZ>AzC zigS{}!@l{bndXkDZXXnk;%T+UXGf2ZICZ9hk80#z^_!2P$~350zOUvRbbU5H$~!$L zB@7DFEeMQ3jfJj;4<=4p;KC}wb?5S_-|3NbdKF4?Z9vWSQ?DI9XxsU9WbvgT+s3Pe zpQ_v=2L|dN);9L}Ss5-!OXWlA#vq+&hoIDNTMpz{4pOCTQa=Z&8k?ffs-gr>?c#Fk z=(3_^;hZ{tVRrIOgmy?RsYlXZV?|2Dq#aMuo3$)-4S zMz$MT;vdhU&u~!$S|qH&aTq>)hQ#U@akNdcScI`M2RqWm@J*y5kc^4^SX=#X@i}#c z$xVyvLZ4y+_2j2oW0;x*R&;BV?rpyp+DLdn3?7iT4g}wXi-1{f6u>|uS`CU#0 zXJ&fh%$sis6!e|Wl%AD)f803!=Js6vj&@a7NR91x;pO9AU>33prGY>ec#l9UB_&Nj z4C2GWz*+YObU=-_fo%ZPw~Zk{fENTF^Xsf`#a<0ne4dVdEpnKxb`~1B$7M>-kz@-&?^fH4`m;eS(mn}@kuHRvl3q{}7S3bnqO&@7bX2E<%5?jn|$C&IrM|W_1NM~cn5zgqRID6?c z%<=8kmNRZYvr8BVPIv|*3e3bSI-~O_- zvC7{uxaW(*M8V6bEFWWGHY#nQ7Atg!F?YAb5r6(bk>(a1gnL4!zK0!5gyU-EDJ4VA zE2CA}zyzjhccwZ-Rica=u-P;=VGt3ZJ%H$9uxe*gdg delta 31839 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ŁB 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 SfuuZh63l{Tq%&8!?EV4fxY~RG diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index b328053..dea1997 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -1,11 +1,14 @@ cmake_minimum_required(VERSION 3.12) +project(chu_pico C CXX ASM) # Pull in SDK (must set be before project) include(pico_sdk_import.cmake) -project(iidx_pico C CXX ASM) set(CMAKE_C_STANDARD 11) pico_sdk_init() +add_subdirectory(modules/aic_pico/firmware aic) +include_directories(modules/aic_pico/firmware/include) + add_subdirectory(src) diff --git a/firmware/src/CMakeLists.txt b/firmware/src/CMakeLists.txt index 88b5b02..611c266 100644 --- a/firmware/src/CMakeLists.txt +++ b/firmware/src/CMakeLists.txt @@ -1,12 +1,8 @@ -set(BTSTACK_ROOT ${PICO_SDK_PATH}/lib/btstack) -set(LWIP_ROOT ${PICO_SDK_PATH}/lib/lwip) - function(make_firmware board board_def) pico_sdk_init() add_executable(${board} main.c slider.c air.c rgb.c save.c config.c commands.c - aime.c cli.c lzfx.c - vl53l0x.c mpr121.c pn532.c usb_descriptors.c) + cli.c lzfx.c vl53l0x.c mpr121.c usb_descriptors.c) target_compile_definitions(${board} PUBLIC ${board_def}) pico_enable_stdio_usb(${board} 1) pico_enable_stdio_uart(${board} 0) @@ -15,11 +11,9 @@ function(make_firmware board board_def) target_compile_options(${board} PRIVATE -Wall -Werror -Wfatal-errors -O3) target_include_directories(${board} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) - target_include_directories(${board} PRIVATE - ${BTSTACK_ROOT}/src - ${LWIP_ROOT}/src/include) target_link_libraries(${board} PRIVATE + aic pico_multicore pico_stdlib hardware_pio hardware_pwm hardware_flash hardware_adc hardware_i2c hardware_watchdog tinyusb_device tinyusb_board) @@ -31,4 +25,3 @@ function(make_firmware board board_def) endfunction() make_firmware(chu_pico BOARD_CHU_PICO) - diff --git a/firmware/src/aime.c b/firmware/src/aime.c deleted file mode 100644 index 6e5a16b..0000000 --- a/firmware/src/aime.c +++ /dev/null @@ -1,712 +0,0 @@ -/* - * AIME Reader - * WHowe - * - * Use PN532 to read AIME - */ - -#include -#include - -#include "bsp/board.h" -#include "hardware/gpio.h" -#include "hardware/i2c.h" - -#include "board_defs.h" - -#include "i2c_hub.h" - -#include "pn532.h" - -#define FRAME_TIMEOUT 200000 - -enum { - CMD_GET_FW_VERSION = 0x30, - CMD_GET_HW_VERSION = 0x32, - // Card read - CMD_START_POLLING = 0x40, - CMD_STOP_POLLING = 0x41, - CMD_CARD_DETECT = 0x42, - CMD_CARD_SELECT = 0x43, - CMD_CARD_HALT = 0x44, - // MIFARE - CMD_MIFARE_KEY_SET_A = 0x50, - CMD_MIFARE_AUTHORIZE_A = 0x51, - CMD_MIFARE_READ = 0x52, - CMD_MIFARE_WRITE = 0x53, - CMD_MIFARE_KEY_SET_B = 0x54, - CMD_MIFARE_AUTHORIZE_B = 0x55, - // Boot,update - CMD_TO_UPDATER_MODE = 0x60, - CMD_SEND_HEX_DATA = 0x61, - CMD_TO_NORMAL_MODE = 0x62, - CMD_SEND_BINDATA_INIT = 0x63, - CMD_SEND_BINDATA_EXEC = 0x64, - // FeliCa - CMD_FELICA_PUSH = 0x70, - CMD_FELICA_OP = 0x71, - CMD_FELICA_OP_POLL = 0x00, - CMD_FELICA_OP_READ = 0x06, - CMD_FELICA_OP_WRITE = 0x08, - CMD_FELICA_OP_GET_SYSTEM_CODE = 0x0C, - CMD_FELICA_OP_NDA_A4 = 0xA4, - // LED board - CMD_EXT_BOARD_LED = 0x80, - CMD_EXT_BOARD_LED_RGB = 0x81, - CMD_EXT_BOARD_LED_RGB_UNKNOWN = 0x82, - CMD_EXT_BOARD_INFO = 0xf0, - CMD_EXT_FIRM_SUM = 0xf2, - CMD_EXT_SEND_HEX_DATA = 0xf3, - CMD_EXT_TO_BOOT_MODE = 0xf4, - CMD_EXT_TO_NORMAL_MODE = 0xf5, -}; - -enum { - STATUS_OK = 0, - STATUS_NFCRW_INIT_ERROR = 1, - STATUS_NFCRW_FIRMWARE_UP_TO_DATE = 3, - STATUS_NFCRW_ACCESS_ERROR = 4, - STATUS_CARD_DETECT_TIMEOUT = 5, - STATUS_CARD_DETECT_ERROR = 32, - STATUS_FELICA_ERROR = 33, -}; - -const char *fw_version[] = { "TN32MSEC003S F/W Ver1.2", "\x94" }; -const char *hw_version[] = { "TN32MSEC003S H/W Ver3.0", "837-15396" }; -const char *led_info[] = { "15084\xFF\x10\x00\x12", "000-00000\xFF\x11\x40" }; -static int baudrate_mode = 0; - -static struct { - bool enabled; // feature enabled - bool active; // currently active - uint8_t idm[8]; - const uint8_t pmm[8]; - const uint8_t syscode[2]; -} virtual_aic = { true, false, "", "\x00\xf1\x00\x00\x00\x01\x43\x00", "\x88\xb4" }; - -void aime_set_baudrate(int mode) -{ - baudrate_mode = (mode == 0) ? 0 : 1; -} - -void aime_set_virtual_aic(bool enable) -{ - virtual_aic.enabled = enable; -} - -static int aime_interface = -1; - -void aime_init(int interface) -{ - aime_interface = interface; - - i2c_select(I2C_PORT, 1 << 5); // PN532 on IR1 (I2C mux chn 5) - pn532_config_sam(); -} - -static uint8_t mifare_keys[2][6]; // 'KeyA' and 'KeyB' - -static union __attribute__((packed)) { - struct { - uint8_t len; - uint8_t addr; - uint8_t seq; - uint8_t cmd; - uint8_t status; - uint8_t payload_len; - uint8_t payload[]; - }; - uint8_t raw[256]; -} response; - -static union __attribute__((packed)) { - struct { - uint8_t len; - uint8_t addr; - uint8_t seq; - uint8_t cmd; - uint8_t payload_len; - union { - struct { - uint8_t uid[4]; - uint8_t block_id; - } mifare; - struct { - uint8_t idm[8]; - uint8_t len; - uint8_t code; - uint8_t data[0]; - } felica; - uint8_t payload[250]; - }; - }; - uint8_t raw[256]; -} request; - -struct { - bool active; - uint8_t len; - uint8_t check_sum; - bool escaping; - uint64_t time; -} req_ctx; - -static void build_response(int payload_len) -{ - response.len = payload_len + 6; - response.addr = request.addr; - response.seq = request.seq; - response.cmd = request.cmd; - response.status = STATUS_OK; - response.payload_len = payload_len; -} - -static void send_response() -{ - uint8_t checksum = 0; - - uint8_t sync = 0xe0; - tud_cdc_n_write(aime_interface, &sync, 1); - - for (int i = 0; i < response.len; i++) { - uint8_t c = response.raw[i]; - checksum += c; - if (c == 0xe0 || c == 0xd0) { - uint8_t escape = 0xd0; - tud_cdc_n_write(aime_interface, &escape, 1); - c--; - } - tud_cdc_n_write(aime_interface, &c, 1); - } - - tud_cdc_n_write(aime_interface, &checksum, 1); - tud_cdc_n_write_flush(aime_interface); -} - -static void send_simple_response(uint8_t status) -{ - build_response(0); - response.status = status; - send_response(); -} - -static void cmd_to_normal_mode() -{ - send_simple_response(pn532_firmware_ver() ? STATUS_NFCRW_FIRMWARE_UP_TO_DATE - : STATUS_NFCRW_INIT_ERROR); -} - -static void cmd_fake_version(const char *version[]) -{ - int len = strlen(version[baudrate_mode]); - build_response(len); - memcpy(response.payload, version[baudrate_mode], len); - send_response(); -} - -static void cmd_key_set(uint8_t key[6]) -{ - memcpy(key, request.payload, 6); - send_simple_response(STATUS_OK); -} - -static void cmd_set_polling(bool enabled) -{ - pn532_set_rf_field(0, enabled ? 1 : 0); - send_simple_response(STATUS_OK); -} - -typedef struct __attribute__((packed)) { - uint8_t count; - uint8_t type; - uint8_t id_len; - union { - struct { - uint8_t idm[8]; - uint8_t pmm[8]; - uint8_t syscode[2]; - }; - uint8_t uid[6]; - }; -} card_info_t; - -static void handle_mifare_card(const uint8_t *uid, int len) -{ - card_info_t *card = (card_info_t *) response.payload; - - build_response(len > 4 ? 10 : 7); - card->count = 1; - card->type = 0x10; - card->id_len = len; - - memcpy(card->uid, uid, len); -} - -static void handle_felica_card(const uint8_t felica_ids[18]) -{ - build_response(19); - card_info_t *card = (card_info_t *) response.payload; - - card->count = 1; - card->type = 0x20; - card->id_len = 16; - memcpy(card->idm, felica_ids, 8); - memcpy(card->pmm, felica_ids + 8, 8); -} - -static void fake_felica_card() -{ - build_response(19); - card_info_t *card = (card_info_t *) response.payload; - - card->count = 1; - card->type = 0x20; - card->id_len = 16; - memcpy(card->idm, virtual_aic.idm, 8); - memcpy(card->pmm, virtual_aic.pmm, 8); -} - -static void handle_no_card() -{ - build_response(1); - card_info_t *card = (card_info_t *) response.payload; - - card->count = 0; - response.status = STATUS_OK; -} - -static void printf_hex(const uint8_t *hex, int len, const char *tail) -{ - for (int i = 0; i < len; i ++) { - printf(" %02x", hex[i]); - } - printf("%s", tail); -} - -static void cmd_detect_card() -{ - uint8_t buf[18]; - - virtual_aic.active = false; - - int len = sizeof(buf); - if (pn532_poll_mifare(buf, &len)) { - printf("Detected Mifare - "); - printf_hex(buf, len, "\n"); - - if (virtual_aic.enabled) { - virtual_aic.active = true; - memset(virtual_aic.idm, 0, 8); - memcpy(virtual_aic.idm, "\x01\x01", 2); - memcpy(virtual_aic.idm + 2, buf, len); - - printf("Virtual AIC -"); - printf_hex(virtual_aic.idm, 8, ","); - printf_hex(virtual_aic.pmm, 8, ","); - printf_hex(virtual_aic.syscode, 2, "\n"); - - fake_felica_card(); - } else { - handle_mifare_card(buf, len); - } - } else if (pn532_poll_felica(buf, buf + 8, buf + 16, false)) { - printf("Detected Felica -"); - printf_hex(buf, 8, ","); - printf_hex(buf + 8, 8, ","); - printf_hex(buf + 16, 2, "\n"); - - handle_felica_card(buf); - } else { - handle_no_card(); - } - - send_response(); -} - -static void cmd_card_select() -{ - printf("CARD SELECT %d\n", request.payload_len); - send_simple_response(STATUS_OK); -} - -static void cmd_mifare_auth(int type) -{ - printf("MIFARE AUTH\n"); - pn532_mifare_auth(request.mifare.uid, request.mifare.block_id, type, mifare_keys[type]); - send_simple_response(STATUS_OK); -} - -static void cmd_mifare_read() -{ - printf("MIFARE READ %02x %02x %02x %02x %02x\n", request.mifare.block_id, - request.mifare.uid[0], request.mifare.uid[1], request.mifare.uid[2], - request.mifare.uid[3]); - build_response(16); - memset(response.payload, 0, 16); - pn532_mifare_read(request.mifare.block_id, response.payload); - send_response(); -} - -static void cmd_mifare_halt() -{ - printf("MIFARE HALT\n"); - send_simple_response(STATUS_OK); -} - -typedef struct __attribute__((packed)) { - uint8_t len; - uint8_t code; - uint8_t idm[8]; - uint8_t data[0]; -} felica_resp_t; - -typedef struct __attribute__((packed)) { - uint8_t idm[8]; - uint8_t pmm[8]; - uint8_t syscode[2]; -} card_id_t; - -static int cmd_felica_poll(const card_id_t *card) -{ - typedef struct __attribute__((packed)) { - uint8_t pmm[8]; - uint8_t syscode[2]; - } felica_poll_resp_t; - - printf("FELICA POLL\n"); - - felica_resp_t *felica_resp = (felica_resp_t *) response.payload; - felica_poll_resp_t *poll_resp = (felica_poll_resp_t *) felica_resp->data; - - if (virtual_aic.active) { - memcpy(poll_resp->pmm, virtual_aic.pmm, 8); - memcpy(poll_resp->syscode, virtual_aic.syscode, 2); - } else { - memcpy(poll_resp->pmm, card->pmm, 8); - memcpy(poll_resp->syscode, card->syscode, 2); - } - - return sizeof(*poll_resp); -} - -static int cmd_felica_get_syscode(const card_id_t *card) -{ - printf("FELICA GET_SYSTEM_CODE\n"); - felica_resp_t *felica_resp = (felica_resp_t *) response.payload; - felica_resp->data[0] = 0x01; - if (virtual_aic.active) { - memcpy(felica_resp->data, virtual_aic.syscode, 2); - } else { - felica_resp->data[1] = card->syscode[0]; - felica_resp->data[2] = card->syscode[1]; - } - return 3; -} - -static int cmd_felica_read() -{ - printf("FELICA READ\n"); - uint8_t *req_data = request.felica.data; - - if (req_data[8] != 1) { - printf("Felica Encap READ Error: service_num != 1\n"); - return -1; - } - - uint16_t svc_code = req_data[9] | (req_data[10] << 8); - - typedef struct __attribute__((packed)) { - uint8_t rw_status[2]; - uint8_t block_num; - uint8_t block_data[0][16]; - } encap_read_resp_t; - - felica_resp_t *felica_resp = (felica_resp_t *) response.payload; - encap_read_resp_t *read_resp = (encap_read_resp_t *) felica_resp->data; - - uint8_t *block = req_data + 11; - uint8_t block_num = block[0]; - - memset(read_resp->block_data, 0, block_num * 16); - - for (int i = 0; i < block_num; i++) { - uint16_t block_id = (block[i * 2 + 1] << 8) | block[i * 2 + 2]; - if (block_id == 0x8082) { - memcpy(read_resp->block_data[i], felica_resp->idm, 8); - } - if (!virtual_aic.active) { - pn532_felica_read_wo_encrypt(svc_code, block_id, read_resp->block_data[i]); - } - } - - read_resp->rw_status[0] = 0x00; - read_resp->rw_status[1] = 0x00; - read_resp->block_num = block_num; - - return sizeof(*read_resp) + block_num * 16; -} - -static int cmd_felica_write() -{ - printf("FELICA WRITE\n"); - uint8_t *req_data = request.felica.data; - - if (req_data[8] != 1) { - printf("Felica Encap Write Error: service_num != 1\n"); - return -1; - } - - uint16_t svc_code = req_data[9] | (req_data[10] << 8); - - uint8_t *block = req_data + 11; - uint8_t block_num = block[0]; - - felica_resp_t *felica_resp = (felica_resp_t *) response.payload; - uint8_t *rw_status = felica_resp->data; - - printf("svc code: %04x\n", svc_code); - printf("blocks:"); - for (int i = 0; i < block_num; i++) { - uint16_t block_id = (block[i * 2 + 1] << 8) | block[i * 2 + 2]; - printf(" %04x", block_id); - } - printf("\n"); - - uint8_t *block_data = block + 1 + block_num * 2; - - rw_status[0] = 0x00; - rw_status[1] = 0x00; - - for (int i = 0; i < block_num; i++) { - printf("writing %02x %02x\n", block_data[i * 16], block_data[i * 16 + 15]); - // uint16_t block_id = (block[i * 2 + 1] << 8) | block[i * 2 + 2]; - // int result = pn532_felica_write_wo_encrypt(svc_code, block_id, block_data + i * 16); - int result = 0; - if (result < 0) { - rw_status[0] = 0x01; - rw_status[1] = 0x01; - } - } - - return 2; -} - -#if 0 - - case CMD_FELICA_OP_NDA_A4: - printf("\tNDA_A4\n"); - build_response(11); - resp->payload[0] = 0x00; - break; - default: - printf("\tUnknown through: %02x\n", code); - build_response(0); - response.status = STATUS_OK; -#endif - -static void cmd_felica() -{ - card_id_t card; - if (!pn532_poll_felica(card.idm, card.pmm, card.syscode, true)) { - send_simple_response(STATUS_FELICA_ERROR); - } - - uint8_t felica_code = request.felica.code; - printf("Felica (%02x):", felica_code); - for (int i = 0; i < request.payload_len; i++) { - printf(" %02x", request.payload[i]); - } - printf("\n"); - - int datalen = -1; - switch (felica_code) { - case CMD_FELICA_OP_GET_SYSTEM_CODE: - datalen = cmd_felica_get_syscode(&card); - break; - case CMD_FELICA_OP_POLL: - datalen = cmd_felica_poll(&card); - break; - case CMD_FELICA_OP_READ: - datalen = cmd_felica_read(); - break; - case CMD_FELICA_OP_WRITE: - datalen = cmd_felica_write(); - break; - default: - printf("Unknown code %d\n", felica_code); - break; - } - - if (datalen < 0) { - send_simple_response(STATUS_FELICA_ERROR); - return; - } - - felica_resp_t *felica_resp = (felica_resp_t *) response.payload; - build_response(sizeof(*felica_resp) + datalen); - felica_resp->len = response.payload_len; - felica_resp->code = felica_code + 1; - if (virtual_aic.active) { - memcpy(felica_resp->idm, virtual_aic.idm, 8); - } else { - memcpy(felica_resp->idm, card.idm, 8); - } - - send_response(); - - printf("Felica Response:"); - for (int i = 0; i < felica_resp->len - 10; i++) { - printf(" %02x", felica_resp->data[i]); - } - printf("\n"); -} - -static uint32_t led_color; - -static void cmd_led_rgb() -{ - led_color = request.payload[0] << 16 | request.payload[1] << 8 | request.payload[2]; - build_response(0); - send_response(); -} - -static void aime_handle_frame() -{ - i2c_select(I2C_PORT, 1 << 5); // PN532 on IR1 (I2C mux chn 5) - - switch (request.cmd) { - case CMD_TO_NORMAL_MODE: - cmd_to_normal_mode(); - break; - case CMD_GET_FW_VERSION: - printf("CMD_GET_FW_VERSION\n"); - cmd_fake_version(fw_version); - break; - case CMD_GET_HW_VERSION: - printf("CMD_GET_HW_VERSION\n"); - cmd_fake_version(hw_version); - break; - case CMD_MIFARE_KEY_SET_A: - printf("CMD_MIFARE_KEY_SET_A\n"); - cmd_key_set(mifare_keys[0]); - break; - case CMD_MIFARE_KEY_SET_B: - printf("CMD_MIFARE_KEY_SET_B\n"); - cmd_key_set(mifare_keys[1]); - break; - - case CMD_START_POLLING: - cmd_set_polling(true); - break; - case CMD_STOP_POLLING: - cmd_set_polling(false); - break; - case CMD_CARD_DETECT: - cmd_detect_card(); - break; - case CMD_FELICA_OP: - cmd_felica(); - break; - - case CMD_CARD_SELECT: - cmd_card_select(); - break; - - case CMD_MIFARE_AUTHORIZE_A: - cmd_mifare_auth(0); - break; - - case CMD_MIFARE_AUTHORIZE_B: - cmd_mifare_auth(1); - break; - - case CMD_MIFARE_READ: - cmd_mifare_read(); - break; - - case CMD_CARD_HALT: - cmd_mifare_halt(); - break; - - case CMD_EXT_BOARD_INFO: - cmd_fake_version(led_info); - break; - case CMD_EXT_BOARD_LED_RGB: - cmd_led_rgb(); - break; - - case CMD_SEND_HEX_DATA: - case CMD_EXT_TO_NORMAL_MODE: - send_simple_response(STATUS_OK); - break; - - default: - printf("Unknown command: %02x [", request.cmd); - for (int i = 0; i < request.len; i++) { - printf(" %02x", request.raw[i]); - } - printf("]\n"); - send_simple_response(STATUS_OK); - break; - } -} - -static bool aime_feed(int c) -{ - if (c == 0xe0) { - req_ctx.active = true; - req_ctx.len = 0; - req_ctx.check_sum = 0; - req_ctx.escaping = false; - req_ctx.time = time_us_64(); - return true; - } - - if (!req_ctx.active) { - return false; - } - - if (c == 0xd0) { - req_ctx.escaping = true; - return true; - } - - if (req_ctx.escaping) { - c++; - req_ctx.escaping = false; - } - - if (req_ctx.len != 0 && req_ctx.len == request.len) { - if (req_ctx.check_sum == c) { - aime_handle_frame(); - req_ctx.active = false; - } - return true; - } - - request.raw[req_ctx.len] = c; - req_ctx.len++; - req_ctx.check_sum += c; - - return true; -} - -void aime_update() -{ - if (req_ctx.active && time_us_64() - req_ctx.time > FRAME_TIMEOUT) { - req_ctx.active = false; - } - - if (tud_cdc_n_available(aime_interface)) { - uint8_t buf[32]; - uint32_t count = tud_cdc_n_read(aime_interface, buf, sizeof(buf)); - for (int i = 0; i < count; i++) { - aime_feed(buf[i]); - } - } -} - -uint32_t aime_led_color() -{ - return led_color; -} diff --git a/firmware/src/aime.h b/firmware/src/aime.h deleted file mode 100644 index dd0b841..0000000 --- a/firmware/src/aime.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * AIME Reader - * WHowe - */ - -#ifndef AIME_H -#define AIME_H - -void aime_init(int interface); -void aime_update(); -uint32_t aime_led_color(); - -// mode 0 or 1 -void aime_set_baudrate(int mode); -void aime_set_virtual_aic(bool enable); - -#endif diff --git a/firmware/src/commands.c b/firmware/src/commands.c index 617161d..ad14727 100644 --- a/firmware/src/commands.c +++ b/firmware/src/commands.c @@ -7,6 +7,8 @@ #include "pico/stdio.h" #include "pico/stdlib.h" +#include "aime.h" + #include "config.h" #include "air.h" #include "slider.h" @@ -14,8 +16,7 @@ #include "cli.h" #include "i2c_hub.h" - -#include "pn532.h" +#include "nfc.h" #define SENSE_LIMIT_MAX 9 #define SENSE_LIMIT_MIN -9 @@ -73,6 +74,12 @@ static void disp_hid() chu_cfg->hid.nkro ? "on" : "off" ); } +static void disp_aime() +{ + printf("[AIME]\n"); + printf(" Virtual AIC: %s\n", chu_cfg->virtual_aic ? "ON" : "OFF"); +} + void handle_display(int argc, char *argv[]) { const char *usage = "Usage: display [colors|style|tof|sense|hid]\n"; @@ -87,11 +94,12 @@ void handle_display(int argc, char *argv[]) disp_tof(); disp_sense(); disp_hid(); + disp_aime(); return; } - const char *choices[] = {"colors", "style", "tof", "sense", "hid"}; - switch (cli_match_prefix(choices, 5, argv[0])) { + const char *choices[] = {"colors", "style", "tof", "sense", "hid", "aime"}; + switch (cli_match_prefix(choices, 6, argv[0])) { case 0: disp_colors(); break; @@ -107,6 +115,9 @@ void handle_display(int argc, char *argv[]) case 4: disp_hid(); break; + case 5: + disp_aime(); + break; default: printf(usage); break; @@ -402,30 +413,36 @@ static void handle_factory_reset() static void handle_nfc() { i2c_select(I2C_PORT, 1 << 5); // PN532 on IR1 (I2C mux chn 5) - - bool ret = pn532_config_sam(); - printf("Sam: %d\n", ret); - - uint8_t buf[32]; - - int len = sizeof(buf); - ret = pn532_poll_mifare(buf, &len); - printf("Mifare: %d -", len); - - if (ret) { - for (int i = 0; i < len; i++) { - printf(" %02x", buf[i]); - } + printf("NFC module: %s\n", nfc_module_name()); + nfc_rf_field(true); + nfc_card_t card = nfc_detect_card(); + nfc_rf_field(false); + printf("Card: %s", nfc_card_name(card.card_type)); + for (int i = 0; i < card.len; i++) { + printf(" %02x", card.uid[i]); } printf("\n"); +} - printf("Felica: "); - if (pn532_poll_felica(buf, buf + 8, buf + 16, false)) { - for (int i = 0; i < 18; i++) { - printf(" %02x%s", buf[i], (i % 8 == 7) ? "," : ""); - } +static void handle_virtual(int argc, char *argv[]) +{ + const char *usage = "Usage: virtual \n"; + if (argc != 1) { + printf("%s", usage); + return; } - printf("\n"); + + const char *commands[] = { "on", "off" }; + int match = cli_match_prefix(commands, 2, argv[0]); + if (match < 0) { + printf("%s", usage); + return; + } + + chu_cfg->virtual_aic = (match == 0); + + aime_virtual_aic(chu_cfg->virtual_aic); + config_changed(); } void commands_init() @@ -442,4 +459,5 @@ void commands_init() cli_register("save", handle_save, "Save config to flash."); cli_register("factory", handle_factory_reset, "Reset everything to default."); cli_register("nfc", handle_nfc, "NFC debug."); + cli_register("virtual", handle_virtual, "Virtual AIC card."); } diff --git a/firmware/src/config.c b/firmware/src/config.c index da5be3d..cd5bc8b 100644 --- a/firmware/src/config.c +++ b/firmware/src/config.c @@ -38,6 +38,7 @@ static chu_cfg_t default_cfg = { .joy = 1, .nkro = 0, }, + .virtual_aic = false, }; chu_runtime_t *chu_runtime; diff --git a/firmware/src/config.h b/firmware/src/config.h index 15906eb..5194590 100644 --- a/firmware/src/config.h +++ b/firmware/src/config.h @@ -38,6 +38,7 @@ typedef struct __attribute__((packed)) { uint8_t joy : 4; uint8_t nkro : 4; } hid; + bool virtual_aic; } chu_cfg_t; typedef struct { diff --git a/firmware/src/main.c b/firmware/src/main.c index b4a15a6..fa30fd8 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -21,14 +21,16 @@ #include "tusb.h" #include "usb_descriptors.h" -#include "board_defs.h" +#include "aime.h" +#include "nfc.h" +#include "board_defs.h" #include "save.h" #include "config.h" #include "cli.h" #include "commands.h" -#include "aime.h" +#include "i2c_hub.h" #include "slider.h" #include "air.h" #include "rgb.h" @@ -144,6 +146,26 @@ static void run_lights() } } +const int aime_intf = 1; +static void cdc_aime_putc(uint8_t byte) +{ + tud_cdc_n_write(aime_intf, &byte, 1); + tud_cdc_n_write_flush(aime_intf); +} + +static void aime_run() +{ + if (tud_cdc_n_available(aime_intf)) { + uint8_t buf[32]; + uint32_t count = tud_cdc_n_read(aime_intf, buf, sizeof(buf)); + + i2c_select(I2C_PORT, 1 << 5); // PN532 on IR1 (I2C mux chn 5) + for (int i = 0; i < count; i++) { + aime_feed(buf[i]); + } + } +} + static mutex_t core1_io_lock; static void core1_loop() { @@ -164,7 +186,7 @@ static void core0_loop() tud_task(); cli_run(); - aime_update(); + aime_run(); save_loop(); cli_fps_count(0); @@ -194,7 +216,11 @@ void init() air_init(); rgb_init(); - aime_init(1); + nfc_attach_i2c(I2C_PORT); + i2c_select(I2C_PORT, 1 << 5); // PN532 on IR1 (I2C mux chn 5) + nfc_init(); + aime_init(cdc_aime_putc); + aime_virtual_aic(chu_cfg->virtual_aic); cli_init("chu_pico>", "\n << Chu Pico Controller >>\n" " https://github.com/whowechina\n\n"); diff --git a/firmware/src/pn532.c b/firmware/src/pn532.c deleted file mode 100644 index 9ada649..0000000 --- a/firmware/src/pn532.c +++ /dev/null @@ -1,497 +0,0 @@ -/* - * PN532 NFC Reader - * WHowe - * - */ - -#include -#include -#include - -#include "hardware/i2c.h" -#include "hardware/gpio.h" - -//#define DEBUG - -#include "pn532.h" -#include "board_defs.h" - -#define IO_TIMEOUT_US 1000 -#define PN532_I2C_ADDRESS 0x24 - -#define PN532_PREAMBLE 0 -#define PN532_STARTCODE1 0 -#define PN532_STARTCODE2 0xff -#define PN532_POSTAMBLE 0 - -#define PN532_HOSTTOPN532 0xd4 -#define PN532_PN532TOHOST 0xd5 - -void pn532_init() -{ - i2c_init(I2C_PORT, I2C_FREQ); - gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); - gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); - gpio_pull_up(I2C_SDA); - gpio_pull_up(I2C_SCL); -} - -static int pn532_write(const uint8_t *data, uint8_t len) -{ - return i2c_write_blocking_until(I2C_PORT, PN532_I2C_ADDRESS, data, len, false, - time_us_64() + IO_TIMEOUT_US * len); -} - -static int pn532_read(uint8_t *data, uint8_t len) -{ - return i2c_read_blocking_until(I2C_PORT, PN532_I2C_ADDRESS, data, len, false, - time_us_64() + IO_TIMEOUT_US * len); -} - -static bool pn532_wait_ready() -{ - uint8_t status = 0; - - for (int retry = 0; retry < 20; retry++) { - if (pn532_read(&status, 1) == 1 && status == 0x01) { - return true; - } - sleep_us(1000); - } - - return false; -} - -static int read_frame(uint8_t *frame, uint8_t len) -{ - uint8_t buf[len + 1]; - int ret = pn532_read(buf, len + 1); - -#ifdef DEBUG - printf("I2C data read: %d -", ret); - for (int i = 0; i < len + 1; i++) { - printf(" %02x", buf[i]); - } - printf("\n"); -#endif - - if (ret == len + 1) { - memcpy(frame, buf + 1, len); - return len; - } - return ret; -} - -static int write_frame(const uint8_t *frame, uint8_t len) -{ - #ifdef DEBUG - printf("I2C frame write: %d -", len); - for (int i = 0; i < len; i++) { - printf(" %02x", frame[i]); - } - printf("\n"); - #endif - - return pn532_write(frame, len); -} - -static bool read_ack() -{ - uint8_t resp[6]; - - if (!pn532_wait_ready()) { - return false; - } - - read_frame(resp, 6); - - const uint8_t expect_ack[] = {0, 0, 0xff, 0, 0xff, 0}; - if (memcmp(resp, expect_ack, 6) != 0) { - return false; - } - - return true; -} - -int pn532_write_data(const uint8_t *data, uint8_t len) -{ - uint8_t frame[5 + len]; - frame[0] = PN532_PREAMBLE; - frame[1] = PN532_STARTCODE1; - frame[2] = PN532_STARTCODE2; - uint8_t checksum = 0xff; - - frame[3] = len; - frame[4] = (~len + 1); - - for (int i = 0; i < len; i++) { - frame[5 + i] = data[i]; - checksum += data[i]; - } - - frame[5 + len] = ~checksum; - frame[6 + len] = PN532_POSTAMBLE; - - write_frame(frame, 7 + len); - - return read_ack(); -} - -int pn532_read_data(uint8_t *data, uint8_t len) -{ - uint8_t resp[len + 7]; - - read_frame(resp, len + 7); - - if (resp[0] != PN532_PREAMBLE || - resp[1] != PN532_STARTCODE1 || - resp[2] != PN532_STARTCODE2) { - return -1; - } - - uint8_t length = resp[3]; - uint8_t length_check = length + resp[4]; - - if (length > len || - length_check != 0 || - resp[length + 6] != PN532_POSTAMBLE) { - return -1; - } - - uint8_t checksum = 0; - for (int i = 0; i <= length; i++) { - data[i] = resp[5 + i]; - checksum += resp[5 + i]; - } - - if (checksum != 0) { - return -1; - } - - return length; -} - -int pn532_write_command(uint8_t cmd, const uint8_t *param, uint8_t len) -{ - uint8_t data[len + 2]; - data[0] = PN532_HOSTTOPN532; - data[1] = cmd; - - memcpy(data + 2, param, len); - - return pn532_write_data(data, len + 2); -} - -static void write_nack() -{ - const uint8_t nack[] = {0, 0, 0xff, 0xff, 0, 0}; - pn532_write(nack, 6); -} - -int pn532_peak_response_len() -{ - uint8_t buf[6]; - if (!pn532_wait_ready()) { - return -1; - } - pn532_read(buf, 6); - if (buf[0] != 0x01 || - buf[1] != PN532_PREAMBLE || - buf[2] != PN532_STARTCODE1 || - buf[3] != PN532_STARTCODE2) { - return -1; - } - - write_nack(); - return buf[4]; -} - -int pn532_read_response(uint8_t cmd, uint8_t *resp, uint8_t len) -{ - int real_len = pn532_peak_response_len(); - if (real_len < 0) { - return -1; - } - - if (!pn532_wait_ready()) { - return -1; - } - - if (real_len < 2) { - return -1; - } - - uint8_t data[real_len]; - int ret = pn532_read_data(data, real_len); - if (ret != real_len || - data[0] != PN532_PN532TOHOST || - data[1] != cmd + 1) { - return -1; - } - - int data_len = real_len - 2; - if (data_len > len) { - return -1; - } - - if (data_len > 0) { - memcpy(resp, data + 2, data_len); - } - - return data_len; -} - -uint32_t pn532_firmware_ver() -{ - int ret = pn532_write_command(0x02, NULL, 0); - if (ret < 0) { - return 0; - } - - uint8_t ver[4]; - int result = pn532_read_response(0x4a, ver, sizeof(ver)); - if (result < 4) { - return 0; - } - - uint32_t version = 0; - for (int i = 0; i < 4; i++) { - version <<= 8; - version |= ver[i]; - } - return version; -} - -bool pn532_config_rf() -{ - uint8_t param[] = {0x05, 0xff, 0x01, 0x50}; - pn532_write_command(0x32, param, sizeof(param)); - - return pn532_read_response(0x32, param, sizeof(param)) == 0; -} - -bool pn532_config_sam() -{ - uint8_t param[] = {0x01, 0x14, 0x01}; - pn532_write_command(0x14, param, sizeof(param)); - - return pn532_read_response(0x14, NULL, 0) == 0; -} - - -bool pn532_set_rf_field(uint8_t auto_rf, uint8_t on_off) -{ - uint8_t param[] = { 1, auto_rf | on_off }; - pn532_write_command(0x32, param, 2); - - return pn532_read_response(0x32, NULL, 0) >= 0; -} - -static uint8_t readbuf[255]; - -bool pn532_poll_mifare(uint8_t *uid, int *len) -{ - uint8_t param[] = {0x01, 0x00}; - int ret = pn532_write_command(0x4a, param, sizeof(param)); - if (ret < 0) { - return false; - } - - int result = pn532_read_response(0x4a, readbuf, sizeof(readbuf)); - if (result < 1 || readbuf[0] != 1) { - return false; - } - - if (result != readbuf[5] + 6) { - return false; - } - - if (*len < readbuf[5]) { - return false; - } - - memcpy(uid, readbuf + 6, readbuf[5]); - *len = readbuf[5]; - - return true; -} - -bool pn532_poll_14443b(uint8_t *uid, int *len) -{ - uint8_t param[] = {0x01, 0x03, 0x00}; - int ret = pn532_write_command(0x4a, param, sizeof(param)); - if (ret < 0) { - return false; - } - - int result = pn532_read_response(0x4a, readbuf, sizeof(readbuf)); - if (result < 1 || readbuf[0] != 1) { - printf("result: %d\n", result); - return false; - } - - if (result != readbuf[5] + 6) { - printf("result: %d %d\n", result, readbuf[5]); - return false; - } - - if (*len < readbuf[5]) { - printf("result: %d %d\n", result, readbuf[5]); - return false; - } - - memcpy(uid, readbuf + 6, readbuf[5]); - *len = readbuf[5]; - - return true; -} - -static struct __attribute__((packed)) { - uint8_t idm[8]; - uint8_t pmm[8]; - uint8_t syscode[2]; - uint8_t inlist_tag; -} felica_poll_cache; - -bool pn532_poll_felica(uint8_t uid[8], uint8_t pmm[8], uint8_t syscode[2], bool from_cache) -{ - if (from_cache) { - memcpy(uid, felica_poll_cache.idm, 8); - memcpy(pmm, felica_poll_cache.pmm, 8); - memcpy(syscode, felica_poll_cache.syscode, 2); - return true; - } - - uint8_t param[] = { 1, 1, 0, 0xff, 0xff, 1, 0}; - int ret = pn532_write_command(0x4a, param, sizeof(param)); - if (ret < 0) { - return false; - } - - int result = pn532_read_response(0x4a, readbuf, sizeof(readbuf)); - if (result != 22 || readbuf[0] != 1 || readbuf[2] != 20) { - return false; - } - - memcpy(&felica_poll_cache, readbuf + 4, 18); - felica_poll_cache.inlist_tag = readbuf[1]; - - memcpy(uid, readbuf + 4, 8); - memcpy(pmm, readbuf + 12, 8); - memcpy(syscode, readbuf + 20, 2); - - return true; -} - -bool pn532_mifare_auth(const uint8_t uid[4], uint8_t block_id, uint8_t key_id, const uint8_t *key) -{ - uint8_t param[] = { 1, key_id ? 1 : 0, block_id, - key[0], key[1], key[2], key[3], key[4], key[5], - uid[0], uid[1], uid[2], uid[3] }; - int ret = pn532_write_command(0x40, param, sizeof(param)); - if (ret < 0) { - printf("Failed mifare auth command\n"); - return false; - } - int result = pn532_read_response(0x40, readbuf, sizeof(readbuf)); - if (readbuf[0] != 0) { - printf("PN532 Mifare AUTH failed %d %02x\n", result, readbuf[0]); - return false; - } - - return true; -} - -bool pn532_mifare_read(uint8_t block_id, uint8_t block_data[16]) -{ - uint8_t param[] = { 1, 0x30, block_id }; - - int ret = pn532_write_command(0x40, param, sizeof(param)); - if (ret < 0) { - printf("Failed mifare read command\n"); - return false; - } - - int result = pn532_read_response(0x40, readbuf, sizeof(readbuf)); - - if (readbuf[0] != 0 || result != 17) { - printf("PN532 Mifare READ failed %d %02x\n", result, readbuf[0]); - return false; - } - - memmove(block_data, readbuf + 1, 16); - - return true; -} - -int pn532_felica_command(uint8_t cmd, const uint8_t *param, uint8_t param_len, uint8_t *outbuf) -{ - int cmd_len = param_len + 11; - uint8_t cmd_buf[cmd_len + 1]; - - cmd_buf[0] = felica_poll_cache.inlist_tag; - cmd_buf[1] = cmd_len; - cmd_buf[2] = cmd; - memcpy(cmd_buf + 3, felica_poll_cache.idm, 8); - memcpy(cmd_buf + 11, param, param_len); - - int ret = pn532_write_command(0x40, cmd_buf, sizeof(cmd_buf)); - if (ret < 0) { - printf("Failed send felica command\n"); - return -1; - } - - int result = pn532_read_response(0x40, readbuf, sizeof(readbuf)); - - int outlen = readbuf[1] - 1; - if ((readbuf[0] & 0x3f) != 0 || result - 2 != outlen) { - return -1; - } - - memmove(outbuf, readbuf + 2, outlen); - - return outlen; -} - - -bool pn532_felica_read_wo_encrypt(uint16_t svc_code, uint16_t block_id, uint8_t block_data[16]) -{ - uint8_t param[] = { 1, svc_code & 0xff, svc_code >> 8, - 1, block_id >> 8, block_id & 0xff }; - - int result = pn532_felica_command(0x06, param, sizeof(param), readbuf); - - if (result != 12 + 16 || readbuf[9] != 0 || readbuf[10] != 0) { - printf("PN532 Felica READ read failed %d %02x %02x\n", - result, readbuf[9], readbuf[10]); - for (int i = 0; i < result; i++) { - printf(" %02x", readbuf[i]); - } - printf("\n"); - return false; - } - - const uint8_t *result_data = readbuf + 12; - memcpy(block_data, result_data, 16); - - return true; -} - -bool pn532_felica_write_wo_encrypt(uint16_t svc_code, uint16_t block_id, const uint8_t block_data[16]) -{ - uint8_t param[22] = { 1, svc_code & 0xff, svc_code >> 8, - 1, block_id >> 8, block_id & 0xff }; - memcpy(param + 6, block_data, 16); - int result = pn532_felica_command(0x08, param, sizeof(param), readbuf); - - if (result < 0) { - printf("PN532 Felica WRITE failed %d\n", result); - return false; - } - - for (int i = 0; i < result; i++) { - printf(" %02x", readbuf[i]); - } - printf("\n"); - return false; -} diff --git a/firmware/src/pn532.h b/firmware/src/pn532.h deleted file mode 100644 index a863f9b..0000000 --- a/firmware/src/pn532.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * PN532 NFC Reader - * WHowe - * - */ - -#ifndef PN532_H -#define PN532_H - -void pn532_init(); -int pn532_write_command(uint8_t cmd, const uint8_t *param, uint8_t len); -int pn532_read_response(uint8_t cmd, uint8_t *resp, uint8_t len); - -uint32_t pn532_firmware_ver(); - -bool pn532_config_sam(); -bool pn532_config_rf(); - -bool pn532_set_rf_field(uint8_t auto_rf, uint8_t on_off); - -bool pn532_poll_mifare(uint8_t *uid, int *len); -bool pn532_poll_14443b(uint8_t *uid, int *len); -bool pn532_poll_felica(uint8_t uid[8], uint8_t pmm[8], uint8_t syscode[2], bool from_cache); - -bool pn532_mifare_auth(const uint8_t uid[4], uint8_t block_id, uint8_t key_id, const uint8_t *key); -bool pn532_mifare_read(uint8_t block_id, uint8_t block_data[16]); - -bool pn532_felica_read_wo_encrypt(uint16_t svc_code, uint16_t block_id, uint8_t block_data[16]); -bool pn532_felica_write_wo_encrypt(uint16_t svc_code, uint16_t block_id, const uint8_t block_data[16]); - -#endif