Add Gitea OTA firmware update, dual-slot partition layout, and Gitea Actions release workflow

This commit is contained in:
2026-09-12 10:49:39 +02:00
parent 8ac465229c
commit 1d954287a2
82 changed files with 7611 additions and 395 deletions

View File

@@ -0,0 +1,75 @@
---
Language: Cpp
BasedOnStyle: WebKit
AlignConsecutiveBitFields: true
AlignConsecutiveMacros: true
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BreakBeforeBraces: Custom
BitFieldColonSpacing: Before
PenaltyBreakBeforeFirstCallParameter: 0
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: false
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
ColumnLimit: 120
CompactNamespaces: true
Cpp11BracedListStyle: false
FixNamespaceComments: true
IndentCaseLabels: true
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
PointerAlignment: Right
ReflowComments: true
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeInheritanceColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Never
InsertNewlineAtEOF: true
InsertTrailingCommas: Wrapped
LineEnding: LF

View File

@@ -0,0 +1 @@
3302c1e7e19ee75c37e3e7284e86d0f624b56b7c5c55929c4ed1a1bdd28b62c5

6
components/rc522/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/dist
**/build/
sdkconfig.old
sdkconfig
.vscode
dependencies.lock

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,47 @@
SET (RC522_REQUIRES
esp_event
esp_driver_gpio
esp_driver_i2c
esp_timer
)
if(${IDF_TARGET} STREQUAL "linux") # tests
list(APPEND RC522_REQUIRES driver)
else()
list(APPEND RC522_REQUIRES esp_driver_spi)
endif()
idf_component_register(
INCLUDE_DIRS
include
internal
SRCS
src/rc522.c
src/rc522_helpers.c
src/rc522_pcd.c
src/rc522_picc.c
src/picc/rc522_mifare.c
src/picc/rc522_nxp.c
src/rc522_driver.c
src/driver/rc522_spi.c
src/driver/rc522_i2c.c
REQUIRES
${RC522_REQUIRES}
)
target_compile_options(${COMPONENT_LIB} PRIVATE
-Werror # Warning as error
#-pedantic
-Wextra
-Wall
-Wshadow
-Wcast-qual
-Wswitch-default
#-Wswitch-enum
#-Wconversion
-Wunreachable-code
-Wuninitialized
-Winit-self
-Wpointer-arith
-Werror-implicit-function-declaration
)

14
components/rc522/Kconfig Normal file
View File

@@ -0,0 +1,14 @@
menu "RC522"
config RC522_PREVENT_SECTOR_TRAILER_WRITE
bool "Prevent writing to the Sector Trailer block"
default y
help
The Sector Trailer block stores authentication keys
and access bits. Enabling this option prevents writing
to the Trailer block. This is enabled by default to protect
inexperienced users from accidentally overwriting keys or
writing incorrect access bits, which could render the sector
unusable.
endmenu

202
components/rc522/LICENSE Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2024] [Alija Bobija]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

109
components/rc522/README.md Normal file
View File

@@ -0,0 +1,109 @@
# esp-idf-rc522
![CI](https://img.shields.io/github/actions/workflow/status/abobija/esp-idf-rc522/validate.yaml?branch=main&style=for-the-badge&logo=githubactions&logoColor=white) [![Component Registry](https://img.shields.io/github/v/release/abobija/esp-idf-rc522?sort=date&display_name=release&style=for-the-badge&logo=espressif&logoColor=white&label=Latest%20version)](https://components.espressif.com/components/abobija/rc522)
This repository contains [ESP-IDF](https://github.com/espressif/esp-idf) library (component) for communication with RFID cards using [MFRC522](https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf) reader.
![read-write-example](docs/img/read-write-example.png)
Library takes care of polling the cards and managing the card lifecycle. It also fires events when a card is detected, removed, or when the card changes to any state described in ISO-14443. Additionally, it provides an API for reading and writing to card memory blocks.
## Installation
To install latest version of this component to your project, run:
```bash
idf.py add-dependency "abobija/rc522"
```
Read more about esp-idf component manager in [official documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/tools/idf-component-manager.html).
## Support
- Full support: `MIFARE 1K`, `MIFARE 4K`, `MIFARE Mini`
- Partial support: Ultralight & NTAG families
- Supported commands: `GET_VERSION`, `READ`, `FAST_READ`, `WRITE`, `READ_CNT`,
`READ_SIG`, `PWD_AUTH`
- Supported features: PICC identification, read/write, page counts, password
authentication
- Unsupported commands: `INCR_CNT`, `WRITE_SIG`, `LOCK_SIG`, `AUTHENTICATE`,
`COMPAT_WRITE`, `VCSL`
- Unsupported features: signature validation, key authentication, high-level
configuration access (raw read + bit-manipulation only)
- No support: ISO-14443-4 compatible PICCs, other proprietary PICC protocols
- Communication protocols: `SPI` and `I2C`
See [examples](examples/) for example projects.
## Create project from example
> [!TIP]
> To find more interesting examples (like [`memory_dump`](examples/memory_dump)), go to [examples](examples) folder.
To run [`basic`](examples/basic) example, create it as follows:
```bash
idf.py create-project-from-example "abobija/rc522:basic"
```
Then build and flash it as usual:
```bash
cd basic
idf.py build flash monitor
```
> [!NOTE]
> [`basic`](examples/basic) example uses SPI communication. Find defined GPIO configuration in [basic.c](examples/basic/main/basic.c) file.
## Pin Layout
Pin layout is configurable by the user. To configure the GPIOs, check the `#define` statements in the [basic example](examples/basic/main/basic.c). If you are not using the RST pin, you can connect it to the 3.3V.
## Unit testing
To run unit tests, go to [`test`](test) directory and set target to `linux`:
```bash
cd test
idf.py --preview set-target linux
```
Then build the project and run tests:
```bash
idf.py build && ./build/test.elf
```
## Security
- Mifare Classic cards use the Crypto-1 cipher for authentication and encryption, which has been [broken](https://eprint.iacr.org/2008/166) for a long time. As a result, it is not advisable to use Mifare Classic cards for security-sensitive applications. Instead, consider using Mifare Plus or Desfire cards, which utilize AES encryption.
- Even though block zero, which contains the UID, is typically considered as read-only, there are certain cards known as "magic" or "Chinese magic" cards that can be used to modify the UID. As a result, relying on the UID of a card as a secure identifier is not recommended.
## Terms
| Term | Description |
| ---- | ----------- |
| PCD | Proximity Coupling Device (the card reader). In our case this is MFRC522 module |
| PICC | Proximity Integrated Circuit Card (e.g: rfid card, tag, ...) |
## References
- [ISO/IEC 14443](https://en.wikipedia.org/wiki/ISO/IEC_14443) Identification cards - Contactless integrated circuit cards
- [ISO/IEC 14443-2](http://www.emutag.com/iso/14443-2.pdf) Radio frequency power and signal interface
- [ISO/IEC 14443-3](http://www.emutag.com/iso/14443-3.pdf) Initialization and anticollision
- [ISO/IEC 14443-4](http://www.emutag.com/iso/14443-4.pdf) Transmission protocol
- [MFRC522](https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf) MFRC522 - Standard performance MIFARE and NTAG frontend
- [AN10833](https://www.nxp.com/docs/en/application-note/AN10833.pdf) MIFARE type identification procedure
- [AN10834](https://www.nxp.com/docs/en/application-note/AN10834.pdf) MIFARE ISO/IEC 14443 PICC selection
- [MF1S50YYX_V1](https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf) MIFARE Classic EV1 1K
- [MF1S70YYX_V1](https://www.nxp.com/docs/en/data-sheet/MF1S70YYX_V1.pdf) MIFARE Classic EV1 4K
## Author
GitHub: [abobija](https://github.com/abobija)<br />
Homepage: [abobija.com](https://abobija.com)
## License
This component is provided under Apache 2.0 license, see [LICENSE](LICENSE) file for details.

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(basic)

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "basic.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,55 @@
#include <esp_log.h>
#include "rc522.h"
#include "driver/rc522_spi.h"
#include "rc522_picc.h"
static const char *TAG = "rc522-basic-example";
#define RC522_SPI_BUS_GPIO_MISO (25)
#define RC522_SPI_BUS_GPIO_MOSI (23)
#define RC522_SPI_BUS_GPIO_SCLK (19)
#define RC522_SPI_SCANNER_GPIO_SDA (22)
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
static rc522_spi_config_t driver_config = {
.host_id = SPI3_HOST,
.bus_config = &(spi_bus_config_t){
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
},
.dev_config = {
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
},
.rst_io_num = RC522_SCANNER_GPIO_RST,
};
static rc522_driver_handle_t driver;
static rc522_handle_t scanner;
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
rc522_picc_t *picc = event->picc;
if (picc->state == RC522_PICC_STATE_ACTIVE) {
rc522_picc_print(picc);
}
else if (picc->state == RC522_PICC_STATE_IDLE && event->old_state >= RC522_PICC_STATE_ACTIVE) {
ESP_LOGI(TAG, "Card has been removed");
}
}
void app_main()
{
rc522_spi_create(&driver_config, &driver);
rc522_driver_install(driver);
rc522_config_t scanner_config = {
.driver = driver,
};
rc522_create(&scanner_config, &scanner);
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
rc522_start(scanner);
}

View File

@@ -0,0 +1,3 @@
dependencies:
abobija/rc522:
version: '*'

View File

@@ -0,0 +1 @@
CONFIG_LOG_COLORS=y

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(basic_i2c)

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "basic_i2c.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,56 @@
#include <esp_log.h>
#include "rc522.h"
#include "driver/rc522_i2c.h"
#include "rc522_picc.h"
static const char *TAG = "rc522-basic-i2c-example";
#define RC522_I2C_ADDRESS (0x28)
#define RC522_I2C_GPIO_SDA (18)
#define RC522_I2C_GPIO_SCL (21)
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
static rc522_i2c_config_t driver_config = {
.bus_config = &(i2c_master_bus_config_t){
.i2c_port = I2C_NUM_0,
.sda_io_num = RC522_I2C_GPIO_SDA,
.scl_io_num = RC522_I2C_GPIO_SCL,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
.clk_source = I2C_CLK_SRC_DEFAULT,
},
.dev_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = RC522_I2C_ADDRESS,
.scl_speed_hz = 100000,
},
.rst_io_num = RC522_SCANNER_GPIO_RST,
};
static rc522_driver_handle_t driver;
static rc522_handle_t scanner;
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
if (event->picc->state != RC522_PICC_STATE_ACTIVE) {
return;
}
rc522_picc_print(event->picc);
}
void app_main()
{
rc522_i2c_create(&driver_config, &driver);
rc522_driver_install(driver);
rc522_config_t scanner_config = {
.driver = driver,
};
rc522_create(&scanner_config, &scanner);
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
rc522_start(scanner);
}

View File

@@ -0,0 +1,3 @@
dependencies:
abobija/rc522:
version: '*'

View File

@@ -0,0 +1 @@
CONFIG_LOG_COLORS=y

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(memory_dump)

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "memory_dump.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,3 @@
dependencies:
abobija/rc522:
version: '*'

View File

@@ -0,0 +1,187 @@
#include <esp_log.h>
#include <esp_check.h>
#include <string.h>
#include "rc522.h"
#include "driver/rc522_spi.h"
#include "picc/rc522_mifare.h"
static const char *TAG = "rc522-memory-dump-example";
#define RC522_SPI_BUS_GPIO_MISO (25)
#define RC522_SPI_BUS_GPIO_MOSI (23)
#define RC522_SPI_BUS_GPIO_SCLK (19)
#define RC522_SPI_SCANNER_GPIO_SDA (22)
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
static rc522_spi_config_t driver_config = {
.host_id = SPI3_HOST,
.bus_config = &(spi_bus_config_t){
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
},
.dev_config = {
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
},
.rst_io_num = RC522_SCANNER_GPIO_RST,
};
static rc522_driver_handle_t driver;
static rc522_handle_t scanner;
#define DUMP(format, ...) esp_log_write(ESP_LOG_INFO, TAG, format, ##__VA_ARGS__)
static void dump_header()
{
DUMP("Sector Block Bytes AccessBits\n");
DUMP(" 0 1 2 3 4 5 6 7 8 9 11 12 15 c1 c2 c3\n");
}
static void dump_block(rc522_mifare_sector_block_t *block, uint8_t sector_index)
{
if (block->type == RC522_MIFARE_BLOCK_TRAILER) {
DUMP("%*d", 6, sector_index);
}
else {
DUMP("%*s", 6, "");
}
// Block address
DUMP(" %*d", 5, block->address);
// Data
DUMP(" ");
for (uint8_t i = 0; i < 16; i++) {
DUMP("%02" RC522_X "", block->bytes[i]);
if ((i % 4) == 3) {
DUMP(" ");
}
}
// Access bits
DUMP(" %d %d %d", block->access_bits.c1, block->access_bits.c2, block->access_bits.c3);
// String representation (if it's data block)
if (block->type == RC522_MIFARE_BLOCK_DATA) {
DUMP(" |");
for (uint8_t i = 0; i < 16; i++) {
if (block->bytes[i] >= 32 && block->bytes[i] <= 126) { // standard ascii codes
DUMP(LOG_COLOR(LOG_COLOR_GREEN) "%c" LOG_RESET_COLOR, block->bytes[i]);
}
else {
DUMP("%c", '.');
}
}
DUMP("|");
}
// Value (if it's value block)
else if (block->type == RC522_MIFARE_BLOCK_VALUE) {
DUMP(" (val=%ld (0x%04l" RC522_X "), adr=0x%02" RC522_X ")",
block->value_info.value,
block->value_info.value,
block->value_info.addr);
}
// Errors and warnings
if (block->type == RC522_MIFARE_BLOCK_TRAILER && block->error == RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION) {
DUMP(" ABITS_ERR");
}
if (block->type == RC522_MIFARE_BLOCK_VALUE && block->error == RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION) {
DUMP(" VAL_ERR");
}
// Termination
DUMP("\n");
}
static esp_err_t dump_memory(rc522_handle_t scanner, rc522_picc_t *picc)
{
rc522_mifare_key_t key = {
.value = { RC522_MIFARE_KEY_VALUE_DEFAULT },
};
rc522_mifare_desc_t mifare;
ESP_RETURN_ON_ERROR(rc522_mifare_get_desc(picc, &mifare), TAG, "");
DUMP("\n");
dump_header();
// Start from the highest sector
uint8_t sector_index = mifare.number_of_sectors - 1;
do {
rc522_mifare_sector_desc_t sector;
ESP_RETURN_ON_ERROR(rc522_mifare_get_sector_desc(sector_index, &sector), TAG, "");
ESP_RETURN_ON_ERROR(rc522_mifare_auth_sector(scanner, picc, &sector, &key), TAG, "");
rc522_mifare_sector_block_t trailer;
ESP_RETURN_ON_ERROR(rc522_mifare_read_sector_trailer_block(scanner, picc, &sector, &trailer), TAG, "");
dump_block(&trailer, sector_index);
// Start from the highest (non-trailer) block
uint8_t block_offset = sector.number_of_blocks - 2;
do {
rc522_mifare_sector_block_t block;
ESP_RETURN_ON_ERROR(rc522_mifare_read_sector_block(scanner, picc, &sector, &trailer, block_offset, &block),
TAG,
"");
dump_block(&block, sector_index);
}
while (block_offset--);
}
while (sector_index--);
DUMP("\n");
return ESP_OK;
}
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
rc522_picc_t *picc = event->picc;
if (picc->state != RC522_PICC_STATE_ACTIVE) {
return;
}
rc522_picc_print(picc);
if (!rc522_mifare_type_is_classic_compatible(picc->type)) {
ESP_LOGW(TAG, "Card is not supported by this example");
return;
}
if (dump_memory(scanner, picc) == ESP_OK) {
ESP_LOGI(TAG, "Memory dump success");
}
else {
ESP_LOGE(TAG, "Memory dump failed");
}
if (rc522_mifare_deauth(scanner, picc) != ESP_OK) {
ESP_LOGW(TAG, "Deauth failed");
}
}
void app_main()
{
rc522_spi_create(&driver_config, &driver);
rc522_driver_install(driver);
rc522_config_t scanner_config = {
.driver = driver,
};
rc522_create(&scanner_config, &scanner);
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
rc522_start(scanner);
}

View File

@@ -0,0 +1 @@
CONFIG_LOG_COLORS=y

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(multiple_scanners)

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "multiple_scanners.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,3 @@
dependencies:
abobija/rc522:
version: '*'

View File

@@ -0,0 +1,117 @@
#include <esp_log.h>
#include "rc522.h"
#include "driver/rc522_spi.h"
#include "rc522_picc.h"
static const char *TAG = "rc522-multiple-scanners-example";
// SPI bus GPIOs
#define RC522_SPI_BUS_GPIO_MISO (25)
#define RC522_SPI_BUS_GPIO_MOSI (23)
#define RC522_SPI_BUS_GPIO_SCLK (19)
// CS GPIOs for each scanner
#define RC522_SPI_SCANNER_1_GPIO_SDA (22)
#define RC522_SPI_SCANNER_2_GPIO_SDA (26)
// {{ Scanner configurations
static rc522_spi_config_t scanner_1_config = {
.host_id = SPI3_HOST,
.bus_config = &(spi_bus_config_t){
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
},
.dev_config = {
.spics_io_num = RC522_SPI_SCANNER_1_GPIO_SDA,
},
.rst_io_num = -1, // soft-reset
};
// Second scanner does not need bus configuration,
// since first scanner will initialize the bus.
static rc522_spi_config_t scanner_2_config = {
.host_id = SPI3_HOST,
.dev_config = {
.spics_io_num = RC522_SPI_SCANNER_2_GPIO_SDA,
},
.rst_io_num = -1, // soft-reset
};
// }}
// Drivers
static rc522_driver_handle_t driver_1;
static rc522_driver_handle_t driver_2;
// Scanners
static rc522_handle_t scanner_1;
static rc522_handle_t scanner_2;
// Event handler
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
rc522_handle_t scanner = (rc522_handle_t)arg;
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
rc522_picc_t *picc = event->picc;
uint8_t scanner_no = 1;
if (scanner == scanner_2) {
scanner_no = 2;
}
if (picc->state == RC522_PICC_STATE_ACTIVE) {
ESP_LOGI(TAG, "Card detected on the scanner #%d", scanner_no);
rc522_picc_print(picc);
}
else if (picc->state == RC522_PICC_STATE_IDLE && event->old_state >= RC522_PICC_STATE_ACTIVE) {
ESP_LOGI(TAG, "Card has been removed from the scanner #%d", scanner_no);
}
}
// App entry point
void app_main()
{
// Create drivers
rc522_spi_create(&scanner_1_config, &driver_1);
rc522_spi_create(&scanner_2_config, &driver_2);
// Install drivers
rc522_driver_install(driver_1);
rc522_driver_install(driver_2);
// Create scanners
rc522_create(
&(rc522_config_t) {
.driver = driver_1,
},
&scanner_1);
rc522_create(
&(rc522_config_t) {
.driver = driver_2,
},
&scanner_2);
// Register events for each scanner
rc522_register_events(scanner_1, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, scanner_1);
rc522_register_events(scanner_2, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, scanner_2);
// Start scanners
rc522_start(scanner_1);
rc522_start(scanner_2);
}

View File

@@ -0,0 +1 @@
CONFIG_LOG_COLORS=y

View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(picc_nxp)

View File

@@ -0,0 +1,65 @@
I (98) main_task: Started on CPU0
I (108) main_task: Calling app_main()
I (128) rc522: PCD (fw=unknown) initialized
I (128) main_task: Returned from app_main()
I (5088) rc522:
I (5088) rc522: ╔══════════════╗
I (5088) rc522: ║ ║ Type: NTAG215
I (5098) rc522: ║ RFID ║ UID: 04 26 F8 F5 9F 61 80
I (5098) rc522: ║ CARD ║ ATQA: 0x4400
I (5098) rc522: ║ ║ SAK: 0x00
I (5098) rc522: ╚══════════════╝
I (5098) rc522:
I (5098) rc522-picc-nxp-example: User pages: 126/135
I (5098) rc522-picc-nxp-example: Writing to address 05: R=00
I (5108) rc522-picc-nxp-example: Reading from address 05: R=00
I (5108) rc522-picc-nxp-example: Data read/write match
I (5108) rc522-picc-nxp-example: Pages 5 to 8: 10 20 30 40 F3 E7 42 BD 00 00 00 00 00 00 00 00
I (5108) rc522-picc-nxp-example: Dumping 3 pages: R=00
I (5108) rc522-picc-nxp-example: Page 004: 67 B6 A5 B4
I (5108) rc522-picc-nxp-example: Page 005: 10 20 30 40
I (5108) rc522-picc-nxp-example: Page 006: F3 E7 42 BD
I (5108) rc522-picc-nxp-example: Password auth: R=00
I (5118) rc522-picc-nxp-example: Read config bytes: R=00
I (5118) rc522-picc-nxp-example: NFC_CNT_EN bit: 0
I (5118) rc522-picc-nxp-example: Counter disabled; skipping
I (5118) rc522-picc-nxp-example: Read signature: R=00
I (5118) rc522-picc-nxp-example: Signature: B6 54 F4 00 A4 A5 6E 28 96 90 76 84 39 CA B4 CE 8E FC 3D 08 65 A3 29 60 1C 36 EA E4 B5 DA 74 7C
Page | Offset: +0 +1 +2 +3
00 | 04 26 F8 52 F5 9F 61 80 8B 48 00 00 E1 10 3E 00
04 | 67 B6 A5 B4* 10 20 30 40 F3 E7 42 BD 00 00 00 00 * Start of user memory (0x04)
08 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
14 | 1A 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00
18 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
1C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
24 | 00 00 00 00 00 00 00 00 1A 2B 00 3C 00 00 00 00
28 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
2C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
30 | 2A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
34 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
38 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
40 | 2A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
44 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
48 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
4C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
50 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
54 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
58 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
5C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
60 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
64 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
68 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
6C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
70 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
74 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
78 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
7C | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
80 | 00 00 00 00 00 00 00 00* 00 00 00 BD 04 00 00 FF * End of user memory (0x81)
84 | 00 05 00 00 00 00 00 00 00 00 00 00 04 26 F8 52
I (5258) rc522-picc-nxp-example: Memory dump success

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "picc_nxp.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,3 @@
dependencies:
abobija/rc522:
version: '*'

View File

@@ -0,0 +1,283 @@
#include <esp_log.h>
#include <esp_check.h>
#include <string.h>
#include "driver/spi_master.h"
#include "rc522.h"
#include "rc522_pcd.h"
#include "driver/rc522_spi.h"
#include "picc/rc522_mifare.h"
#include "picc/rc522_nxp.h"
#include "rc522_picc.h"
static const char *TAG = "rc522-picc-nxp-example";
#define RC522_SPI_BUS_GPIO_MISO (16)
#define RC522_SPI_BUS_GPIO_MOSI (15)
#define RC522_SPI_BUS_GPIO_SCLK (17)
#define RC522_SPI_SCANNER_GPIO_SDA (1)
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
static rc522_spi_config_t driver_config = {
.host_id = SPI3_HOST,
.bus_config = &(spi_bus_config_t){
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
},
.dev_config = {
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
},
.rst_io_num = RC522_SCANNER_GPIO_RST,
};
static rc522_driver_handle_t driver;
static rc522_handle_t scanner;
static char str_buf[128];
#define DUMP(format, ...) esp_log_write(ESP_LOG_INFO, TAG, format, ##__VA_ARGS__)
static void dump_header()
{
DUMP("Page | Offset: +0 +1 +2 +3\n");
// 00 | 10 20 30 40 50 60 70 80 90 A0 B0 C0 D0 E0 F0 11
}
// Dump four pages (size of a single READ) to output, annotating the start
// and end pages of user memory if present
static void dump_pages(uint8_t address, uint8_t page[RC522_NXP_READ_SIZE], rc522_picc_type_t picc_type)
{
// Get the start and end pages of user memory so we can mark them in the dump
uint8_t start_page = rc522_nxp_get_user_mem_start(picc_type);
uint8_t end_page = rc522_nxp_get_user_mem_end(picc_type);
// 0: nothing of interest, 1: start page, 2: end page
uint8_t found = 0;
DUMP(" %02X |", address);
for (uint8_t i = 0; i < RC522_NXP_READ_SIZE; i++) {
DUMP(" %02X", page[i]);
if (i / 4 + address == start_page && i % 4 == 3) {
found = 1;
DUMP("*");
}
else if (i / 4 + address == end_page && i % 4 == 3) {
found = 2;
DUMP("*");
}
else if (i % 4 == 3) {
DUMP(" ");
}
}
if (found == 1) {
DUMP(" * Start of user memory (0x%02X)", start_page);
}
else if (found == 2) {
DUMP(" * End of user memory (0x%02X)", end_page);
}
DUMP("\n");
}
// Dump all memory to output
static esp_err_t dump_memory(rc522_handle_t scanner, rc522_picc_t *picc)
{
DUMP("\n");
dump_header();
uint8_t page_count = rc522_nxp_get_page_count(picc->type);
// Standard read is 4 pages; allocate an appropriate buffer and increment by
// 4 pages on each loop
uint8_t recv[RC522_NXP_PAGE_SIZE * 4];
for (uint8_t i = 0; i < page_count; i += 4) {
ESP_RETURN_ON_ERROR(rc522_nxp_read(scanner, picc, i, recv), TAG, "");
dump_pages(i, recv, picc->type);
}
DUMP("\n");
return ESP_OK;
}
static void buf_to_hex(const uint8_t *buffer, uint8_t buflen, char *strbuf, uint8_t strbuflen)
{
const char *format = "%02X ";
uint16_t len = 0;
for (uint16_t i = 0; i < buflen; i++) {
len += sprintf(strbuf + len, format, buffer[i]);
}
strbuf[len - 1] = 0x00;
}
// Example: Get the total and user memory page count of an NXP PICC
static void example_page_counts(rc522_handle_t rc522, rc522_picc_t *picc)
{
uint8_t page_count = rc522_nxp_get_page_count(picc->type);
uint8_t user_page_count = rc522_nxp_get_user_page_count(picc->type);
ESP_LOGI(TAG, "User pages: %d/%d", user_page_count, page_count);
}
// Example: Write to and read from user memory, verifying write works
static esp_err_t example_read_write(rc522_handle_t rc522, rc522_picc_t *picc)
{
uint8_t page = 5;
uint8_t write_buf[4] = { 0x10, 0x20, 0x30, 0x40 };
esp_err_t ret = rc522_nxp_write(scanner, picc, page, write_buf);
ESP_LOGI(TAG, "Writing to address %02X: R=%02X", page, ret);
ESP_RETURN_ON_ERROR(ret, TAG, "Error in WRITE");
uint8_t read_buf[16] = { 0 };
ret = rc522_nxp_read(scanner, picc, page, read_buf);
ESP_LOGI(TAG, "Reading from address %02X: R=%02X", page, ret);
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ");
if (memcmp(write_buf, read_buf, 4) == 0) {
ESP_LOGI(TAG, "Data read/write match");
}
else {
ESP_LOGW(TAG, "Data read/write mismatch");
}
buf_to_hex(read_buf, 16, str_buf, sizeof(str_buf));
ESP_LOGI(TAG, "Pages %d to %d: %s", page, page + 3, str_buf);
return ESP_OK;
}
// Example: Use of FAST_READ command for variable-size read
static esp_err_t example_fast_read(rc522_handle_t rc522, rc522_picc_t *picc)
{
uint8_t page_count = 3;
uint8_t start_page = 4;
// Need to allocate 4 bytes per page
uint8_t *mem_buf = calloc(page_count, 4);
ESP_RETURN_ON_FALSE(mem_buf != NULL, ESP_FAIL, TAG, "Out of memory");
rc522_nxp_fast_read_data_t mem_data = {
.bytes = mem_buf,
.buffer_size = page_count * 4,
};
esp_err_t ret = rc522_nxp_fast_read(scanner,
picc,
start_page,
start_page + page_count - 1, // end is inclusive, so subtract 1
&mem_data);
ESP_LOGI(TAG, "Dumping %d pages: R=%02X", page_count, ret);
ESP_RETURN_ON_ERROR(ret, TAG, "Error in FAST_READ");
for (uint8_t i = 0; i < page_count; i++) {
buf_to_hex(&mem_buf[i * 4], 4, str_buf, 128);
ESP_LOGI(TAG, "Page %03d: %s", start_page + i, str_buf);
}
free(mem_buf);
return ESP_OK;
}
// Example: Password authentication with PWD/PACK
static esp_err_t example_pwd_auth(rc522_handle_t rc522, rc522_picc_t *picc)
{
// Note both PWD and PACK can and should be programmed during initial
// PICC setup
esp_err_t ret = rc522_nxp_pwd_auth(scanner,
picc,
RC522_NXP_DEFAULT_PWD, // 4-byte password, default is 0xFFFFFFFF
RC522_NXP_DEFAULT_PACK, // 2-byte acknowledge, default is 0x0000
&picc->state // Ensure the card state is updated if successful
);
ESP_LOGI(TAG, "Password auth: R=%02X", ret);
return ret;
}
// Example: Counter reading
static esp_err_t example_read_cnt(rc522_handle_t rc522, rc522_picc_t *picc)
{
// Counters can only be read if they're enabled, otherwise the PICC will
// just respond with a NAK. On the NTAG21x, the single counter can be
// enabled in the config section, so read it and check if it's enabled.
// 0x83 for NTAG215; address will vary on other PICCs. Check your datasheet
uint8_t read_buf[16];
esp_err_t ret = rc522_nxp_read(scanner, picc, 0x83, read_buf);
ESP_LOGI(TAG, "Read config bytes: R=%02X", ret);
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ");
uint8_t count_en = (read_buf[4] >> 4) & 0x01;
ESP_LOGI(TAG, "NFC_CNT_EN bit: %hhu", count_en);
if (count_en) { // Avoid a NAK since then we need to wake the chip back up
uint32_t counter = 0;
// Counter argument is always 0x02 on NTAG21x; Ultralight PICCs with
// counters usually have 3 and the argument selects which to read
ret = rc522_nxp_read_cnt(scanner, picc, 0x02, &counter);
ESP_LOGI(TAG, "Read counter: R=%02X", ret);
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ_CNT");
ESP_LOGI(TAG, "Counter value: %lu", counter);
}
else {
ESP_LOGI(TAG, "Counter disabled; skipping");
}
return ESP_OK;
}
// Example: Reading the originality signature from the PICC
static esp_err_t example_get_signature(rc522_handle_t rc522, rc522_picc_t *picc)
{
// Signatures can be either 32 or 48 bytes - here we default to 48 and let
// the library fill as appropriate. The sig_size field is updated with the
// correct size
uint8_t sig_buf[48];
rc522_nxp_sig_t sig = { .bytes = sig_buf, .buffer_size = sizeof(sig_buf) };
esp_err_t ret = rc522_nxp_read_sig(scanner, picc, &sig);
ESP_LOGI(TAG, "Read signature: R=%02X", ret);
ESP_RETURN_ON_ERROR(ret, TAG, "Error in READ_SIG");
buf_to_hex(sig_buf, sig.sig_size, str_buf, 128);
ESP_LOGI(TAG, "Signature: %s", str_buf);
return ESP_OK;
}
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
rc522_picc_t *picc = event->picc;
if (picc->state != RC522_PICC_STATE_ACTIVE) {
return;
}
if (picc->type != RC522_PICC_TYPE_MIFARE_UL) {
return;
}
// get_type MUST be called before rc522_nxp_* functions to ensure the PICC
// has been correctly identified
rc522_nxp_get_type(scanner, picc, &picc->type);
rc522_picc_print(picc);
example_page_counts(scanner, picc);
example_read_write(scanner, picc);
example_fast_read(scanner, picc);
example_pwd_auth(scanner, picc);
example_read_cnt(scanner, picc);
example_get_signature(scanner, picc);
esp_err_t ret = dump_memory(scanner, picc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Memory dump failed: E=%02X", ret);
return;
}
ESP_LOGI(TAG, "Memory dump success");
}
void app_main()
{
rc522_spi_create(&driver_config, &driver);
rc522_driver_install(driver);
rc522_config_t scanner_config = {
.driver = driver,
};
rc522_create(&scanner_config, &scanner);
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
rc522_start(scanner);
}

View File

@@ -0,0 +1 @@
CONFIG_LOG_COLORS=y

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(read_write)

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "read_write.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,3 @@
dependencies:
abobija/rc522:
version: '*'

View File

@@ -0,0 +1,163 @@
#include <esp_log.h>
#include <esp_check.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include "rc522.h"
#include "driver/rc522_spi.h"
#include "picc/rc522_mifare.h"
static const char *TAG = "rc522-read-write-example";
#define RC522_SPI_BUS_GPIO_MISO (25)
#define RC522_SPI_BUS_GPIO_MOSI (23)
#define RC522_SPI_BUS_GPIO_SCLK (19)
#define RC522_SPI_SCANNER_GPIO_SDA (22)
#define RC522_SCANNER_GPIO_RST (-1) // soft-reset
static rc522_spi_config_t driver_config = {
.host_id = SPI3_HOST,
.bus_config = &(spi_bus_config_t){
.miso_io_num = RC522_SPI_BUS_GPIO_MISO,
.mosi_io_num = RC522_SPI_BUS_GPIO_MOSI,
.sclk_io_num = RC522_SPI_BUS_GPIO_SCLK,
},
.dev_config = {
.spics_io_num = RC522_SPI_SCANNER_GPIO_SDA,
},
.rst_io_num = RC522_SCANNER_GPIO_RST,
};
static rc522_driver_handle_t driver;
static rc522_handle_t scanner;
static void dump_block(uint8_t buffer[RC522_MIFARE_BLOCK_SIZE])
{
for (uint8_t i = 0; i < RC522_MIFARE_BLOCK_SIZE; i++) {
esp_log_write(ESP_LOG_INFO, TAG, "%02" RC522_X " ", buffer[i]);
}
esp_log_write(ESP_LOG_INFO, TAG, "\n");
}
static esp_err_t read_write(rc522_handle_t scanner, rc522_picc_t *picc)
{
const char *data_to_write = "rc522 is dope";
const uint8_t block_address = 4;
rc522_mifare_key_t key = {
.value = { RC522_MIFARE_KEY_VALUE_DEFAULT },
};
if (strlen(data_to_write) > 14) {
ESP_LOGW(TAG, "Please make sure that data length is no more than 14 characters");
ESP_LOGW(TAG, "since we are going to use random values for last two bytes");
return ESP_ERR_INVALID_ARG;
}
ESP_RETURN_ON_ERROR(rc522_mifare_auth(scanner, picc, block_address, &key), TAG, "auth fail");
uint8_t read_buffer[RC522_MIFARE_BLOCK_SIZE];
uint8_t write_buffer[RC522_MIFARE_BLOCK_SIZE];
// Read
ESP_LOGI(TAG, "Reading data from the block %d", block_address);
ESP_RETURN_ON_ERROR(rc522_mifare_read(scanner, picc, block_address, read_buffer), TAG, "read fail");
ESP_LOGI(TAG, "Current data:");
dump_block(read_buffer);
// ~Read
// Write
strncpy((char *)write_buffer, data_to_write, RC522_MIFARE_BLOCK_SIZE);
// Set random values for the last two bytes in the write buffer
// so we are using new data on each call of read_write function
int r = rand();
write_buffer[RC522_MIFARE_BLOCK_SIZE - 2] = ((r >> 8) & 0xFF);
write_buffer[RC522_MIFARE_BLOCK_SIZE - 1] = ((r >> 0) & 0xFF);
ESP_LOGI(TAG, "Writing data (%s) to the block %d:", data_to_write, block_address);
dump_block(write_buffer);
ESP_RETURN_ON_ERROR(rc522_mifare_write(scanner, picc, block_address, write_buffer), TAG, "write fail");
// ~Write
// Read again
ESP_LOGI(TAG, "Write done. Verifying...");
ESP_RETURN_ON_ERROR(rc522_mifare_read(scanner, picc, block_address, read_buffer), TAG, "read fail");
ESP_LOGI(TAG, "New data in the block %d:", block_address);
dump_block(read_buffer);
// ~Read again
// Validate
bool rw_missmatch = false;
uint8_t i;
for (i = 0; i < RC522_MIFARE_BLOCK_SIZE; i++) {
if (write_buffer[i] != read_buffer[i]) {
rw_missmatch = true;
break;
}
}
// ~Validate
// Feedback
if (!rw_missmatch) {
ESP_LOGI(TAG, "Verified.");
}
else {
ESP_LOGE(TAG,
"Write failed. RW missmatch on the byte %d (w:%02" RC522_X ", r:%02" RC522_X ")",
i,
write_buffer[i],
read_buffer[i]);
dump_block(write_buffer);
dump_block(read_buffer);
}
// ~Feedback
return ESP_OK;
}
static void on_picc_state_changed(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
rc522_picc_state_changed_event_t *event = (rc522_picc_state_changed_event_t *)data;
rc522_picc_t *picc = event->picc;
if (picc->state != RC522_PICC_STATE_ACTIVE) {
return;
}
rc522_picc_print(picc);
if (!rc522_mifare_type_is_classic_compatible(picc->type)) {
ESP_LOGW(TAG, "Card is not supported by this example");
return;
}
if (read_write(scanner, picc) == ESP_OK) {
ESP_LOGI(TAG, "Read/Write success");
}
else {
ESP_LOGE(TAG, "Read/Write failed");
}
if (rc522_mifare_deauth(scanner, picc) != ESP_OK) {
ESP_LOGW(TAG, "Deauth failed");
}
}
void app_main()
{
srand(time(NULL)); // Initialize random generator
rc522_spi_create(&driver_config, &driver);
rc522_driver_install(driver);
rc522_config_t scanner_config = {
.driver = driver,
};
rc522_create(&scanner_config, &scanner);
rc522_register_events(scanner, RC522_EVENT_PICC_STATE_CHANGED, on_picc_state_changed, NULL);
rc522_start(scanner);
}

View File

@@ -0,0 +1 @@
CONFIG_LOG_COLORS=y

View File

@@ -0,0 +1,28 @@
#pragma once
#include "driver/i2c_master.h"
#include "driver/i2c_types.h"
#include "rc522_driver.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
i2c_master_bus_handle_t bus;
i2c_master_bus_config_t *bus_config; // Only required if `bus` is NULL
i2c_device_config_t dev_config;
/**
* GPIO number of the RC522 RST pin.
* Set to -1 if the RST pin is not connected.
*/
gpio_num_t rst_io_num;
} rc522_i2c_config_t;
esp_err_t rc522_i2c_create(const rc522_i2c_config_t *config, rc522_driver_handle_t *driver);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,32 @@
#pragma once
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include "rc522_driver.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RC522_SPI_WRITE (0)
#define RC522_SPI_READ (1)
typedef struct
{
spi_host_device_t host_id;
spi_bus_config_t *bus_config;
spi_device_interface_config_t dev_config;
spi_dma_chan_t dma_chan;
/**
* GPIO number of the RC522 RST pin.
* Set to -1 if the RST pin is not connected.
*/
gpio_num_t rst_io_num;
} rc522_spi_config_t;
esp_err_t rc522_spi_create(const rc522_spi_config_t *config, rc522_driver_handle_t *driver);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,193 @@
#pragma once
#include "rc522_types.h"
#include "rc522_picc.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RC522_ERR_MIFARE_BASE (RC522_ERR_BASE + 0xFF)
#define RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION (RC522_ERR_MIFARE_BASE + 1)
#define RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION (RC522_ERR_MIFARE_BASE + 2)
#define RC522_ERR_MIFARE_NACK (RC522_ERR_MIFARE_BASE + 3)
#define RC522_ERR_MIFARE_AUTHENTICATION_FAILED (RC522_ERR_MIFARE_BASE + 4)
#define RC522_ERR_MIFARE_SECTOR_TRAILER_WRITE_NOT_ALLOWED (RC522_ERR_MIFARE_BASE + 5)
#define RC522_MIFARE_SECTOR_INDEX_MAX (39) // No MIFARE Classic has more than 40 sectors
#define RC522_MIFARE_BLOCK_SIZE (16)
#define RC522_MIFARE_ACK (0x0A) // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK.
#define RC522_MIFARE_KEY_SIZE (6)
#define RC522_MIFARE_KEY_VALUE_DEFAULT 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
typedef struct
{
uint8_t number_of_sectors;
} rc522_mifare_desc_t;
typedef struct
{
uint8_t index; // Zero-based index of Sector
uint8_t number_of_blocks; // Total number of blocks inside of Sector
uint8_t block_0_address; // Zero-based index of the first Block inside of MIFARE memory
} rc522_mifare_sector_desc_t;
typedef enum
{
RC522_MIFARE_KEY_A = 0,
RC522_MIFARE_KEY_B,
} rc522_mifare_key_type_t;
typedef struct
{
rc522_mifare_key_type_t type;
uint8_t value[RC522_MIFARE_KEY_SIZE];
} rc522_mifare_key_t;
typedef enum
{
RC522_MIFARE_BLOCK_UNDEFINED = 0,
RC522_MIFARE_BLOCK_TRAILER,
RC522_MIFARE_BLOCK_DATA,
RC522_MIFARE_BLOCK_VALUE,
RC522_MIFARE_BLOCK_MANUFACTURER_DATA,
} rc522_mifare_block_type_t;
typedef struct
{
uint8_t c1 :1;
uint8_t c2 :1;
uint8_t c3 :1;
} rc522_mifare_access_bits_t;
typedef struct
{
rc522_mifare_access_bits_t access_bits[4]; // [3] = Trailer, [2] = Block 2, [1] = Block 1, [0] = Block 0
} rc522_mifare_sector_trailer_info_t;
typedef struct
{
int32_t value; // Value stored in the block
uint8_t addr; // Value block address (this is not the memory address)
} rc522_mifare_sector_value_block_info_t;
typedef struct
{
uint8_t address;
rc522_mifare_block_type_t type;
uint8_t bytes[RC522_MIFARE_BLOCK_SIZE];
union
{
rc522_mifare_sector_trailer_info_t trailer_info; // Valid only if type == RC522_MIFARE_BLOCK_TRAILER
rc522_mifare_sector_value_block_info_t value_info; // Valid only if type == RC522_MIFARE_BLOCK_VALUE
};
rc522_mifare_access_bits_t access_bits;
esp_err_t error; // e.g. acces bits or value block integrity violation
} rc522_mifare_sector_block_t;
// {{ MIFARE_Specific_Functions
/**
* @brief Authenticate a block read/write operations.
*
* After a successful authentication, communication between
* the PCD and the PICC is secured with MF1xxS20, MF1xxS70 or MF1xxS50 encryption.
*
* Once when read/write operations are authenticated for one block inside of a sector,
* read/write operations are authenticated for all blocks inside of that sector as well.
*
* @note After read/write operations are done, the PICC must be deauthenticated using @c rc522_mifare_deauth().
*
* @param rc522 RC522 handle
* @param picc PICC that is currently selected
* @param block_address Address of the block to authenticate
* @param key Key to authenticate with
*/
esp_err_t rc522_mifare_auth(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address, const rc522_mifare_key_t *key);
/**
* @brief Read from a block at the given @c block_address on the PICC.
*
* @note The block must be authenticated before calling this function.
*
* @param rc522 RC522 handle
* @param picc PICC that is currently selected
* @param block_address Address of the block to read
* @param[out] out_buffer Buffer of exactly @c RC522_MIFARE_BLOCK_SIZE bytes to store the retrived data in
*/
esp_err_t rc522_mifare_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
uint8_t out_buffer[RC522_MIFARE_BLOCK_SIZE]);
/**
* @brief Write to a block at the given @c block_address on the PICC.
*
* @note The block must be authenticated before calling this function.
*
* @param rc522 RC522 handle
* @param picc PICC that is currently selected
* @param block_address Address of the block to write
* @param[in] buffer Buffer of exactly @c RC522_MIFARE_BLOCK_SIZE that contains the data to write
*/
esp_err_t rc522_mifare_write(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
const uint8_t buffer[RC522_MIFARE_BLOCK_SIZE]);
// }}
// {{ MIFARE_Utility_Functions
/**
* @brief Deauthenticates read/write operations and allows PCD to perform other commands
*
* @param rc522 RC522 handle
* @param picc PICC that is currently selected
*/
esp_err_t rc522_mifare_deauth(const rc522_handle_t rc522, const rc522_picc_t *picc);
/**
* @brief Authenticates read/write operations
*/
esp_err_t rc522_mifare_auth_sector(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_key_t *key);
/**
* @brief Checks if the PICC is MIFARE Classic
*/
bool rc522_mifare_type_is_classic_compatible(rc522_picc_type_t type);
/**
* @brief Get MIFARE description (e.g number of sectors)
*/
esp_err_t rc522_mifare_get_desc(const rc522_picc_t *picc, rc522_mifare_desc_t *out_mifare_desc);
/**
* @brief Get MIFARE sector description (e.g number of blocks, block 0 address)
*/
esp_err_t rc522_mifare_get_sector_desc(uint8_t sector_index, rc522_mifare_sector_desc_t *out_sector_desc);
/**
* @brief Read and parse MIFARE sector trailer block
*/
esp_err_t rc522_mifare_read_sector_trailer_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, rc522_mifare_sector_block_t *out_trailer);
/**
* @brief Read and parse MIFARE sector (non-trailer) block
*/
esp_err_t rc522_mifare_read_sector_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_sector_block_t *trailer, uint8_t block_offset,
rc522_mifare_sector_block_t *out_block);
esp_err_t rc522_mifare_get_number_of_sectors(rc522_picc_type_t type, uint8_t *out_result);
uint8_t rc522_mifare_get_sector_index_by_block_address(uint8_t block_address);
esp_err_t rc522_mifare_get_number_of_blocks_in_sector(uint8_t sector_index, uint8_t *out_result);
esp_err_t rc522_mifare_get_sector_block_0_address(uint8_t sector_index, uint8_t *out_result);
// }}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,377 @@
/** Functions to support interfacing with NXP tags
*
* This header defines functions for working with ISO14443-3 compliant NXP
* PICCs, primarily the Ultralight and NTAG families. These PICCs use
* predominately the same command set, with some small variations.
*
* For MIFARE Classic PICCs, see `include/picc/rc522_mifare.h`.
*
* Authentication:
* Ultralight {C,AES}: Key-based (3DES and AES respectively)
* Ultralight EV1, NTAG 21x: Password-based
*
* Some functions have been commented out; ideally these would be implemented
* at some stage to implement full support for these PICC functions, but I don't
* have any of the actual PICCs to properly test with.
*
* CMD/PICC | UL | UL C | UL EV1 | UL AES | UL Nano | NTAG21x |
* -------------|---------|---------|---------|---------|---------|---------|
* GET_VERSION | | | y | y | y | y |
* READ | y | y | y | y | y | y |
* FAST_READ | | | y | y | | y |
* WRITE | y | y | y | y | y | y |
* COMPAT_WRITE | y | y | y | | y | y |
* READ_CNT | | | 3 | 3 | | 1 |
* INCR_CNT | | | y | y | | |
* READ_SIG | | | 32 | 48 | 32 | 32 | (bytes)
* WRITE_SIG | | | | y | y | |
* LOCK_SIG | | | | y | y | |
* AUTHENTICATE | | 3DES | | AES | | |
* PWD_AUTH | | | y | | | y |
* VCSL | | | y | y | | |
*/
#pragma once
#include "rc522_picc.h"
#include "rc522_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RC522_ERR_NXP_BASE (RC522_ERR_BASE + 0XF0)
#define RC522_ERR_NXP_NACK (RC522_ERR_NXP_BASE + 1)
#define RC522_ERR_NXP_AUTHENTICATION_FAILED (RC522_ERR_NXP_BASE + 2)
#define RC522_ERR_NXP_PACK_MISMATCH (RC522_ERR_NXP_BASE + 3)
#define RC522_NXP_PWD_SIZE 4
#define RC522_NXP_PACK_SIZE 2
#define RC522_NXP_PAGE_SIZE 4
#define RC522_NXP_READ_SIZE (RC522_NXP_PAGE_SIZE * 4)
extern const uint8_t RC522_NXP_DEFAULT_PWD[RC522_NXP_PWD_SIZE];
extern const uint8_t RC522_NXP_DEFAULT_PACK[RC522_NXP_PACK_SIZE];
enum
{
/**
* Get PICC information.
*/
RC522_NXP_GET_VERSION = 0x60,
/**
* Read four pages from memory.
*/
RC522_NXP_READ = 0x30,
/**
* Read pages from START to END in memory.
*/
RC522_NXP_FAST_READ = 0x3A,
/**
* Write a page to memory.
*/
RC522_NXP_WRITE = 0xA2,
/**
* Write the first 4 bytes of 16 to a page in memory.
*/
RC522_NXP_COMPAT_WRITE = 0xA0,
/**
* Read an onboard counter.
*/
RC522_NXP_READ_CNT = 0x39,
/**
* Increment an onboard counter.
*/
RC522_NXP_INCR_CNT = 0xA5,
/**
* Read the originality signature.
*/
RC522_NXP_READ_SIG = 0x3C,
/**
* Write a new originality signature.
*/
RC522_NXP_WRITE_SIG = 0xA9,
/**
* Control access to the originality signature.
*/
RC522_NXP_LOCK_SIG = 0xAC,
/**
* Perform key-based authentication, stage 1.
*/
RC522_NXP_AUTHENTICATE = 0x1A,
/**
* Perform key-based authentication, stage 2.
*/
RC522_NXP_AUTHENTICATE_2 = 0xAF,
/**
* Perform password-based authentication.
*/
RC522_NXP_PWD_AUTH = 0x1B,
RC522_NXP_VCSL = 0x4B,
};
/**
* L3 GET_VERSION: product_type field
*/
typedef enum
{
RC522_NXP_PRODUCT_TYPE_UNKNOWN,
RC522_NXP_PRODUCT_TYPE_UL = 0x03,
RC522_NXP_PRODUCT_TYPE_NTAG = 0x04,
} rc522_nxp_product_type_t;
/**
* L3 GET_VERSION: major_version field
*/
typedef enum
{
RC522_NXP_MAJ_VER_UNKNOWN = 0,
RC522_NXP_MAJ_VER_NTAG21 = 0x01,
RC522_NXP_MAJ_VER_UL_EV1 = 0x01,
RC522_NXP_MAJ_VER_UL_NANO = 0x02,
RC522_NXP_MAJ_VER_UL_AES = 0x04,
} rc522_nxp_major_version_t;
/**
* L3 GET_VERSION result
*/
typedef struct
{
uint8_t header;
uint8_t vendor;
rc522_nxp_product_type_t product_type :8;
uint8_t product_subtype;
rc522_nxp_major_version_t major_version :8;
uint8_t minor_version;
uint8_t storage_size;
uint8_t protocol_type;
} rc522_nxp_picc_version_t;
/**
* L3 FAST_READ result
*
* Contains a buffer, the size of the buffer, and the amount of space occupied
* by the data after a read
*/
typedef struct
{
uint8_t *bytes;
uint8_t buffer_size;
uint8_t read_size;
} rc522_nxp_fast_read_data_t;
/**
* Variable-length protection signature
*
* UL EV1: 32 bytes
* UL AES: 48 bytes
* UL Nano: 32 bytes
* NTAG213/215/216: 32 bytes
*/
typedef struct
{
uint8_t *bytes;
uint8_t buffer_size;
uint8_t sig_size;
} rc522_nxp_sig_t;
/**
* @brief Determine the total memory size of an NXP PICC
*
* Given an NXP PICC type, returns the total size of the memory in pages. This
* is not equivalent to generic user memory.
*
* For invalid types, unknown types, or types without pages, returns 0.
*
* @sa rc522_nxp_get_user_page_count()
*/
uint8_t rc522_nxp_get_page_count(rc522_picc_type_t type);
/**
* @brief Determine the user memory size of an NXP PICC
*
* Given an NXP PICC type (RC522_PICC_TYPE_MIFARE_UL_* or RC522_PICC_TYPE_NTAG*),
* returns the total size of the user memory in pages. The user memory always
* starts at page 4.
*
* For invalid types, unknown types, or types without pages, returns 0.
*/
uint8_t rc522_nxp_get_user_page_count(rc522_picc_type_t type);
/**
* @brief Determine the start page of user memory for an NXP PICC
*
* Currently this is 0x04 for all PICCs. It's unlikely to change in future
* for compatibility reasons, but this function exists in case (and also
* as a reference).
*
* For invalid or unknown types, returns 0.
*/
uint8_t rc522_nxp_get_user_mem_start(rc522_picc_type_t type);
/**
* @brief Determine the final page of user memory for an NXP PICC
*
* Returns the address of the final page of user memory for a given PICC
* type. This is the last page that can be safely written to without changing
* the configuration of the PICC.
*
* For invalid or unknown types, returns 0.
*/
uint8_t rc522_nxp_get_user_mem_end(rc522_picc_type_t type);
/**
* @brief Determine the type of an NXP PICC
*
* The Ultralight and NTAG families both use SAK=0, and need further processing
* to identify. This function attempts to determine the PICC in use following
* the procedure in NXP Application Note 10833.
*/
esp_err_t rc522_nxp_get_type(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_type_t *out_type);
/**
* @brief NXP Level 3 GET_VERSION
*
* If supported, returns information about the PICC, including manufacturer,
* product, and memory size.
*/
esp_err_t rc522_nxp_get_version(
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_picc_version_t *out_version);
/**
* @brief NXP Level 3 READ
*
* Reads 4 pages (16 bytes) from a given page address on the PICC.
*/
esp_err_t rc522_nxp_read(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, uint8_t out_buffer[RC522_NXP_PAGE_SIZE * 4]);
/**
* NXP Level 3 FAST_READ
*
* If supported, allows a variable number of pages to be read at once, instead
* of the fixed 4 in READ.
*
* @param start Page address to start reading from
* @param end Page address to end reading (inclusive)
* @param out_buffer Output buffer; should be at least (end-start+1) * 4 bytes
*
* Supported PICCs: UL EV1, UL AES, NTAG21x
*/
esp_err_t rc522_nxp_fast_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t start, uint8_t end,
rc522_nxp_fast_read_data_t *out_buffer);
/**
* @brief NXP Level 3 WRITE
*
* Writes 4 bytes of data to a single page.
*/
esp_err_t rc522_nxp_write(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, const uint8_t buffer[RC522_NXP_PAGE_SIZE]);
/**
* @brief NXP Level 3 READ_CNT
*
* If supported, reads a counter from the PICC. Some PICCs have more than one
* counter; if so counter_no specifies the counter to address.
*
* Supported PICCs: UL EV1 (3), UL AES (3), NTAG21x (1)
*/
esp_err_t rc522_nxp_read_cnt(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t counter_no, uint32_t *out_count);
/**
* @brief NXP Level 3 INCR_CNT
*
* If supported, increments a counter on the PICC. If the PICC has more than one
* counter, counter_no specifies the counter to target.
*
* TODO
*/
// esp_err_t rc522_nxp_incr_cnt(const rc522_handle_t rc522, const rc522_picc_t *picc,
// uint8_t counter_no);
/**
* @brief Checks for support for AUTHENTICATE in L3 on a PICC
*
* Used to determine if a chip supports AUTHENTICATE, to differentiate between
* Ultralight and Ultralight C. Does not perform actual authentication.
*/
esp_err_t rc522_nxp_keyauth_supported(const rc522_handle_t rc522, const rc522_picc_t *picc);
/**
* @brief NXP Level 3 AUTHENTICATE
*
* If supported, performs key-based authentication with a PICC. Note support
* for DES and/or AES algorithms is required.
*
* TODO
*/
// esp_err_t rc522_nxp_key_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
// rc522_nxp_key_t *key);
/**
* @brief NXP Level 3 PWD_AUTH
*
* If supported, performs password-based authentication with a PICC.
*
* @param pwd Password to send to pick
* @param pack Password ACKnowledgement expected to be returned by PICC
* @param[out] out_state PICC state on return. Correct PWD with mismatched PACK
* will still result in AUTHENTICATED
*
* Supported PICCs: UL EV1, NTAG21x
*/
esp_err_t rc522_nxp_pwd_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
const uint8_t pwd[RC522_NXP_PWD_SIZE], const uint8_t pack[RC522_NXP_PACK_SIZE], rc522_picc_state_t *out_state);
/**
* @brief NXP Level 3 READ_SIG
*
* If supported, reads the ECC signature from the PICC. This signature can
* be used to verify originality. Does not actually perform verification.
*
* TODO: Implement verification per AN11350?
*
* Supported PICCs: UL EV1, UL AES, UL Nano, NTAG21x
*/
esp_err_t rc522_nxp_read_sig(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_sig_t *out_sig);
/**
* @brief NXP Level 3 WRITE_SIG
*
* If supported, writes a new signature to the PICC.
*
* TODO
*/
// esp_err_t rc522_nxp_write_sig(const rc522_handle_t rc522, const rc522_picc_t *picc,
// rc522_nxp_sig_t *sig);
/**
* @brief NXP Level 3 LOCK_SIG
*
* If supported, controls the originality signature lock on the PICC.
*
* @param argument 0x00 for unlock, 0x01 for lock, 0x02 for permanent lock
*
* TODO
*/
// esp_err_t rc522_nxp_lock_sig(const rc522_handle_t rc522, const rc522_picc_t *picc,
// rc522_nxp_sig_arg_t argument);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,26 @@
#pragma once
#include "rc522_types.h"
#ifdef __cplusplus
extern "C" {
#endif
ESP_EVENT_DECLARE_BASE(RC522_EVENTS);
esp_err_t rc522_create(const rc522_config_t *config, rc522_handle_t *out_rc522);
esp_err_t rc522_register_events(
const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler, void *event_handler_arg);
esp_err_t rc522_unregister_events(const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler);
esp_err_t rc522_start(rc522_handle_t rc522);
esp_err_t rc522_pause(rc522_handle_t rc522);
esp_err_t rc522_destroy(rc522_handle_t rc522);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,15 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct rc522_driver_handle *rc522_driver_handle_t;
esp_err_t rc522_driver_install(const rc522_driver_handle_t driver);
esp_err_t rc522_driver_uninstall(const rc522_driver_handle_t driver);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,33 @@
#pragma once
#include "rc522_types.h"
#ifdef __cplusplus
extern "C" {
#endif
enum // RC522_PCD_RF_CFG_REG
{
RC522_PCD_RX_GAIN_2_BIT = BIT6,
RC522_PCD_RX_GAIN_1_BIT = BIT5,
RC522_PCD_RX_GAIN_0_BIT = BIT4,
};
/**
* Receiver (antenna) gain
*/
typedef enum
{
RC522_PCD_18_DB_RX_GAIN = (RC522_PCD_RX_GAIN_1_BIT), /* 18 dB */
RC522_PCD_23_DB_RX_GAIN = (RC522_PCD_RX_GAIN_1_BIT | RC522_PCD_RX_GAIN_0_BIT), /* 23 dB */
RC522_PCD_33_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT), /* 33 dB */
RC522_PCD_38_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT | RC522_PCD_RX_GAIN_0_BIT), /* 38 dB */
RC522_PCD_43_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT | RC522_PCD_RX_GAIN_1_BIT), /* 43 dB */
RC522_PCD_48_DB_RX_GAIN = (RC522_PCD_RX_GAIN_2_BIT | RC522_PCD_RX_GAIN_1_BIT | RC522_PCD_RX_GAIN_0_BIT), /* 48 dB */
} rc522_pcd_rx_gain_t;
esp_err_t rc522_pcd_set_rx_gain(const rc522_handle_t rc522, rc522_pcd_rx_gain_t gain);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,170 @@
#pragma once
#include "rc522_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RC522_PICC_UID_SIZE_MAX (10)
#define RC522_PICC_UID_SIZE_MIN (4)
#define RC522_PICC_UID_STR_BUFFER_SIZE_MAX (RC522_PICC_UID_SIZE_MAX * 3)
typedef struct
{
uint8_t value[RC522_PICC_UID_SIZE_MAX];
uint8_t length;
} rc522_picc_uid_t;
typedef enum
{
RC522_PICC_TYPE_UNKNOWN = -1,
RC522_PICC_TYPE_UNDEFINED = 0,
RC522_PICC_TYPE_ISO_14443_4, // PICC compliant with ISO/IEC 14443-4
RC522_PICC_TYPE_ISO_18092, // PICC compliant with ISO/IEC 18092 (NFC)
RC522_PICC_TYPE_MIFARE_MINI, // MIFARE Classic protocol, 320 bytes
RC522_PICC_TYPE_MIFARE_1K, // MIFARE Classic protocol, 1KB
RC522_PICC_TYPE_MIFARE_4K, // MIFARE Classic protocol, 4KB
RC522_PICC_TYPE_MIFARE_UL, // MIFARE Ultralight or Ultralight C
RC522_PICC_TYPE_MIFARE_PLUS, // MIFARE Plus
RC522_PICC_TYPE_MIFARE_DESFIRE, // MIFARE DESFire
RC522_PICC_TYPE_TNP3XXX, // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure
RC522_PICC_TYPE_MIFARE_UL_, // Ultralight MF0ICU1; 16x 4-byte pages
RC522_PICC_TYPE_MIFARE_UL_C, // Ultralight C MF0ICU2; 48x 4-byte pages
RC522_PICC_TYPE_MIFARE_UL_EV1_1, // Ultralight EV1 MF0UL11; 20x 4-byte pages
RC522_PICC_TYPE_MIFARE_UL_EV1_2, // Ultralight EV1 MF0UL21; 41x 4-byte pages
RC522_PICC_TYPE_MIFARE_UL_NANO, // Ultralight NANO; 40 bytes (probably 10x 4-byte pages)
RC522_PICC_TYPE_MIFARE_UL_AES, // Ultralight AES MF0AES(H)20; 60x 4-byte pages
RC522_PICC_TYPE_NTAG2xx, // NTAG 2xx; further information unknown
RC522_PICC_TYPE_NTAG213, // NTAG213; 45x 4-byte pages
RC522_PICC_TYPE_NTAG215, // NTAG215; 135x 4-byte pages
RC522_PICC_TYPE_NTAG216, // NTAG216; 231x 4-byte pages
} rc522_picc_type_t;
typedef enum
{
/**
* Not used! Ignore this one.
*
* In the POWER-OFF State, the PICC is not powered
* due to a lack of carrier energy.
*/
RC522_PICC_STATE_POWER_OFF = -1,
/**
* PICC is maybe in the field.
*
* In the IDLE State, the PICC shall recognize REQA and WUPA Commands.
* The PICC enters the READY State after it has received a valid
* REQA or WUPA Command and transmitted its ATQA.
*/
RC522_PICC_STATE_IDLE = 0,
/**
* In the READY State, either the bit frame anticollision or a proprietary
* anticollision method can be applied. Cascade levels are handled inside
* this state to get the complete UID.
*
* The PICC enters the ACTIVE State when it is selected with its complete UID.
*/
RC522_PICC_STATE_READY,
/**
* PICC is in the field.
*
* In the ACTIVE State, the PICC listens to any higher layer message.
* The PICC enters the HALT State when a valid HLTA Command is received.
*
* @note
* In the higher layer protocol, specific commands may be defined
* to return the PICC to its HALT State.
*/
RC522_PICC_STATE_ACTIVE,
/**
* In the HALT State, the PICC shall respond only to a WUPA Command.
*
* The PICC enters the READY* state after it has received a valid
* WUPA Command and transmitted its ATQA.
*/
RC522_PICC_STATE_HALT,
/**
* READY* State
*
* PICC is woken from HALT state by WUPA command.
*
* The READY* State is similar to the READY State, either the bit frame
* anticollision or a proprietary anticollision method can be applied.
* Cascade levels are handled inside this state to get complete UID.
*
* The PICC enters the ACTIVE* State when it is selected with its complete UID.
*/
RC522_PICC_STATE_READY_H,
/**
* ACTIVE* State
*
* PICC is still in the field. PICC enters this state
* when it is SELECTED in the READY* state.
*
* The ACTIVE* State is similar to the ACTIVE State, the PICC is selected and
* listens to any higher layer message.
*
* The PICC enters the HALT State when a valid HLTA Command is received.
*/
RC522_PICC_STATE_ACTIVE_H,
/**
* AUTHENTICATED State
*
* PICC has been authenticated, via either keys or passwords, and accessing
* protected areas is now possible. This is a Level 3 extension on some NXP
* PICCs.
*/
RC522_PICC_STATE_AUTHENTICATED,
} rc522_picc_state_t;
/**
* All RFU bits shall be set to zero.
*/
typedef struct
{
uint16_t source;
uint8_t rfu4 :4;
uint8_t prop_coding :4;
uint8_t uid_size :2; // 00b - single (4 bytes), 01b - double (7 bytes), 10b - triple (10 bytes)
uint8_t rfu1 :1;
uint8_t anticollision :5;
} rc522_picc_atqa_desc_t;
typedef struct
{
rc522_picc_atqa_desc_t atqa;
rc522_picc_uid_t uid;
uint8_t sak;
rc522_picc_type_t type;
rc522_picc_state_t state;
} rc522_picc_t;
typedef struct
{
rc522_picc_state_t old_state;
rc522_picc_t *picc;
} rc522_picc_state_changed_event_t;
char *rc522_picc_type_name(rc522_picc_type_t type);
/**
* @brief Convert PICC UID to (null-terminated) string
*/
esp_err_t rc522_picc_uid_to_str(const rc522_picc_uid_t *uid, char *buffer, uint8_t buffer_size);
/**
* @brief Print PICC information in a fancy way
*/
esp_err_t rc522_picc_print(const rc522_picc_t *picc);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,52 @@
#pragma once
#include <esp_err.h>
#include <esp_event.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "rc522_driver.h"
#include "rc522_picc.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RC522_X "X"
#define RC522_ERR_BASE (0xF522)
#define RC522_ERR_COLLISION (RC522_ERR_BASE + 1)
#define RC522_ERR_COLLISION_UNSOLVABLE (RC522_ERR_BASE + 2)
#define RC522_ERR_CRC_WRONG (RC522_ERR_BASE + 3)
#define RC522_ERR_INVALID_ATQA (RC522_ERR_BASE + 4)
#define RC522_ERR_RX_TIMEOUT (RC522_ERR_BASE + 5)
#define RC522_ERR_RX_TIMER_TIMEOUT (RC522_ERR_BASE + 6)
#define RC522_ERR_INVALID_SAK (RC522_ERR_BASE + 7)
#define RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH (RC522_ERR_BASE + 8)
#define RC522_ERR_PCD_FIFO_BUFFER_OVERFLOW (RC522_ERR_BASE + 9)
#define RC522_ERR_PCD_PARITY_CHECK_FAILED (RC522_ERR_BASE + 10)
#define RC522_ERR_PCD_PROTOCOL_ERROR (RC522_ERR_BASE + 11)
#define RC522_ERR_RST_PIN_UNUSED (RC522_ERR_BASE + 12)
#define RC522_ERR_PCD_FIFO_EMPTY (RC522_ERR_BASE + 13)
#define RC522_ERR_HLTA_NOT_ACKED (RC522_ERR_BASE + 14)
typedef struct rc522 *rc522_handle_t;
typedef struct
{
rc522_driver_handle_t driver;
uint16_t poll_interval_ms; /*<! Delay (in milliseconds) between polls */
size_t task_stack_size; /*<! Stack size of rc522 task */
uint8_t task_priority; /*<! Priority of rc522 task */
SemaphoreHandle_t task_mutex; /*<! Mutex for rc522 task */
} rc522_config_t;
typedef enum
{
RC522_EVENT_ANY = ESP_EVENT_ANY_ID,
RC522_EVENT_NONE,
RC522_EVENT_PICC_STATE_CHANGED,
} rc522_event_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,42 @@
#include <driver/gpio.h>
#include "rc522_types_internal.h"
#include "rc522_driver.h"
#define RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL (0)
#define RC522_DRIVER_HARD_RST_PULSE_DURATION_MS (15)
typedef esp_err_t (*rc522_driver_install_handler_t)(const rc522_driver_handle_t driver);
typedef esp_err_t (*rc522_driver_send_handler_t)(
const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes);
typedef esp_err_t (*rc522_driver_receive_handler_t)(
const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes);
typedef esp_err_t (*rc522_driver_reset_handler_t)(const rc522_driver_handle_t driver);
typedef esp_err_t (*rc522_driver_uninstall_handler_t)(const rc522_driver_handle_t driver);
struct rc522_driver_handle
{
void *config;
void *device;
void *meta; /* <! Additional (driver specific) data */
rc522_driver_install_handler_t install;
rc522_driver_send_handler_t send;
rc522_driver_receive_handler_t receive;
rc522_driver_reset_handler_t reset;
rc522_driver_uninstall_handler_t uninstall;
};
esp_err_t rc522_driver_init_rst_pin(gpio_num_t rst_io_num);
esp_err_t rc522_driver_create(const void *config, size_t config_size, rc522_driver_handle_t *driver);
esp_err_t rc522_driver_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes);
esp_err_t rc522_driver_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes);
esp_err_t rc522_driver_reset(const rc522_driver_handle_t driver);
esp_err_t rc522_driver_destroy(rc522_driver_handle_t driver);

View File

@@ -0,0 +1,21 @@
#pragma once
#include <esp_err.h>
#include <inttypes.h>
#ifdef __cplusplus
extern "C" {
#endif
uint32_t rc522_millis();
void rc522_delay_ms(uint32_t ms);
esp_err_t rc522_buffer_to_hex_str(
const uint8_t *buffer, uint8_t buffer_length, char *str_buffer, uint8_t str_buffer_length);
esp_err_t rc522_nibbles(uint8_t byte, uint8_t *msb, uint8_t *lsb);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,15 @@
#pragma once
#include "rc522.h"
#ifdef __cplusplus
extern "C" {
#endif
void rc522_task(void *arg);
esp_err_t rc522_dispatch_event(const rc522_handle_t rc522, rc522_event_t event, const void *data, size_t data_size);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,448 @@
#pragma once
#include "rc522_types_internal.h"
#include "rc522_pcd.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RC522_PCD_MOD_WIDTH_REG_RESET_VALUE (38)
#define RC522_PCD_TX_MODE_REG_RESET_VALUE (0x00)
#define RC522_PCD_RX_MODE_REG_RESET_VALUE (0x00)
typedef enum
{
// Starts and stops command execution
RC522_PCD_COMMAND_REG = 0x01,
// Communication Interrupt Enable Register.
// Control bits to enable and disable the passing of interrupt requests
RC522_PCD_COM_INT_EN_REG = 0x02,
// Diverted Interrupt Enable Register.
// Control bits to enable and disable the passing of interrupt requests
RC522_PCD_DIV_INT_EN_REG = 0x03,
// Communication Interrupt request bits
RC522_PCD_COM_INT_REQ_REG = 0x04,
// Diverted Interrupt request bits
RC522_PCD_DIV_INT_REQ_REG = 0x05,
// Error bits showing the error status of the last command executed
RC522_PCD_ERROR_REG = 0x06,
// Contains status bits of the receiver, transmitter and data mode detector
RC522_PCD_STATUS_2_REG = 0x08,
// Input and output of 64 byte FIFO buffer
RC522_PCD_FIFO_DATA_REG = 0x09,
// Number of bytes stored in the FIFO buffer
RC522_PCD_FIFO_LEVEL_REG = 0x0A,
// Shows the MFRC522 software version
RC522_PCD_VERSION_REG = 0x37,
// Controls the logical behavior of the antenna driver pins TX1 and TX2
RC522_PCD_TX_CONTROL_REG = 0x14,
// Configures the receiver gain
RC522_PCD_RF_CFG_REG = 0x26,
// Miscellaneous control register
RC522_PCD_CONTROL_REG = 0x0C,
// Adjustments for bit-oriented frames
RC522_PCD_BIT_FRAMING_REG = 0x0D,
// Defines the first bit-collision detected on the RF interface
RC522_PCD_COLL_REG = 0x0E,
// MSB (higher bits) values of the CRC calculation
RC522_PCD_CRC_RESULT_MSB_REG = 0x21,
// LSB (lower bits) values of the CRC calculation
RC522_PCD_CRC_RESULT_LSB_REG = 0x22,
// Sets the modulation width
RC522_PCD_MOD_WIDTH_REG = 0x24,
// Defines the mode of the timer
RC522_PCD_TIMER_MODE_REG = 0x2A,
// Defines the timer prescaler settings
RC522_PCD_TIMER_PRESCALER_REG = 0x2B,
// MSB (higher bits) value of 16-bit timer reload value
RC522_PCD_TIMER_RELOAD_MSB_REG = 0x2C,
// LSB (lower bits) value of 16-bit timer reload value
RC522_PCD_TIMER_RELOAD_LSB_REG = 0x2D,
// Defines general modes for transmitting and receiving
RC522_PCD_MODE_REG = 0x11,
// Controls the setting of the transmission modulation
RC522_PCD_TX_ASK_REG = 0x15,
// Defines the data rate during transmission
RC522_PCD_TX_MODE_REG = 0x12,
// Defines the data rate during reception
RC522_PCD_RX_MODE_REG = 0x13,
} rc522_pcd_register_t;
enum // RC522_PCD_COLL_REG
{
/**
* all received bits will be cleared after a collision
* only used during bitwise anticollision at 106 kBd,
* otherwise it is set to logic 1
*/
RC522_PCD_VALUES_AFTER_COLL_BIT = BIT7,
/**
* no collision detected or the position of the collision is
* out of the range of CollPos[4:0]
*/
RC522_PCD_COLL_POS_NOT_VALID_BIT = BIT5,
};
enum // RC522_PCD_ERROR_REG
{
/**
* data is written into the FIFO buffer by the host during the MFAuthent
* command or if data is written into the FIFO buffer by the host during the
* time between sending the last bit on the RF interface and receiving the
* last bit on the RF interface
*/
RC522_PCD_WR_ERR_BIT = BIT6,
/**
* internal temperature sensor detects overheating, in which case the
* antenna drivers are automatically switched off
*/
RC522_PCD_TEMP_ERR_BIT = BIT5,
/**
* the host or a MFRC522’s internal state machine (e.g. receiver) tries to
* write data to the FIFO buffer even though it is already full
*/
RC522_PCD_BUFFER_OVFL_BIT = BIT4,
/**
* a bit-collision is detected
* cleared automatically at receiver start-up phase
* only valid during the bitwise anticollision at 106 kBd
* always set to logic 0 during communication protocols at 212 kBd,
* 424 kBd and 848 kBd
*/
RC522_PCD_COLL_ERR_BIT = BIT3,
/**
* the RxModeReg register’s RxCRCEn bit is set and the CRC calculation fails
* automatically cleared to logic 0 during receiver start-up phase
*/
RC522_PCD_CRC_ERR_BIT = BIT2,
/**
* parity check failed
* automatically cleared during receiver start-up phase
* only valid for ISO/IEC 14443 A/MIFARE communication at 106 kBd
*/
RC522_PCD_PARITY_ERR_BIT = BIT1,
/**
* set to logic 1 if the SOF is incorrect
* automatically cleared during receiver start-up phase
* bit is only valid for 106 kBd
* during the MFAuthent command, the ProtocolErr bit is set to logic 1 if the
* number of bytes received in one data stream is incorrect
*/
RC522_PCD_PROTOCOL_ERR_BIT = BIT0,
};
enum // RC522_PCD_COM_INT_REQ_REG
{
/**
* 1 - indicates that the marked bits in the ComIrqReg register are set
* 0 - indicates that the marked bits in the ComIrqReg register are cleared
*/
RC522_PCD_SET_1_BIT = BIT7,
/**
* set immediately after the last bit of the transmitted data was sent out
*/
RC522_PCD_TX_IRQ_BIT = BIT6,
/**
* receiver has detected the end of a valid data stream
* if the RxModeReg register’s RxNoErr bit is set to logic 1, the RxIRq bit is
* only set to logic 1 when data bytes are available in the FIFO
*/
RC522_PCD_RX_IRQ_BIT = BIT5,
/**
* If a command terminates, for example, when the CommandReg changes
* its value from any command to the Idle command (see Table 149 on page 70)
* if an unknown command is started, the CommandReg register
* Command[3:0] value changes to the idle state and the IdleIRq bit is set
* The microcontroller starting the Idle command does not set the IdleIRq bit
*/
RC522_PCD_IDLE_IRQ_BIT = BIT4,
/**
* the Status1Reg register’s HiAlert bit is set
* in opposition to the HiAlert bit, the HiAlertIRq bit stores this event and
* can only be reset as indicated by the Set1 bit in this register
*/
RC522_PCD_HI_ALERT_IRQ_BIT = BIT3,
/**
* Status1Reg register’s LoAlert bit is set
* in opposition to the LoAlert bit, the LoAlertIRq bit stores this event and
* can only be reset as indicated by the Set1 bit in this register
*/
RC522_PCD_LO_ALERT_IRQ_BIT = BIT2,
/**
* any error bit in the ErrorReg register is set
*/
RC522_PCD_ERR_IRQ_BIT = BIT1,
/**
* the timer decrements the timer value in register TCounterValReg to zero
*/
RC522_PCD_TIMER_IRQ_BIT = BIT0,
};
enum // RC522_PCD_COMMAND_REG
{
// Soft power-down mode entered
RC522_PCD_POWER_DOWN_BIT = BIT4,
};
enum // RC522_PCD_TX_CONTROL_REG
{
// Output signal on pin TX2 delivers the 13.56 MHz energy carrier modulated by the transmission data
RC522_PCD_TX2_RF_EN_BIT = BIT1,
// Output signal on pin TX1 delivers the 13.56 MHz energy carrier modulated by the transmission data
RC522_PCD_TX1_RF_EN_BIT = BIT0,
};
typedef enum
{
/**
* Places the MFRC522 in Idle mode. The Idle command also terminates itself.
*/
RC522_PCD_IDLE_CMD = 0b0000,
/**
* The FIFO buffer content is transferred to the CRC coprocessor and the CRC calculation is
* started. The calculation result is stored in the CRCResultReg register. The CRC
* calculation is not limited to a dedicated number of bytes. The calculation is not stopped
* when the FIFO buffer is empty during the data stream. The next byte written to the FIFO
* buffer is added to the calculation.
*
* The CRC preset value is controlled by the ModeReg register’s CRCPreset[1:0] bits. The
* value is loaded in to the CRC coprocessor when the command starts.
*
* This command must be terminated by writing a command to the CommandReg register,
* such as, the Idle command.
*
* If the AutoTestReg register’s SelfTest[3:0] bits are set correctly, the MFRC522 enters Self
* Test mode. Starting the CalcCRC command initiates a digital self test. The result of the
* self test is written to the FIFO buffer.
*/
RC522_PCD_CALC_CRC_CMD = 0b0011,
/**
* This command continuously repeats the transmission of data from the FIFO buffer and the
* reception of data from the RF field. The first action is transmit and after transmission the
* command is changed to receive a data stream.
*
* Each transmit process must be started by setting the BitFramingReg register’s StartSend
* bit to logic 1. This command must be cleared by writing any command to the
* CommandReg register
*/
RC522_PCD_TRANSCEIVE_CMD = 0b1100,
/**
* This command manages MIFARE authentication to enable a secure communication to
* any MIFARE Mini, MIFARE 1K and MIFARE 4K card
*/
RC522_PCD_MF_AUTH_CMD = 0b1110,
/**
* This command performs a reset of the device. The configuration data of the internal buffer
* remains unchanged. All registers are set to the reset values. This command automatically
* terminates when finished.
*/
RC522_PCD_SOFT_RESET_CMD = 0b1111,
} rc522_pcd_command_t;
enum // RC522_PCD_TIMER_MODE_REG
{
/**
* When 1:
* - Timer starts automatically at the end of the transmission in
* all communication modes at all speeds
* - If the RxModeReg register’s RxMultiple bit is not set, the
* timer stops immediately after receiving the 5th bit (1 start bit,
* 4 data bits)
* - If the RxMultiple bit is set to logic 1 the timer never stops, in
* which case the timer can be stopped by setting the
* ControlReg register’s TStopNow bit to logic 1
*
* When 0:
* - Indicates that the timer is not influenced by the protocol
*/
RC522_PCD_T_AUTO_BIT = BIT7,
};
enum // RC522_PCD_TX_ASK_REG
{
/**
* Forces a 100 % ASK modulation independent of the ModGsPReg register setting
*/
RC522_PCD_FORCE_100_ASK_BIT = BIT6,
};
enum // RC522_PCD_MODE_REG
{
// Transmitter can only be started if an RF field is generated
RC522_PCD_TX_WAIT_RF_BIT = BIT5,
/**
* Defines the polarity of pin MFIN
*
* 1 - Polarity of pin MFIN is active HIGH
* 0 - Polarity of pin MFIN is active LOW
*/
RC522_PCD_POL_MFIN_BIT = BIT3,
RC522_PCD_CRC_PRESET_1_BIT = BIT1,
RC522_PCD_CRC_PRESET_0_BIT = BIT0,
};
typedef enum
{
RC522_PCD_CRC_PRESET_0000H = (0x00), /* 0000h */
RC522_PCD_CRC_PRESET_6363H = (RC522_PCD_CRC_PRESET_0_BIT), /* 6363h */
RC522_PCD_CRC_PRESET_A671H = (RC522_PCD_CRC_PRESET_1_BIT), /* A671h */
RC522_PCD_CRC_PRESET_FFFFH = (RC522_PCD_CRC_PRESET_1_BIT | RC522_PCD_CRC_PRESET_0_BIT), /* FFFFh */
} rc522_pcd_crc_preset_value_t;
enum // RC522_PCD_DIV_INT_REQ_REG
{
// The CalcCRC command is active and all data is processed (CRC calculation is done)
RC522_PCD_CRC_IRQ_BIT = BIT2,
};
enum // RC522_PCD_FIFO_LEVEL_REG
{
/**
* Immediately clears the internal FIFO buffer’s read and write pointer
* and ErrorReg register’s BufferOvfl bit
*
* Reading this bit always returns 0
*/
RC522_PCD_FLUSH_BUFFER_BIT = BIT7,
};
enum // RC522_PCD_BIT_FRAMING_REG
{
/**
* Starts the transmission of data
* Only valid in combination with the Transceive command
*/
RC522_PCD_START_SEND_BIT = BIT7,
};
enum // RC522_PCD_STATUS_2_REG
{
/**
* Indicates that the MIFARE Crypto1 unit is switched on and
* therefore all data communication with the card is encrypted
* can only be set to logic 1 by a successful execution of the
* MFAuthent command.
* Only valid in Read/Write mode for MIFARE standard cards.
* This bit is cleared by software.
*/
RC522_PCD_MF_CRYPTO1_ON_BIT = BIT3,
};
enum // RC522_PCD_RX_MODE_REG
{
/**
* An invalid received data stream (less than 4 bits received) will
* be ignored and the receiver remains active
*/
RC522_PCD_RX_NO_ERR_BIT = BIT3,
};
typedef enum
{
RC522_PCD_FIRMWARE_CLONE = 0x88, // clone
RC522_PCD_FIRMWARE_00 = 0x90, // v0.0
RC522_PCD_FIRMWARE_10 = 0x91, // v1.0
RC522_PCD_FIRMWARE_20 = 0x92, // v2.0
RC522_PCD_FIRMWARE_COUNTERFEIT = 0x12, // counterfeit chip
} rc522_pcd_firmware_t;
typedef union
{
uint16_t value;
struct
{
uint8_t lsb;
uint8_t msb;
};
} rc522_pcd_crc_t;
esp_err_t rc522_pcd_reset(const rc522_handle_t rc522, uint32_t timeout_ms);
esp_err_t rc522_pcd_calculate_crc(const rc522_handle_t rc522, const rc522_bytes_t *bytes, rc522_pcd_crc_t *result);
esp_err_t rc522_pcd_init(const rc522_handle_t rc522);
esp_err_t rc522_pcd_firmware(const rc522_handle_t rc522, rc522_pcd_firmware_t *result);
char *rc522_pcd_firmware_name(rc522_pcd_firmware_t firmware);
esp_err_t rc522_pcd_stop_active_command(const rc522_handle_t rc522);
esp_err_t rc522_pcd_clear_all_com_interrupts(const rc522_handle_t rc522);
esp_err_t rc522_pcd_fifo_write(const rc522_handle_t rc522, const rc522_bytes_t *bytes);
esp_err_t rc522_pcd_fifo_read(const rc522_handle_t rc522, rc522_bytes_t *bytes);
esp_err_t rc522_pcd_fifo_flush(const rc522_handle_t rc522);
esp_err_t rc522_pcd_start_data_transmission(const rc522_handle_t rc522);
esp_err_t rc522_pcd_stop_data_transmission(const rc522_handle_t rc522);
esp_err_t rc522_pcd_stop_crypto1(const rc522_handle_t rc522);
esp_err_t rc522_pcd_rw_test(const rc522_handle_t rc522);
esp_err_t rc522_pcd_write_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, const rc522_bytes_t *bytes);
esp_err_t rc522_pcd_write(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t val);
esp_err_t rc522_pcd_read_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, rc522_bytes_t *bytes);
esp_err_t rc522_pcd_read(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t *value_ref);
esp_err_t rc522_pcd_set_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits);
esp_err_t rc522_pcd_clear_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,115 @@
#pragma once
#include "rc522_pcd_internal.h"
#include "rc522_picc.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Commands sent to the PICC
*/
typedef enum
{
/**
* The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)
* REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for
* anticollision or selection. 7 bit frame.
*/
RC522_PICC_CMD_REQA = 0x26,
/**
* Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare
* for anticollision or selection. 7 bit frame.
*/
RC522_PICC_CMD_WUPA = 0x52,
/**
* Cascade Tag. Not really a command, but used during anti collision.
* TODO: move it from here in that case
*/
RC522_PICC_CMD_CT = 0x88,
/**
* Anti collision/Select, Cascade Level 1
*/
RC522_PICC_CMD_SEL_CL1 = 0x93,
/**
* Anti collision/Select, Cascade Level 2
*/
RC522_PICC_CMD_SEL_CL2 = 0x95,
/**
* Anti collision/Select, Cascade Level 3
*/
RC522_PICC_CMD_SEL_CL3 = 0x97,
/**
* HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
*/
RC522_PICC_CMD_HLTA = 0x50,
/**
* Request command for Answer To Reset.
*
*/
RC522_PICC_CMD_RATS = 0xE0,
/**
* AUTHenticate command for Ultralight C & AES.
*
*/
RC522_PICC_CMD_UL_AUTH = 0x1A,
/**
* GET Version level 3 command for Ultralight EV1 & NANO, and NTAGs.
*/
RC522_PICC_CMD_GETV = 0x60,
} rc522_picc_command_t;
typedef struct
{
rc522_pcd_command_t pcd_command;
rc522_bytes_t bytes;
uint8_t expected_interrupts;
uint8_t rx_align;
uint8_t valid_bits;
bool check_crc;
} rc522_picc_transaction_t;
typedef struct rc522_picc_transaction_context rc522_picc_transaction_context_t;
typedef struct
{
rc522_bytes_t bytes;
uint8_t valid_bits;
} rc522_picc_transaction_result_t;
esp_err_t rc522_picc_send(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
rc522_picc_transaction_context_t *out_context);
esp_err_t rc522_picc_transceive(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
rc522_picc_transaction_result_t *out_result);
esp_err_t rc522_picc_reqa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa);
esp_err_t rc522_picc_wupa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa);
esp_err_t rc522_picc_select(
const rc522_handle_t rc522, rc522_picc_uid_t *out_uid, uint8_t *out_sak, bool skip_anticoll);
esp_err_t rc522_picc_halta(const rc522_handle_t rc522, rc522_picc_t *picc);
esp_err_t rc522_picc_heartbeat(
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_uid_t *out_uid, uint8_t *out_sak);
rc522_picc_type_t rc522_picc_get_type(const rc522_picc_t *picc);
esp_err_t rc522_picc_set_state(
const rc522_handle_t rc522, rc522_picc_t *picc, rc522_picc_state_t new_state, bool fire_event);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,97 @@
#pragma once
#include <esp_log.h>
#include <esp_check.h>
#include <esp_bit_defs.h>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include "rc522_types.h"
#include "rc522_picc.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RC522_LOG_TAG "rc522"
#define RC522_POLL_INTERVAL_MS_DEFAULT (120)
#define RC522_POLL_INTERVAL_MS_MIN (50)
#define RC522_TASK_STACK_SIZE_DEFAULT (4 * 1024)
#define RC522_TASK_PRIORITY_DEFAULT (3)
#define RC522_TASK_STOPPED_BIT (BIT0)
#define RC522_LOG_LEVEL LOG_LOCAL_LEVEL
typedef enum
{
RC522_STATE_UNDEFINED = 0,
RC522_STATE_CREATED,
RC522_STATE_POLLING, /*<! Scanning for nearby PICCs */
RC522_STATE_PAUSED,
} rc522_state_t;
struct rc522
{
rc522_config_t *config; /*<! Configuration */
bool exit_requested; /*<! Indicates whether polling task exit is requested */
TaskHandle_t task_handle; /*<! Handle of task */
esp_event_loop_handle_t event_handle; /*<! Handle of event loop */
rc522_state_t state; /*<! Current state */
rc522_picc_t picc;
EventGroupHandle_t bits;
};
typedef struct
{
uint8_t *ptr;
uint8_t length;
} rc522_bytes_t;
#define RC522_LOG_DEFINE_BASE() static const char *TAG = RC522_LOG_TAG
#define RC522_LOG(esp_log_foo, format, ...) esp_log_foo(TAG, "%s(%d): " format, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define RC522_LOGE(format, ...) RC522_LOG(ESP_LOGE, format, ##__VA_ARGS__)
#define RC522_LOGW(format, ...) RC522_LOG(ESP_LOGW, format, ##__VA_ARGS__)
#define RC522_LOGI(format, ...) RC522_LOG(ESP_LOGI, format, ##__VA_ARGS__)
#define RC522_LOGD(format, ...) RC522_LOG(ESP_LOGD, format, ##__VA_ARGS__)
#define RC522_LOGV(format, ...) RC522_LOG(ESP_LOGV, format, ##__VA_ARGS__)
#define RC522_LOG_WRITE(format, ...) esp_log_write(ESP_LOG_INFO, TAG, format, ##__VA_ARGS__)
#define RC522_RETURN_ON_ERROR(x) \
do { \
esp_err_t err_rc_ = (x); \
if (unlikely(err_rc_ != ESP_OK)) { \
ESP_LOGE(TAG, "%s(%d): %04" RC522_X "", __FUNCTION__, __LINE__, err_rc_); \
return err_rc_; \
} \
} \
while (0)
#define RC522_LOG_ON_ERROR(x) \
do { \
esp_err_t err_rc_ = (x); \
if (unlikely(err_rc_ != ESP_OK)) { \
ESP_LOGE(TAG, "%s(%d): %04" RC522_X "", __FUNCTION__, __LINE__, err_rc_); \
} \
} \
while (0)
#define RC522_RETURN_ON_FALSE(a, err_code) ESP_RETURN_ON_FALSE(a, err_code, TAG, "")
#define RC522_CHECK_WITH_MESSAGE(a, message) ESP_RETURN_ON_FALSE(!(a), ESP_ERR_INVALID_ARG, TAG, message)
#define RC522_CHECK_AND_RETURN(a, ret_val) ESP_RETURN_ON_FALSE(!(a), ret_val, TAG, #a)
#define RC522_CHECK(a) ESP_RETURN_ON_FALSE(!(a), ESP_ERR_INVALID_ARG, TAG, #a)
#define RC522_CHECK_BYTES(b) RC522_CHECK(b == NULL || (b)->ptr == NULL || (b)->length < 1)
#define RC522_RETURN_ON_ERROR_SILENTLY(x) \
do { \
esp_err_t err_rc_ = (x); \
if (unlikely(err_rc_ != ESP_OK)) { \
return err_rc_; \
} \
} \
while (0)
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,114 @@
#include <string.h>
#include "rc522_helpers_internal.h"
#include "rc522_types_internal.h"
#include "rc522_driver_internal.h"
#include "driver/rc522_i2c.h"
#define RC522_I2C_RW_TIMEOUT_MS 1000
RC522_LOG_DEFINE_BASE();
static esp_err_t rc522_i2c_install(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
rc522_i2c_config_t *conf = (rc522_i2c_config_t *)(driver->config);
if (!conf->bus) {
RC522_CHECK(conf->bus_config == NULL);
RC522_RETURN_ON_ERROR(i2c_new_master_bus(conf->bus_config, &conf->bus));
}
RC522_RETURN_ON_ERROR(
i2c_master_bus_add_device(conf->bus, &conf->dev_config, (i2c_master_dev_handle_t *)(&driver->device)));
if (conf->rst_io_num > GPIO_NUM_NC) {
RC522_RETURN_ON_ERROR(rc522_driver_init_rst_pin(conf->rst_io_num));
}
return ESP_OK;
}
static esp_err_t rc522_i2c_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
RC522_CHECK_BYTES(bytes);
// FIXME: Find a way to send [address + buffer]
// without need for second buffer
uint8_t buffer2[64];
buffer2[0] = address;
memcpy(buffer2 + 1, bytes->ptr, bytes->length);
RC522_RETURN_ON_ERROR(i2c_master_transmit((i2c_master_dev_handle_t)driver->device,
buffer2,
bytes->length + 1,
RC522_I2C_RW_TIMEOUT_MS));
return ESP_OK;
}
static esp_err_t rc522_i2c_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
RC522_CHECK_BYTES(bytes);
ESP_ERROR_CHECK(i2c_master_transmit_receive((i2c_master_dev_handle_t)driver->device,
&address,
1,
bytes->ptr,
bytes->length,
RC522_I2C_RW_TIMEOUT_MS));
return ESP_OK;
}
static esp_err_t rc522_i2c_reset(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
rc522_i2c_config_t *conf = (rc522_i2c_config_t *)(driver->config);
if (conf->rst_io_num < 0) {
return RC522_ERR_RST_PIN_UNUSED;
}
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
return ESP_OK;
}
static esp_err_t rc522_i2c_uninstall(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
RC522_RETURN_ON_ERROR(i2c_master_bus_rm_device((i2c_master_dev_handle_t)driver->device));
// TODO: remove bus if handle was NULL?
return ESP_OK;
}
esp_err_t rc522_i2c_create(const rc522_i2c_config_t *config, rc522_driver_handle_t *driver)
{
RC522_CHECK(config == NULL);
RC522_CHECK(driver == NULL);
RC522_RETURN_ON_ERROR(rc522_driver_create(config, sizeof(rc522_i2c_config_t), driver));
(*driver)->install = rc522_i2c_install;
(*driver)->send = rc522_i2c_send;
(*driver)->receive = rc522_i2c_receive;
(*driver)->reset = rc522_i2c_reset;
(*driver)->uninstall = rc522_i2c_uninstall;
return ESP_OK;
}

View File

@@ -0,0 +1,216 @@
#include <string.h>
#include "rc522_helpers_internal.h"
#include "rc522_types_internal.h"
#include "rc522_driver_internal.h"
#include "driver/rc522_spi.h"
RC522_LOG_DEFINE_BASE();
typedef struct
{
gpio_num_t cs_io_num;
} rc522_spi_meta_t;
static void rc522_spi_transaction_pre_cb(spi_transaction_t *trans);
static void rc522_spi_transaction_post_cb(spi_transaction_t *trans);
static esp_err_t rc522_spi_uninstall(const rc522_driver_handle_t driver);
static esp_err_t rc522_spi_install(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
esp_err_t ret = ESP_OK;
rc522_spi_meta_t *meta = calloc(1, sizeof(rc522_spi_meta_t));
ESP_GOTO_ON_FALSE(meta != NULL, ESP_ERR_NO_MEM, error, TAG, "nomem");
driver->meta = (void *)meta;
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
if (conf->bus_config) {
// overwrite bus config
conf->bus_config->quadwp_io_num = GPIO_NUM_NC;
conf->bus_config->quadhd_io_num = GPIO_NUM_NC;
ESP_GOTO_ON_ERROR(spi_bus_initialize(conf->host_id, conf->bus_config, conf->dma_chan),
error,
TAG,
"spi bus initialization failed");
}
else {
RC522_LOGD("skips spi bus initialization");
}
// {{ overwrite device config
if (conf->dev_config.clock_speed_hz == 0) {
conf->dev_config.clock_speed_hz = 5000000;
}
conf->dev_config.mode = 0;
if (conf->dev_config.queue_size == 0) {
conf->dev_config.queue_size = 7;
}
if (conf->dev_config.flags == 0) {
conf->dev_config.flags = SPI_DEVICE_HALFDUPLEX;
}
conf->dev_config.command_bits = 8;
conf->dev_config.address_bits = 0;
conf->dev_config.dummy_bits = 0;
// }}
// ESP32 SPI bus has limitation of 3 CS lines, so we need to use
// software control for CS line in order to use more devices.
{
meta->cs_io_num = conf->dev_config.spics_io_num;
conf->dev_config.spics_io_num = GPIO_NUM_NC;
gpio_config_t cs_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << meta->cs_io_num),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
ESP_GOTO_ON_ERROR(gpio_config(&cs_conf), error, TAG, "cs gpio config failed");
ESP_GOTO_ON_ERROR(gpio_set_level(meta->cs_io_num, 1), error, TAG, "cs set level failed");
conf->dev_config.pre_cb = &rc522_spi_transaction_pre_cb;
conf->dev_config.post_cb = &rc522_spi_transaction_post_cb;
}
ESP_GOTO_ON_ERROR(spi_bus_add_device(conf->host_id, &conf->dev_config, (spi_device_handle_t *)(&driver->device)),
error,
TAG,
"spi bus add device failed");
if (conf->rst_io_num > GPIO_NUM_NC) {
ESP_GOTO_ON_ERROR(rc522_driver_init_rst_pin(conf->rst_io_num), error, TAG, "init rst pin failed");
}
goto exit;
error:
RC522_LOG_ON_ERROR(rc522_spi_uninstall(driver));
exit:
return ret;
}
static esp_err_t rc522_spi_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->device == NULL);
RC522_CHECK_BYTES(bytes);
uint8_t cmd_byte = (address << 1) & 0x7E;
esp_err_t ret = spi_device_polling_transmit((spi_device_handle_t)(driver->device),
&(spi_transaction_t) {
.cmd = cmd_byte,
.length = 8 * bytes->length,
.tx_buffer = bytes->ptr,
.user = driver,
});
return ret;
}
static esp_err_t rc522_spi_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->device == NULL);
RC522_CHECK_BYTES(bytes);
uint8_t cmd_byte = ((address << 1) & 0x7E) | 0x80;
for (uint8_t i = 0; i < bytes->length; i++) {
RC522_RETURN_ON_ERROR(spi_device_polling_transmit((spi_device_handle_t)(driver->device),
&(spi_transaction_t) {
.cmd = cmd_byte,
.rxlength = 8,
.rx_buffer = (bytes->ptr + i),
.user = driver,
}));
}
return ESP_OK;
}
static esp_err_t rc522_spi_reset(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
RC522_CHECK(driver->config == NULL);
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
if (conf->rst_io_num < 0) {
return RC522_ERR_RST_PIN_UNUSED;
}
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
RC522_RETURN_ON_ERROR(gpio_set_level(conf->rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
rc522_delay_ms(RC522_DRIVER_HARD_RST_PULSE_DURATION_MS);
return ESP_OK;
}
static esp_err_t rc522_spi_uninstall(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
if (driver->meta) {
free(driver->meta);
driver->meta = NULL;
}
if (driver->device) {
RC522_LOG_ON_ERROR(spi_bus_remove_device((spi_device_handle_t)(driver->device)));
driver->device = NULL;
}
if (driver->config) {
rc522_spi_config_t *conf = (rc522_spi_config_t *)(driver->config);
if (conf->bus_config) {
RC522_LOG_ON_ERROR(spi_bus_free(conf->host_id));
}
}
return ESP_OK;
}
esp_err_t rc522_spi_create(const rc522_spi_config_t *config, rc522_driver_handle_t *driver)
{
RC522_CHECK(config == NULL);
RC522_CHECK(driver == NULL);
RC522_RETURN_ON_ERROR(rc522_driver_create(config, sizeof(rc522_spi_config_t), driver));
(*driver)->install = rc522_spi_install;
(*driver)->send = rc522_spi_send;
(*driver)->receive = rc522_spi_receive;
(*driver)->reset = rc522_spi_reset;
(*driver)->uninstall = rc522_spi_uninstall;
return ESP_OK;
}
static void rc522_spi_transaction_pre_cb(spi_transaction_t *trans)
{
rc522_driver_handle_t driver = (rc522_driver_handle_t)trans->user;
rc522_spi_meta_t *meta = (rc522_spi_meta_t *)(driver->meta);
RC522_LOG_ON_ERROR(gpio_set_level(meta->cs_io_num, 0));
}
static void rc522_spi_transaction_post_cb(spi_transaction_t *trans)
{
rc522_driver_handle_t driver = (rc522_driver_handle_t)trans->user;
rc522_spi_meta_t *meta = (rc522_spi_meta_t *)(driver->meta);
RC522_LOG_ON_ERROR(gpio_set_level(meta->cs_io_num, 1));
}

View File

@@ -0,0 +1,604 @@
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* # Memory access management via access bits
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* The access conditions for every data block and sector trailer are defined by 3 bits, which
* are stored non-inverted and inverted in the sector trailer of the specified sector.
*
* The access bits control the rights of memory access using the secret keys A and B. The
* access conditions may be altered, provided one knows the relevant key and the current
* access condition allows this operation.
*
* @warning
* With each memory access the internal logic verifies the format of the access
* conditions. If it detects a format violation the whole sector is irreversibly blocked.
*
* +-----------------------------------------------------------------------------------+
* | Sector Trailer |
* +=============+===+===+===+===+===+===+===+===+===+===+====+====+====+====+====+====+
* | Byte number | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
* +-------------+---+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+
* | Description | Key A | Access Bits | Key B (optional) |
* +-------------+-----------------------+---------------+-----------------------------+
*
* Bytes 6, 7 and 8 of sector trailer block are bytes that holds access bits.
* Next table shows descriptions for each bit of those bytes.
*
* +-----+------+------+------+------+------+------+------+------+
* | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
* +-----+------+------+------+------+------+------+------+------+
* | [6] | ~C23 | ~C22 | ~C21 | ~C20 | ~C13 | ~C12 | ~C11 | ~C10 |
* +-----+------+------+------+------+------+------+------+------+
* | [7] | C13 | C12 | C11 | C10 | ~C33 | ~C32 | ~C31 | ~C30 |
* +-----+------+------+------+------+------+------+------+------+
* | [8] | C33 | C32 | C31 | C30 | C23 | C22 | C21 | C20 |
* +-----+------+------+------+------+------+------+------+------+
*
* Where Cxy is access bit Cx for the block (group) y, for example:
* - C10 is the access bit C1 for the block (group) 0,
* - C20 is the access bit C2 for the block (group) 0,
* - ...,
* - C33 is the access bit C3 for the sector trailer,
* - ~C10 is the inverted bit C10.
*/
#include <string.h>
#include "rc522_types_internal.h"
#include "rc522_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "picc/rc522_mifare.h"
RC522_LOG_DEFINE_BASE();
/**
* The commands used for MIFARE Classic
*/
enum
{
/**
* Perform authentication with Key A
*/
RC522_MIFARE_AUTH_KEY_A_CMD = 0x60,
/**
* Perform authentication with Key B
*/
RC522_MIFARE_AUTH_KEY_B_CMD = 0x61,
/**
* Reads one 16 byte block from the authenticated sector of the PICC
*/
RC522_MIFARE_READ_CMD = 0x30,
/**
* Writes one 16 byte block to the authenticated sector of the PICC
*/
RC522_MIFARE_WRITE_CMD = 0xA0,
/**
* Decrements the contents of a block and stores the result in the internal data register
*/
RC522_MIFARE_DECREMENT_CMD = 0xC0,
/**
* Increments the contents of a block and stores the result in the internal data register
*/
RC522_MIFARE_INCREMENT_CMD = 0xC1,
/**
* Reads the contents of a block into the internal data register
*/
RC522_MIFARE_RESTORE_CMD = 0xC2,
/**
* Writes the contents of the internal data register to a block
*/
RC522_MIFARE_TRANSFER_CMD = 0xB0,
};
/**
* Checks if PICC type is compatible with MIFARE Classic protocol
*/
bool rc522_mifare_type_is_classic_compatible(rc522_picc_type_t type)
{
return type == RC522_PICC_TYPE_MIFARE_MINI || type == RC522_PICC_TYPE_MIFARE_1K
|| type == RC522_PICC_TYPE_MIFARE_4K;
}
inline esp_err_t rc522_mifare_auth_sector(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_key_t *key)
{
RC522_CHECK(sector_desc == NULL);
RC522_RETURN_ON_ERROR(rc522_mifare_auth(rc522, picc, sector_desc->block_0_address, key));
return ESP_OK;
}
esp_err_t rc522_mifare_auth(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address, const rc522_mifare_key_t *key)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(key == NULL);
// TODO: Validate block_address
RC522_LOGD("MIFARE AUTH (block_address=%02" RC522_X ")", block_address);
uint8_t send_data[12];
switch (key->type) {
case RC522_MIFARE_KEY_A:
send_data[0] = RC522_MIFARE_AUTH_KEY_A_CMD;
break;
case RC522_MIFARE_KEY_B:
send_data[0] = RC522_MIFARE_AUTH_KEY_B_CMD;
break;
default:
RC522_LOGE("Invalid key type");
return ESP_ERR_INVALID_ARG;
}
send_data[1] = block_address;
memcpy(send_data + 2, key->value, RC522_MIFARE_KEY_SIZE);
// Use the last uid bytes
// section 3.2.5 "MIFARE Classic Authentication".
// The only missed case is the MF1Sxxxx shortcut activation,
// but it requires cascade tag (CT) byte, that is not part of uid.
memcpy(send_data + 8, picc->uid.value + picc->uid.length - 4, 4);
// Start the authentication.
rc522_picc_transaction_t transaction = {
.pcd_command = RC522_PCD_MF_AUTH_CMD,
.expected_interrupts = RC522_PCD_IDLE_IRQ_BIT,
.bytes = { .ptr = send_data, .length = sizeof(send_data) },
};
esp_err_t ret = rc522_picc_send(rc522, &transaction, NULL);
// 10.3.1.9
//
// "If an error occurs during authentication,
// the ErrorReg register’s ProtocolErr bit is set to logic 1
// and the Status2Reg register’s Crypto1On bit is set to logic 0"
// Timer interrupt fires before ProtocolErr bit is set to 1
// so we will only check for Crypto1On bit (even if ret == ESP_OK)
uint8_t status2;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_STATUS_2_REG, &status2));
if (!(status2 & RC522_PCD_MF_CRYPTO1_ON_BIT)) {
return RC522_ERR_MIFARE_AUTHENTICATION_FAILED;
}
return ret;
}
esp_err_t rc522_mifare_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
uint8_t out_buffer[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_buffer == NULL);
RC522_LOGD("MIFARE READ (block_address=%02" RC522_X ")", block_address);
uint8_t cmd_buffer[4] = { 0 };
// Build command buffer
cmd_buffer[0] = RC522_MIFARE_READ_CMD;
cmd_buffer[1] = block_address;
// Calculate CRC_A
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = cmd_buffer, .length = 2 }, &crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
uint8_t block_buffer[RC522_MIFARE_BLOCK_SIZE + 2] = { 0 }; // +2 for CRC_A
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
.check_crc = true,
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = block_buffer, .length = sizeof(block_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_CHECK_AND_RETURN((result.bytes.length - 2) != RC522_MIFARE_BLOCK_SIZE, ESP_FAIL);
memcpy(out_buffer, block_buffer, sizeof(block_buffer) - 2); // -2 cuz of CRC_A
return ESP_OK;
}
static esp_err_t rc522_mifare_send(const rc522_handle_t rc522, const uint8_t *send_data, uint8_t send_length)
{
RC522_CHECK(send_data == NULL);
RC522_CHECK(send_length > RC522_MIFARE_BLOCK_SIZE);
#pragma GCC diagnostic ignored "-Wcast-qual"
uint8_t *sdata = (uint8_t *)send_data;
#pragma GCC diagnostic pop
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = sdata, .length = send_length }, &crc));
uint8_t buffer[RC522_MIFARE_BLOCK_SIZE + 2]; // +2 for CRC_A
memcpy(buffer, send_data, send_length);
buffer[send_length] = crc.lsb;
buffer[send_length + 1] = crc.msb;
send_length += 2;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = send_length },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
// The PICC must reply with a 4 bit ACK
if (result.bytes.length != 1 || result.valid_bits != 4) {
return ESP_FAIL; // TODO: use custom err
}
if (result.bytes.ptr[0] != RC522_MIFARE_ACK) {
return RC522_ERR_MIFARE_NACK;
}
return ESP_OK;
}
esp_err_t rc522_mifare_get_number_of_sectors(rc522_picc_type_t type, uint8_t *out_result)
{
RC522_CHECK(rc522_mifare_type_is_classic_compatible(type) == false);
RC522_CHECK(out_result == NULL);
switch (type) {
case RC522_PICC_TYPE_MIFARE_MINI:
// Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes.
*out_result = 5;
return ESP_OK;
case RC522_PICC_TYPE_MIFARE_1K:
// Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes.
*out_result = 16;
return ESP_OK;
case RC522_PICC_TYPE_MIFARE_4K:
// Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes.
*out_result = 40;
return ESP_OK;
default:
return ESP_FAIL;
}
}
inline uint8_t rc522_mifare_get_sector_index_by_block_address(uint8_t block_address)
{
if (block_address < 128) {
return block_address / 4;
}
else {
return 32 + ((block_address - 128) / 16);
}
}
inline esp_err_t rc522_mifare_get_number_of_blocks_in_sector(uint8_t sector_index, uint8_t *out_result)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_result == NULL);
if (sector_index < 32) {
*out_result = 4;
return ESP_OK;
}
*out_result = 16;
return ESP_OK;
}
inline esp_err_t rc522_mifare_get_sector_block_0_address(uint8_t sector_index, uint8_t *out_result)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_result == NULL);
if (sector_index < 32) {
*out_result = sector_index * 4;
return ESP_OK;
}
*out_result = 128 + (sector_index - 32) * 16;
return ESP_OK;
}
esp_err_t rc522_mifare_get_sector_desc(uint8_t sector_index, rc522_mifare_sector_desc_t *out_sector_desc)
{
RC522_CHECK(sector_index > RC522_MIFARE_SECTOR_INDEX_MAX);
RC522_CHECK(out_sector_desc == NULL);
rc522_mifare_sector_desc_t desc;
memset(&desc, 0, sizeof(desc));
desc.index = sector_index;
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_blocks_in_sector(sector_index, &desc.number_of_blocks));
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_0_address(sector_index, &desc.block_0_address));
memcpy(out_sector_desc, &desc, sizeof(rc522_mifare_sector_desc_t));
return ESP_OK;
}
static esp_err_t rc522_mifare_block_at_address_is_sector_trailer(uint8_t block_address, bool *result)
{
RC522_CHECK(result == NULL);
uint8_t sector_index = rc522_mifare_get_sector_index_by_block_address(block_address);
rc522_mifare_sector_desc_t sector = { 0 };
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_desc(sector_index, &sector));
*result = (block_address == (sector.block_0_address + sector.number_of_blocks - 1));
return ESP_OK;
}
static esp_err_t rc522_mifare_verify_access_bits_integrity(const uint8_t trailer_bytes[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(trailer_bytes == NULL);
uint8_t c1 = 0, c2 = 0, c3 = 0;
uint8_t c1_ = 0, c2_ = 0, c3_ = 0;
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[6], &c2_, &c1_));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, &c3_));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
if ((c1 != (~c1_ & 0x0F)) || (c2 != (~c2_ & 0x0F)) || (c3 != (~c3_ & 0x0F))) {
return RC522_ERR_MIFARE_ACCESS_BITS_INTEGRITY_VIOLATION;
}
return ESP_OK;
}
esp_err_t rc522_mifare_write(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t block_address,
const uint8_t buffer[RC522_MIFARE_BLOCK_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(!rc522_mifare_type_is_classic_compatible(picc->type));
bool is_trailer = false;
RC522_RETURN_ON_ERROR(rc522_mifare_block_at_address_is_sector_trailer(block_address, &is_trailer));
if (is_trailer) {
#ifdef CONFIG_RC522_PREVENT_SECTOR_TRAILER_WRITE
ESP_LOGE(TAG, "");
ESP_LOGE(TAG,
"The block at address %d that you are trying to update is a Sector Trailer block.",
block_address);
ESP_LOGE(TAG, "");
ESP_LOGE(TAG, "Writing to Sector Trailer blocks is prevented by default in the component configuration");
ESP_LOGE(TAG, "to protect inexperienced users from accidentally overwriting keys");
ESP_LOGE(TAG, "or writing incorrect access bits, which could make the sector unusable.");
ESP_LOGE(TAG, "");
ESP_LOGE(TAG, "If you know what you are doing, please use menuconfig to disable this protection.");
ESP_LOGE(TAG, "");
return RC522_ERR_MIFARE_SECTOR_TRAILER_WRITE_NOT_ALLOWED;
#endif
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(buffer));
}
RC522_LOGD("MIFARE WRITE (block_address=%02" RC522_X ")", block_address);
uint8_t cmd_buffer[] = { RC522_MIFARE_WRITE_CMD, block_address };
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, cmd_buffer, sizeof(cmd_buffer)));
RC522_RETURN_ON_ERROR(rc522_mifare_send(rc522, buffer, RC522_MIFARE_BLOCK_SIZE));
return ESP_OK;
}
inline esp_err_t rc522_mifare_deauth(const rc522_handle_t rc522, const rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_RETURN_ON_ERROR(rc522_pcd_stop_crypto1(rc522));
return ESP_OK;
}
esp_err_t rc522_mifare_get_desc(const rc522_picc_t *picc, rc522_mifare_desc_t *out_mifare_desc)
{
RC522_CHECK(picc == NULL);
RC522_CHECK(out_mifare_desc == NULL);
rc522_mifare_desc_t desc;
memset(&desc, 0, sizeof(desc));
RC522_RETURN_ON_ERROR(rc522_mifare_get_number_of_sectors(picc->type, &desc.number_of_sectors));
memcpy(out_mifare_desc, &desc, sizeof(rc522_mifare_desc_t));
return ESP_OK;
}
static esp_err_t rc522_mifare_nibbles_to_access_bits(
uint8_t c1, uint8_t c2, uint8_t c3, uint8_t offset, rc522_mifare_access_bits_t *access_bits)
{
RC522_CHECK(offset > 3);
RC522_CHECK(access_bits == NULL);
rc522_mifare_access_bits_t bits = { 0 };
bits.c1 = (c1 & (1 << offset)) >> offset;
bits.c2 = (c2 & (1 << offset)) >> offset;
bits.c3 = (c3 & (1 << offset)) >> offset;
memcpy(access_bits, &bits, sizeof(bits));
return ESP_OK;
}
static esp_err_t rc522_mifare_parse_sector_trailer(
const uint8_t *trailer_bytes, uint8_t trailer_bytes_size, rc522_mifare_sector_trailer_info_t *out_trailer_info)
{
RC522_CHECK(trailer_bytes == NULL);
RC522_CHECK(trailer_bytes_size != RC522_MIFARE_BLOCK_SIZE);
RC522_CHECK(out_trailer_info == NULL);
RC522_RETURN_ON_ERROR(rc522_mifare_verify_access_bits_integrity(trailer_bytes));
uint8_t c1 = 0, c2 = 0, c3 = 0;
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[7], &c1, NULL));
RC522_RETURN_ON_ERROR(rc522_nibbles(trailer_bytes[8], &c3, &c2));
rc522_mifare_sector_trailer_info_t info = { 0 };
for (uint8_t i = 0; i < 4; i++) {
RC522_RETURN_ON_ERROR(rc522_mifare_nibbles_to_access_bits(c1, c2, c3, i, &info.access_bits[i]));
}
memcpy(out_trailer_info, &info, sizeof(info));
return ESP_OK;
}
/**
* Checks if block is Value block based on access bits
*/
inline static bool rc522_mifare_block_is_value(rc522_mifare_access_bits_t access_bits)
{
uint8_t bits = (access_bits.c1 << 2) | (access_bits.c2 << 1) | (access_bits.c3 << 0);
return bits == 0b110 || bits == 0b001;
}
static esp_err_t rc522_mifare_parse_value_block(
const uint8_t *bytes, uint8_t bytes_size, rc522_mifare_sector_value_block_info_t *out_value_block_info)
{
RC522_CHECK(bytes == NULL);
RC522_CHECK(bytes_size != RC522_MIFARE_BLOCK_SIZE);
RC522_CHECK(out_value_block_info == NULL);
rc522_mifare_sector_value_block_info_t value_block_info;
memset(&value_block_info, 0, sizeof(value_block_info));
value_block_info.value = 0;
value_block_info.value |= ((int32_t)(bytes[3]) << (8 * 3));
value_block_info.value |= ((int32_t)(bytes[2]) << (8 * 2));
value_block_info.value |= ((int32_t)(bytes[1]) << (8 * 1));
value_block_info.value |= ((int32_t)(bytes[0]) << (8 * 0));
value_block_info.addr = bytes[12];
// TODO: Check for integrity violation
// Use RC522_ERR_MIFARE_VALUE_BLOCK_INTEGRITY_VIOLATION
memcpy(out_value_block_info, &value_block_info, sizeof(value_block_info));
return ESP_OK;
}
inline static esp_err_t rc522_mifare_get_sector_block_group_index(
const rc522_mifare_sector_desc_t *sector, uint8_t block_offset, uint8_t *out_group)
{
RC522_CHECK(sector == NULL);
RC522_CHECK(out_group == NULL);
RC522_CHECK(block_offset >= sector->number_of_blocks);
if (sector->number_of_blocks > 4) {
*out_group = block_offset / 5;
return ESP_OK;
}
*out_group = block_offset;
return ESP_OK;
}
esp_err_t rc522_mifare_read_sector_trailer_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, rc522_mifare_sector_block_t *out_trailer)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(sector_desc == NULL);
RC522_CHECK(out_trailer == NULL);
rc522_mifare_sector_block_t block;
memset(&block, 0x00, sizeof(block));
block.address = (sector_desc->block_0_address + sector_desc->number_of_blocks - 1);
block.type = RC522_MIFARE_BLOCK_TRAILER;
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
block.error = rc522_mifare_parse_sector_trailer(block.bytes, sizeof(block.bytes), &block.trailer_info);
memcpy(&block.access_bits, &block.trailer_info.access_bits[3], sizeof(rc522_mifare_access_bits_t));
memcpy(out_trailer, &block, sizeof(block));
return ESP_OK;
}
esp_err_t rc522_mifare_read_sector_block(const rc522_handle_t rc522, const rc522_picc_t *picc,
const rc522_mifare_sector_desc_t *sector_desc, const rc522_mifare_sector_block_t *trailer, uint8_t block_offset,
rc522_mifare_sector_block_t *out_block)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(sector_desc == NULL);
RC522_CHECK(trailer == NULL);
RC522_CHECK(block_offset >= sector_desc->number_of_blocks);
RC522_CHECK(out_block == NULL);
RC522_CHECK_WITH_MESSAGE(block_offset == sector_desc->number_of_blocks - 1,
"use rc522_mifare_read_sector_trailer_block() to read the sector trailer block");
RC522_CHECK(trailer->type != RC522_MIFARE_BLOCK_TRAILER);
rc522_mifare_sector_block_t block;
memset(&block, 0, sizeof(block));
block.address = (sector_desc->block_0_address + block_offset);
RC522_RETURN_ON_ERROR(rc522_mifare_read(rc522, picc, block.address, block.bytes));
uint8_t group = 0;
RC522_RETURN_ON_ERROR(rc522_mifare_get_sector_block_group_index(sector_desc, block_offset, &group));
memcpy(&block.access_bits, &trailer->trailer_info.access_bits[group], sizeof(rc522_mifare_access_bits_t));
if (block.address == 0) {
block.type = RC522_MIFARE_BLOCK_MANUFACTURER_DATA;
}
else if (rc522_mifare_block_is_value(block.access_bits)) {
block.type = RC522_MIFARE_BLOCK_VALUE;
block.error = rc522_mifare_parse_value_block(block.bytes, sizeof(block.bytes), &block.value_info);
}
else {
block.type = RC522_MIFARE_BLOCK_DATA;
}
memcpy(out_block, &block, sizeof(block));
return ESP_OK;
}

View File

@@ -0,0 +1,618 @@
#include <esp_system.h>
#include <esp_check.h>
#include <string.h>
#include "rc522_picc.h"
#include "rc522_types_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "picc/rc522_nxp.h"
#include "picc/rc522_mifare.h"
RC522_LOG_DEFINE_BASE();
const uint8_t RC522_NXP_DEFAULT_PWD[RC522_NXP_PWD_SIZE] = { 0xFF, 0xFF, 0xFF, 0xFF };
const uint8_t RC522_NXP_DEFAULT_PACK[RC522_NXP_PACK_SIZE] = { 0x00 };
// Helper to check if the card responded with a NACK
inline esp_err_t rc522_nxp_check_for_nak(rc522_picc_transaction_result_t *result)
{
if (result->bytes.length == 1 && result->valid_bits == 4 && result->bytes.ptr[0] != RC522_MIFARE_ACK) {
return RC522_ERR_NXP_NACK;
}
return ESP_OK;
}
// WRITE supported? (not classic write - see COMPAT_WRITE)
inline bool rc522_nxp_type_has_write(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
case RC522_PICC_TYPE_MIFARE_UL_C:
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG2xx:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// FAST_READ supported?
inline bool rc522_nxp_type_has_fast_read(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// READ_CNT supported?
inline bool rc522_nxp_type_has_read_cnt(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// PWD_AUTH supported?
inline bool rc522_nxp_type_has_pwd_auth(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return true;
default:
return false;
}
}
// Get originality signature size, or 0 if not supported
inline uint8_t rc522_nxp_type_sig_size(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 48;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return 32;
default:
return 0;
}
}
// Convert GET_VERSION response to type
static rc522_picc_type_t rc522_nxp_type_from_version(rc522_nxp_picc_version_t *version)
{
rc522_nxp_product_type_t type = version->product_type;
rc522_nxp_major_version_t major_version = version->major_version;
uint8_t size = version->storage_size;
if (type == RC522_NXP_PRODUCT_TYPE_NTAG) {
if (major_version != RC522_NXP_MAJ_VER_NTAG21) { // Not an NTAG21x
RC522_LOGW("Unknown NTAG product version: %02" RC522_X, major_version);
}
else if (size == 0x0F) { // 0x0F => between 128 and 256 user bytes
return RC522_PICC_TYPE_NTAG213;
}
else if (size == 0x11) { // 0x11 => between 256 and 512 user bytes
return RC522_PICC_TYPE_NTAG215;
}
else if (size == 0x13) { // 0x13 => between 512 and 1024 user byets
return RC522_PICC_TYPE_NTAG216;
}
else { // Unknown size?
RC522_LOGW("Unknown NTAG size: %02" RC522_X, size);
}
}
else if (type == RC522_NXP_PRODUCT_TYPE_UL) {
if (major_version == RC522_NXP_MAJ_VER_UL_EV1) {
if (size == 0x0B) { // 0x0B => between 32 and 64 user bytes
return RC522_PICC_TYPE_MIFARE_UL_EV1_1;
}
else if (size == 0x0E) { // 0x0E => 128 user bytes
return RC522_PICC_TYPE_MIFARE_UL_EV1_2;
}
}
else if (major_version == RC522_NXP_MAJ_VER_UL_NANO) {
return RC522_PICC_TYPE_MIFARE_UL_NANO;
}
else if (major_version == RC522_NXP_MAJ_VER_UL_AES) {
return RC522_PICC_TYPE_MIFARE_UL_AES;
}
else {
RC522_LOGW("Unknown UL product version: %02" RC522_X, major_version);
}
}
else {
RC522_LOGE("Unknown product type: %02" RC522_X, type);
}
return RC522_PICC_TYPE_UNKNOWN;
}
uint8_t rc522_nxp_get_user_page_count(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 12;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 36;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 12;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 32;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 10;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 36;
case RC522_PICC_TYPE_NTAG213:
return 36;
case RC522_PICC_TYPE_NTAG215:
return 126;
case RC522_PICC_TYPE_NTAG216:
return 222;
default:
return 0;
}
}
uint8_t rc522_nxp_get_user_mem_start(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
case RC522_PICC_TYPE_MIFARE_UL_C:
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
case RC522_PICC_TYPE_MIFARE_UL_NANO:
case RC522_PICC_TYPE_MIFARE_UL_AES:
case RC522_PICC_TYPE_NTAG2xx:
case RC522_PICC_TYPE_NTAG213:
case RC522_PICC_TYPE_NTAG215:
case RC522_PICC_TYPE_NTAG216:
return 0x04;
default:
return 0;
}
}
uint8_t rc522_nxp_get_user_mem_end(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 0x0F;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 0x27;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 0x0F;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 0x23;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 0x0D;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 0x27;
case RC522_PICC_TYPE_NTAG213:
return 0x27;
case RC522_PICC_TYPE_NTAG215:
return 0x81;
case RC522_PICC_TYPE_NTAG216:
return 0xE1;
default:
return 0;
}
}
uint8_t rc522_nxp_get_page_count(rc522_picc_type_t type)
{
switch (type) {
case RC522_PICC_TYPE_MIFARE_UL_:
return 16;
case RC522_PICC_TYPE_MIFARE_UL_C:
return 48;
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return 20;
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return 41;
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return 14;
case RC522_PICC_TYPE_MIFARE_UL_AES:
return 60;
case RC522_PICC_TYPE_NTAG213:
return 45;
case RC522_PICC_TYPE_NTAG215:
return 135;
case RC522_PICC_TYPE_NTAG216:
return 231;
default:
return 0;
}
}
esp_err_t rc522_nxp_keyauth_supported(const rc522_handle_t rc522, const rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_LOGD("AUTHENTICATE (support check)");
uint8_t buffer[11] = { 0 }; // combined send(4)/recv(11) buffer
buffer[0] = RC522_PICC_CMD_UL_AUTH;
// buffer[1] is argument; 0x00 is fine for support check
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 2 }, &crc));
buffer[2] = crc.lsb;
buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = 4 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret == ESP_OK && buffer[0] != 0xAF) {
return RC522_ERR_NXP_NACK;
}
return ret;
}
esp_err_t rc522_nxp_get_type(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_type_t *out_type)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_type == NULL);
RC522_CHECK(picc->type != RC522_PICC_TYPE_MIFARE_UL);
RC522_LOGD("Attempting to get NXP PICC type");
RC522_LOGD("Checking for GET_VERSION support...");
// In case of early exit due to errors
rc522_picc_type_t type = RC522_PICC_TYPE_MIFARE_UL;
rc522_nxp_picc_version_t version_info = { 0 };
esp_err_t ret = rc522_nxp_get_version(rc522, picc, &version_info);
if (ret == ESP_OK) { // GET_VERSION supported
type = rc522_nxp_type_from_version(&version_info);
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT) { // No GET_VERSION
// Failure probably resulted in a HALT, so make sure we can still connect
RC522_LOGD("No L3 GET_VERSION; reselecting");
rc522_picc_uid_t uid;
uint8_t sak;
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
// Fall back to checking for authentication availability (C)
RC522_LOGD("Checking for authentication support...");
ret = rc522_nxp_keyauth_supported(rc522, picc);
if (ret == ESP_OK) {
// Chip supports authentication
// TODO: Do we need to complete authentication, or can we leave it here?
// Note datasheet also mentions a "MIFARE Hospitality" here, but there
// doesn't seem to be any more information about it
type = RC522_PICC_TYPE_MIFARE_UL_C;
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_MIFARE_NACK) { // Not supported
// Only Ultralight without auth is the original
type = RC522_PICC_TYPE_MIFARE_UL_;
}
else { // Some other error in AUTHENTICATE
RC522_RETURN_ON_ERROR(ret);
}
rc522_picc_heartbeat(rc522, picc, &uid, &sak);
}
else { // A different error occured in GET_VERSION
RC522_RETURN_ON_ERROR(ret);
}
RC522_LOGD("Found %s", rc522_picc_type_name(type));
*out_type = type;
return ESP_OK;
}
esp_err_t rc522_nxp_get_version(
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_picc_version_t *out_version)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_version == NULL);
RC522_LOGD("GET_VERSION");
uint8_t buffer[10]; // Combined buffer; response is 8-byte data + 2-byte CRC
buffer[0] = RC522_PICC_CMD_GETV;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 1 }, &crc));
buffer[1] = crc.lsb;
buffer[2] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = 3 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
memcpy(out_version, buffer, sizeof(rc522_nxp_picc_version_t));
return ESP_OK;
}
esp_err_t rc522_nxp_read(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, uint8_t out_buffer[RC522_NXP_PAGE_SIZE * 4])
{
// Same protocol as MIFARE cards, so defer
return rc522_mifare_read(rc522, picc, address, out_buffer);
}
esp_err_t rc522_nxp_fast_read(const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t start_page,
uint8_t end_page, rc522_nxp_fast_read_data_t *out_buffer)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_buffer == NULL);
RC522_CHECK(!rc522_nxp_type_has_fast_read(picc->type));
// some sanity checks - valid range, output buffer sufficiently large
RC522_CHECK(start_page > end_page);
RC522_CHECK(out_buffer->buffer_size < (end_page - start_page + 1) * 4);
RC522_LOGD("NXP FAST_READ (start=%02" RC522_X ", end=%02" RC522_X ")", start_page, end_page);
uint8_t cmd_buffer[5] = { RC522_NXP_FAST_READ, start_page, end_page, 0, 0 };
uint8_t byte_count = (end_page - start_page + 1) * 4;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = 3,
},
&crc));
cmd_buffer[3] = crc.lsb;
cmd_buffer[4] = crc.msb;
// I'd rather not malloc() here, but we need extra space for the CRC which
// we're not guaranteed to have in out_buffer
// TODO: Allow for separate CRC fetch and processing to avoid this?
uint8_t *recv_buffer = malloc(byte_count + 2); // Allow for CRC_A
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
.check_crc = true,
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = recv_buffer, .length = byte_count + 2 },
};
// Bunch of manual handling here to make sure free() is called
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
else if ((result.bytes.length - 2) != byte_count) {
free(recv_buffer);
return ESP_FAIL;
}
memcpy(out_buffer->bytes, recv_buffer, byte_count);
out_buffer->read_size = byte_count;
free(recv_buffer);
return ESP_OK;
}
esp_err_t rc522_nxp_write(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t address, const uint8_t buffer[RC522_NXP_PAGE_SIZE])
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(!rc522_nxp_type_has_write(picc->type));
RC522_LOGD("NXP WRITE (address=%02" RC522_X ")", address);
uint8_t cmd_buffer[RC522_NXP_PAGE_SIZE + 4] = { 0 };
cmd_buffer[0] = RC522_NXP_WRITE;
cmd_buffer[1] = address;
memcpy(&cmd_buffer[2], buffer, RC522_NXP_PAGE_SIZE);
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2 + RC522_NXP_PAGE_SIZE] = crc.lsb;
cmd_buffer[2 + RC522_NXP_PAGE_SIZE + 1] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
return rc522_nxp_check_for_nak(&result);
}
esp_err_t rc522_nxp_read_cnt(
const rc522_handle_t rc522, const rc522_picc_t *picc, uint8_t counter_no, uint32_t *out_count)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_count == NULL);
RC522_CHECK(!rc522_nxp_type_has_read_cnt(picc->type));
RC522_LOGD("NXP READ_CNT (counter=%02" RC522_X ")", counter_no);
uint8_t cmd_buffer[5] = {
// send(4)/recv(5) buffer
RC522_NXP_READ_CNT,
counter_no,
0,
0, // CRC
0 // Space for reply (3 bytes + CRC)
};
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = 4 },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
// TODO: Verify this counter is correct
*out_count = (cmd_buffer[2] << 16) | (cmd_buffer[1] << 8) | cmd_buffer[0];
return ESP_OK;
}
esp_err_t rc522_nxp_pwd_auth(const rc522_handle_t rc522, const rc522_picc_t *picc,
const uint8_t pwd[RC522_NXP_PWD_SIZE], const uint8_t pack[RC522_NXP_PACK_SIZE], rc522_picc_state_t *out_state)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(pwd == NULL);
RC522_CHECK(pack == NULL);
RC522_CHECK(!rc522_nxp_type_has_pwd_auth(picc->type));
RC522_LOGD("NXP PWD_AUTH");
uint8_t cmd_buffer[RC522_NXP_PWD_SIZE + 3] = { 0 }; // send(11)/recv(4) buffer
cmd_buffer[0] = RC522_NXP_PWD_AUTH;
memcpy(&cmd_buffer[1], pwd, RC522_NXP_PWD_SIZE);
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[1 + RC522_NXP_PWD_SIZE] = crc.lsb;
cmd_buffer[1 + RC522_NXP_PWD_SIZE + 1] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
// If authentication fails (timeout or NAK), the card will HALT, so assume
// it has until we know otherwise
*out_state = RC522_PICC_STATE_HALT;
RC522_RETURN_ON_ERROR(rc522_picc_transceive(rc522, &transaction, &result));
RC522_RETURN_ON_ERROR(rc522_nxp_check_for_nak(&result));
// Successful authentication, set state
*out_state = RC522_PICC_STATE_AUTHENTICATED;
// Validate the PACK - not critical to authenticate the card, but good
// practice to verify
RC522_CHECK_AND_RETURN(memcmp(pack, cmd_buffer, RC522_NXP_PACK_SIZE) != 0, RC522_ERR_NXP_PACK_MISMATCH);
*out_state = RC522_PICC_STATE_AUTHENTICATED;
return ESP_OK;
}
esp_err_t rc522_nxp_read_sig(const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_nxp_sig_t *out_sig)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(out_sig == NULL);
uint8_t sig_size = rc522_nxp_type_sig_size(picc->type);
RC522_CHECK(sig_size == 0);
RC522_CHECK(out_sig->buffer_size < sig_size);
RC522_LOGD("NXP READ_SIG (size=%d)", sig_size);
uint8_t cmd_buffer[4] = {
RC522_NXP_READ_SIG,
0,
0,
0 // CRC
};
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) {
.ptr = cmd_buffer,
.length = sizeof(cmd_buffer) - 2,
},
&crc));
cmd_buffer[2] = crc.lsb;
cmd_buffer[3] = crc.msb;
// TODO: Combine to single malloc() for both buffers?
// Again, I'd rather not do a large malloc() here, but we need the space for
// CRC. See TODO in FAST_READ
uint8_t *recv_buffer = malloc(sig_size + 2); // +2 for CRC
RC522_CHECK_AND_RETURN(recv_buffer == NULL, ESP_ERR_NO_MEM);
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = cmd_buffer, .length = sizeof(cmd_buffer) },
};
rc522_picc_transaction_result_t result = {
.bytes = { .ptr = recv_buffer, .length = sig_size + 2 },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
ret = rc522_nxp_check_for_nak(&result);
if (ret != ESP_OK) {
free(recv_buffer);
return ret;
}
memcpy(out_sig->bytes, recv_buffer, sig_size);
out_sig->sig_size = sig_size;
free(recv_buffer);
return ESP_OK;
}

View File

@@ -0,0 +1,342 @@
#include <esp_system.h>
#include <esp_log.h>
#include <esp_check.h>
#include <string.h>
#include <sys/time.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_types_internal.h"
#include "rc522_internal.h"
RC522_LOG_DEFINE_BASE();
ESP_EVENT_DEFINE_BASE(RC522_EVENTS);
inline static bool rc522_is_able_to_start(const rc522_handle_t rc522)
{
return rc522->state >= RC522_STATE_CREATED && rc522->state != RC522_STATE_POLLING;
}
static esp_err_t rc522_clone_config(const rc522_config_t *config, rc522_config_t **result)
{
rc522_config_t *config_clone = calloc(1, sizeof(rc522_config_t));
RC522_CHECK_AND_RETURN(config_clone == NULL, ESP_ERR_NO_MEM);
memcpy(config_clone, config, sizeof(rc522_config_t));
// defaults
if (config_clone->poll_interval_ms < RC522_POLL_INTERVAL_MS_MIN) {
config_clone->poll_interval_ms = RC522_POLL_INTERVAL_MS_DEFAULT;
}
if (config_clone->task_stack_size == 0) {
config_clone->task_stack_size = RC522_TASK_STACK_SIZE_DEFAULT;
}
if (config_clone->task_priority == 0) {
config_clone->task_priority = RC522_TASK_PRIORITY_DEFAULT;
}
// ~defaults
*result = config_clone;
return ESP_OK;
}
esp_err_t rc522_register_events(
const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler, void *event_handler_arg)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(event_handler == NULL);
return esp_event_handler_register_with(rc522->event_handle, RC522_EVENTS, event, event_handler, event_handler_arg);
}
esp_err_t rc522_unregister_events(const rc522_handle_t rc522, rc522_event_t event, esp_event_handler_t event_handler)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(event_handler == NULL);
return esp_event_handler_unregister_with(rc522->event_handle, RC522_EVENTS, event, event_handler);
}
esp_err_t rc522_start(rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
ESP_RETURN_ON_FALSE(rc522 != NULL, ESP_ERR_INVALID_ARG, TAG, "rc522 is null");
ESP_RETURN_ON_FALSE(rc522_is_able_to_start(rc522),
ESP_ERR_INVALID_STATE,
TAG,
"Unable to start (state=%d)",
rc522->state);
if (rc522->state == RC522_STATE_PAUSED) {
// Scanning has been paused. No need for reinitialization. Just resume
rc522->state = RC522_STATE_POLLING;
return ESP_OK;
}
RC522_RETURN_ON_ERROR(rc522_pcd_reset(rc522, 150));
esp_err_t rw_ret = rc522_pcd_rw_test(rc522);
if (rw_ret != ESP_OK) {
ESP_LOGW(TAG, "rw test warning (%s), proceeding with pcd_init...", esp_err_to_name(rw_ret));
}
ESP_RETURN_ON_ERROR(rc522_pcd_init(rc522), TAG, "unable to init pcd");
rc522->state = RC522_STATE_POLLING;
return ESP_OK;
}
esp_err_t rc522_pause(rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
ESP_RETURN_ON_FALSE(rc522->state == RC522_STATE_POLLING,
ESP_ERR_INVALID_STATE,
TAG,
"Invalid state (state=%d)",
rc522->state);
rc522->state = RC522_STATE_PAUSED;
return ESP_OK;
}
/**
* @brief Exit and delete the task
*/
inline static void rc522_request_task_to_exit(const rc522_handle_t rc522)
{
rc522->exit_requested = true; // task will delete itself
}
esp_err_t rc522_create(const rc522_config_t *config, rc522_handle_t *out_rc522)
{
RC522_CHECK(config == NULL);
RC522_CHECK(out_rc522 == NULL);
rc522_handle_t rc522 = calloc(1, sizeof(struct rc522));
ESP_RETURN_ON_FALSE(rc522 != NULL, ESP_ERR_NO_MEM, TAG, "nomem");
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, false);
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_ERROR(rc522_clone_config(config, &(rc522->config)), _error, TAG, "clone config failed");
esp_event_loop_args_t event_args = {
.queue_size = 1,
.task_name = NULL, // no task will be created
};
rc522->bits = xEventGroupCreate();
ESP_GOTO_ON_FALSE(rc522->bits != NULL, ESP_ERR_NO_MEM, _error, TAG, "nomem");
ESP_GOTO_ON_ERROR(esp_event_loop_create(&event_args, &rc522->event_handle),
_error,
TAG,
"Failed to create event loop");
BaseType_t task_create_result = xTaskCreate(rc522_task,
"rc522_poll",
rc522->config->task_stack_size,
rc522,
rc522->config->task_priority,
&rc522->task_handle);
ESP_GOTO_ON_FALSE(task_create_result == pdTRUE, ESP_FAIL, _error, TAG, "task create failed");
goto _success;
_error:
rc522_destroy(rc522);
rc522 = NULL;
goto _return;
_success:
rc522->state = RC522_STATE_CREATED;
*out_rc522 = rc522;
_return:
return ret;
}
esp_err_t rc522_destroy(rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
ESP_RETURN_ON_FALSE(xTaskGetCurrentTaskHandle() != rc522->task_handle,
ESP_ERR_INVALID_STATE,
TAG,
"Cannot destroy from event handler");
rc522_request_task_to_exit(rc522);
if (rc522->bits) {
xEventGroupWaitBits(rc522->bits, RC522_TASK_STOPPED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
vEventGroupDelete(rc522->bits);
rc522->bits = NULL;
}
if (rc522->event_handle) {
if (esp_event_loop_delete(rc522->event_handle) != ESP_OK) {
ESP_LOGW(TAG, "Failed to delete event loop");
}
rc522->event_handle = NULL;
}
if (rc522->config) {
free(rc522->config);
rc522->config = NULL;
}
free(rc522);
return ESP_OK;
}
esp_err_t rc522_dispatch_event(const rc522_handle_t rc522, rc522_event_t event, const void *data, size_t data_size)
{
RC522_RETURN_ON_ERROR(esp_event_post_to(rc522->event_handle, RC522_EVENTS, event, data, data_size, portMAX_DELAY));
return esp_event_loop_run(rc522->event_handle, 0);
}
void rc522_task(void *arg)
{
esp_err_t ret = ESP_OK;
rc522_handle_t rc522 = (rc522_handle_t)arg;
uint32_t last_poll_ms = 0;
const uint32_t task_delay_ms = 50;
const uint32_t picc_heartbeat_failure_threshold_ms = (2 * task_delay_ms);
uint32_t picc_heartbeat_failure_at_ms = 0;
bool mutex_taken = false;
const uint16_t mutex_take_timeout_ms = 4000;
xEventGroupClearBits(rc522->bits, RC522_TASK_STOPPED_BIT);
while (!rc522->exit_requested) {
if (mutex_taken && rc522->config->task_mutex != NULL) {
if (xSemaphoreGive(rc522->config->task_mutex) == pdTRUE) {
mutex_taken = false;
}
else {
RC522_LOGW("failed to give mutex");
}
}
if (rc522->state != RC522_STATE_POLLING) {
// waiting for state change to polling
rc522_delay_ms(100);
continue;
}
rc522_delay_ms(task_delay_ms);
if (rc522->config->task_mutex != NULL) {
if (xSemaphoreTake(rc522->config->task_mutex, pdMS_TO_TICKS(mutex_take_timeout_ms)) == pdTRUE) {
mutex_taken = true;
}
else {
RC522_LOGW("failed to take mutex in %d ms", mutex_take_timeout_ms);
continue;
}
}
bool should_poll = (rc522_millis() - last_poll_ms) > rc522->config->poll_interval_ms;
if (rc522->picc.state == RC522_PICC_STATE_IDLE || rc522->picc.state == RC522_PICC_STATE_HALT) {
rc522_picc_atqa_desc_t atqa;
if (rc522->picc.state == RC522_PICC_STATE_IDLE && ((ret = rc522_picc_reqa(rc522, &atqa)) != ESP_OK)) {
continue;
}
if (rc522->picc.state == RC522_PICC_STATE_HALT && ((ret = rc522_picc_wupa(rc522, &atqa)) != ESP_OK)) {
continue;
}
ESP_LOGI(TAG, "Tag detected in field (ATQA=0x%04X)", atqa.source);
// card is present
rc522->picc.atqa = atqa;
if (rc522->picc.state == RC522_PICC_STATE_IDLE) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_READY, true);
}
else if (rc522->picc.state == RC522_PICC_STATE_HALT) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_READY_H, true);
}
}
if (should_poll
&& (rc522->picc.state == RC522_PICC_STATE_READY || rc522->picc.state == RC522_PICC_STATE_READY_H)) {
rc522_picc_uid_t uid;
uint8_t sak;
ret = rc522_picc_select(rc522, &uid, &sak, false);
last_poll_ms = rc522_millis();
if (ret != ESP_OK) {
if (ret != RC522_ERR_RX_TIMEOUT && ret != RC522_ERR_INVALID_ATQA && ret != RC522_ERR_INVALID_SAK) {
RC522_LOGW("select failed (err=%04" RC522_X ")", ret);
}
else {
RC522_LOGD("select failed (err=%04" RC522_X ")", ret);
}
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, true);
continue;
}
memcpy(&rc522->picc.uid, &uid, sizeof(rc522_picc_uid_t));
rc522->picc.sak = sak;
rc522->picc.type = rc522_picc_get_type(&rc522->picc);
ESP_LOGI(TAG, "Tag selected (SAK=0x%02X, length=%d)", sak, uid.length);
if (rc522->picc.state == RC522_PICC_STATE_READY) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_ACTIVE, true);
}
else if (rc522->picc.state == RC522_PICC_STATE_READY_H) {
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_ACTIVE_H, true);
}
picc_heartbeat_failure_at_ms = 0;
continue;
}
if (rc522->picc.state == RC522_PICC_STATE_ACTIVE || rc522->picc.state == RC522_PICC_STATE_ACTIVE_H) {
if (picc_heartbeat_failure_at_ms != 0
&& ((rc522_millis() - picc_heartbeat_failure_at_ms) > picc_heartbeat_failure_threshold_ms)) {
picc_heartbeat_failure_at_ms = 0;
rc522_picc_set_state(rc522, &rc522->picc, RC522_PICC_STATE_IDLE, true);
continue;
}
if ((ret = rc522_picc_heartbeat(rc522, &rc522->picc, NULL, NULL)) == ESP_OK) {
picc_heartbeat_failure_at_ms = 0;
}
else if (picc_heartbeat_failure_at_ms == 0) {
picc_heartbeat_failure_at_ms = rc522_millis();
}
if (ret != ESP_OK) {
RC522_LOGD("heartbeat failed (err=%04" RC522_X ")", ret);
}
// card is still in the field
continue;
}
}
xEventGroupSetBits(rc522->bits, RC522_TASK_STOPPED_BIT);
ESP_LOGI(TAG, "Task exited");
vTaskDelete(NULL); // self-delete
}

View File

@@ -0,0 +1,108 @@
#include <string.h>
#include <driver/gpio.h>
#include "rc522_types_internal.h"
#include "rc522_driver_internal.h"
RC522_LOG_DEFINE_BASE();
esp_err_t rc522_driver_init_rst_pin(gpio_num_t rst_io_num)
{
RC522_CHECK(rst_io_num < 0);
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << rst_io_num),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_ENABLE,
};
RC522_RETURN_ON_ERROR(gpio_config(&io_conf));
RC522_RETURN_ON_ERROR(gpio_set_level(rst_io_num, !RC522_DRIVER_HARD_RST_PIN_PWR_DOWN_LEVEL));
return ESP_OK;
}
inline esp_err_t rc522_driver_install(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
return driver->install(driver);
}
inline esp_err_t rc522_driver_send(const rc522_driver_handle_t driver, uint8_t address, const rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK_BYTES(bytes);
return driver->send(driver, address, bytes);
}
inline esp_err_t rc522_driver_receive(const rc522_driver_handle_t driver, uint8_t address, rc522_bytes_t *bytes)
{
RC522_CHECK(driver == NULL);
RC522_CHECK_BYTES(bytes);
return driver->receive(driver, address, bytes);
}
inline esp_err_t rc522_driver_reset(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
return driver->reset(driver);
}
inline esp_err_t rc522_driver_uninstall(const rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
return driver->uninstall(driver);
}
esp_err_t rc522_driver_create(const void *config, size_t config_size, rc522_driver_handle_t *driver)
{
RC522_CHECK(config == NULL);
RC522_CHECK(config_size == 0);
RC522_CHECK(driver == NULL);
esp_err_t ret = ESP_OK;
rc522_driver_handle_t _driver = calloc(1, sizeof(struct rc522_driver_handle));
ESP_RETURN_ON_FALSE(_driver != NULL, ESP_ERR_NO_MEM, TAG, "nomem");
_driver->config = calloc(1, config_size);
ESP_GOTO_ON_FALSE(_driver->config != NULL, ESP_ERR_NO_MEM, error, TAG, "nomem");
memcpy(_driver->config, config, config_size);
goto success;
error:
rc522_driver_destroy(_driver);
goto exit;
success:
*driver = _driver;
exit:
return ret;
}
esp_err_t rc522_driver_destroy(rc522_driver_handle_t driver)
{
RC522_CHECK(driver == NULL);
if (driver->config) {
free(driver->config);
driver->config = NULL;
}
driver->install = NULL;
driver->send = NULL;
driver->receive = NULL;
driver->uninstall = NULL;
driver->device = NULL;
free(driver);
return ESP_OK;
}

View File

@@ -0,0 +1,65 @@
#include <esp_system.h>
#include <esp_log.h>
#include <esp_check.h>
#include <string.h>
#include <sys/time.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "rc522_types_internal.h"
#include "rc522_helpers_internal.h"
#include <esp_timer.h>
RC522_LOG_DEFINE_BASE();
uint32_t rc522_millis()
{
return (uint32_t)(esp_timer_get_time() / 1000ULL);
}
void rc522_delay_ms(uint32_t ms)
{
TickType_t ticks = pdMS_TO_TICKS(ms);
if (ticks == 0 && ms > 0) {
ticks = 1;
}
vTaskDelay(ticks);
}
esp_err_t rc522_buffer_to_hex_str(
const uint8_t *buffer, uint8_t buffer_length, char *str_buffer, uint8_t str_buffer_length)
{
RC522_CHECK(buffer == NULL);
RC522_CHECK(buffer_length < 1);
RC522_CHECK(str_buffer == NULL);
const char *format = "%02" RC522_X " ";
const uint8_t formatted_length = 3;
RC522_CHECK(str_buffer_length < (buffer_length * formatted_length));
uint16_t len = 0;
for (uint16_t i = 0; i < buffer_length; i++) {
len += sprintf(str_buffer + len, format, buffer[i]);
}
str_buffer[len - 1] = 0x00;
return ESP_OK;
}
inline esp_err_t rc522_nibbles(uint8_t byte, uint8_t *msb, uint8_t *lsb)
{
RC522_CHECK(msb == NULL && lsb == NULL);
if (msb) {
*msb = byte >> 4;
}
if (lsb) {
*lsb = byte & 0x0F;
}
return ESP_OK;
}

View File

@@ -0,0 +1,370 @@
#include <esp_system.h>
#include <esp_check.h>
#include <string.h>
#include "rc522_types_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_driver_internal.h"
#include "rc522_pcd_internal.h"
RC522_LOG_DEFINE_BASE();
/**
* @see https://stackoverflow.com/a/48705557
*/
esp_err_t rc522_pcd_calculate_crc(const rc522_handle_t rc522, const rc522_bytes_t *bytes, rc522_pcd_crc_t *result)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK_BYTES(bytes);
RC522_CHECK(result == NULL);
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_DIV_INT_REQ_REG, RC522_PCD_CRC_IRQ_BIT));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, bytes));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_CALC_CRC_CMD));
uint32_t deadline_ms = rc522_millis() + 90;
bool calculation_done = false;
do {
uint8_t irq;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_DIV_INT_REQ_REG, &irq));
if (irq & RC522_PCD_CRC_IRQ_BIT) {
calculation_done = true;
break;
}
rc522_delay_ms(2);
}
while (rc522_millis() < deadline_ms);
if (!calculation_done) { // Deadline reached
return ESP_ERR_TIMEOUT;
}
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CRC_RESULT_MSB_REG, &crc.msb));
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CRC_RESULT_LSB_REG, &crc.lsb));
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
char debug_buffer[64];
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
RC522_LOGD("crc(%s) = 0x%04" RC522_X, debug_buffer, crc.value);
}
*result = crc;
return ESP_OK;
}
static esp_err_t rc522_pcd_wait_for_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
RC522_CHECK(rc522 == NULL);
esp_err_t ret = ESP_OK;
bool power_down_bit = true;
uint32_t start_ms = rc522_millis();
// Wait for the PowerDown bit in CommandReg to be cleared
do {
rc522_delay_ms(25);
uint8_t cmd;
if ((ret = rc522_pcd_read(rc522, RC522_PCD_COMMAND_REG, &cmd)) != ESP_OK) {
RC522_LOGD("wait for reset error %04" RC522_X, ret);
continue;
}
if (!(power_down_bit = (cmd & RC522_PCD_POWER_DOWN_BIT))) {
break;
}
}
while ((rc522_millis() - start_ms) < timeout_ms);
return power_down_bit ? ESP_ERR_TIMEOUT : ESP_OK;
}
inline static esp_err_t rc522_pcd_hard_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
RC522_CHECK(rc522 == NULL);
RC522_LOGD("hard reset");
esp_err_t ret = rc522_driver_reset(rc522->config->driver);
return ret == ESP_OK ? rc522_pcd_wait_for_reset(rc522, timeout_ms) : ret;
}
inline static esp_err_t rc522_pcd_soft_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
RC522_CHECK(rc522 == NULL);
RC522_LOGD("soft reset");
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_SOFT_RESET_CMD));
return rc522_pcd_wait_for_reset(rc522, timeout_ms);
}
esp_err_t rc522_pcd_reset(const rc522_handle_t rc522, uint32_t timeout_ms)
{
esp_err_t ret = ESP_OK;
if ((ret = rc522_pcd_hard_reset(rc522, timeout_ms)) != ESP_OK) {
if (ret != RC522_ERR_RST_PIN_UNUSED) {
RC522_LOGW("hard reset failed, trying soft reset");
}
ret = rc522_pcd_soft_reset(rc522, timeout_ms);
}
return ret;
}
inline static esp_err_t rc522_pcd_tx_enable(const rc522_handle_t rc522)
{
return rc522_pcd_set_bits(rc522, RC522_PCD_TX_CONTROL_REG, (RC522_PCD_TX2_RF_EN_BIT | RC522_PCD_TX1_RF_EN_BIT));
}
static esp_err_t rc522_pcd_configure_timer(const rc522_handle_t rc522, uint8_t mode, uint16_t prescaler)
{
uint8_t prescaler_hi = (prescaler >> 8) & 0x0F;
uint8_t timer_mode = (mode & 0xF0) | prescaler_hi;
uint8_t prescaler_lo = (prescaler & 0xFF);
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_MODE_REG, timer_mode));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_PRESCALER_REG, prescaler_lo));
return ESP_OK;
}
inline static esp_err_t rc522_pcd_set_timer_reload_value(const rc522_handle_t rc522, uint16_t value)
{
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_RELOAD_MSB_REG, (value >> 8) & 0xFF));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TIMER_RELOAD_LSB_REG, value & 0xFF));
return ESP_OK;
}
inline esp_err_t rc522_pcd_set_rx_gain(const rc522_handle_t rc522, rc522_pcd_rx_gain_t gain)
{
uint8_t value = 0;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_RF_CFG_REG, &value));
return rc522_pcd_write(rc522, RC522_PCD_RF_CFG_REG, (value & ~0x70) | (gain & 0x70));
}
esp_err_t rc522_pcd_init(const rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
// Reset baud rates
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TX_MODE_REG, RC522_PCD_TX_MODE_REG_RESET_VALUE));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_RX_MODE_REG, RC522_PCD_RX_MODE_REG_RESET_VALUE));
// Reset modulation width
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_MOD_WIDTH_REG, RC522_PCD_MOD_WIDTH_REG_RESET_VALUE));
// When communicating with a PICC we need a timeout if something goes wrong.
// f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = [TPrescaler_Hi:TPrescaler_Lo].
// TPrescaler_Hi are the four low bits in TModeReg. TPrescaler_Lo is TPrescalerReg.
// TAuto=1; timer starts automatically at the end of the transmission in all communication modes at all speeds
// TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => f_timer=40kHz, ie a timer period of 25μs.
RC522_RETURN_ON_ERROR(rc522_pcd_configure_timer(rc522, RC522_PCD_T_AUTO_BIT, 169));
// Reload timer with 0x3E8 = 1000, ie 25ms before timeout.
RC522_RETURN_ON_ERROR(rc522_pcd_set_timer_reload_value(rc522, 1000));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_TX_ASK_REG, RC522_PCD_FORCE_100_ASK_BIT));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_MODE_REG, 0x3D));
// Enable the antenna driver pins TX1 and TX2 (they were disabled by the reset)
RC522_RETURN_ON_ERROR(rc522_pcd_tx_enable(rc522));
// Set maximum receiver antenna gain (48dB) for compact antenna on MH-ET LIVE module
RC522_RETURN_ON_ERROR(rc522_pcd_set_rx_gain(rc522, RC522_PCD_48_DB_RX_GAIN));
rc522_pcd_firmware_t fw;
ESP_RETURN_ON_ERROR(rc522_pcd_firmware(rc522, &fw), TAG, "read fw version failed");
// When 0x00 or 0xFF is returned, communication probably failed
if (fw == 0x00 || fw == 0xFF) {
RC522_LOGE("Communication failure, is the MFRC522 properly connected?");
return ESP_FAIL; // TODO: use custom err
}
uint8_t mode_reg = 0, tx_ctrl = 0, rf_cfg = 0;
rc522_pcd_read(rc522, RC522_PCD_MODE_REG, &mode_reg);
rc522_pcd_read(rc522, RC522_PCD_TX_CONTROL_REG, &tx_ctrl);
rc522_pcd_read(rc522, RC522_PCD_RF_CFG_REG, &rf_cfg);
ESP_LOGI(TAG, "PCD (fw=%s) initialized [ModeReg=0x%02X TxControlReg=0x%02X RFCfgReg=0x%02X]",
rc522_pcd_firmware_name(fw), mode_reg, tx_ctrl, rf_cfg);
return ESP_OK;
}
esp_err_t rc522_pcd_firmware(const rc522_handle_t rc522, rc522_pcd_firmware_t *fw)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(fw == NULL);
uint8_t value;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_VERSION_REG, &value));
*fw = (rc522_pcd_firmware_t)value;
return ESP_OK;
}
char *rc522_pcd_firmware_name(rc522_pcd_firmware_t firmware)
{
switch (firmware) {
case RC522_PCD_FIRMWARE_CLONE:
return "clone";
case RC522_PCD_FIRMWARE_00:
return "v0.0";
case RC522_PCD_FIRMWARE_10:
return "v1.0";
case RC522_PCD_FIRMWARE_20:
return "v2.0";
case RC522_PCD_FIRMWARE_COUNTERFEIT:
return "counterfeit_chip";
default:
return "unknown";
}
}
inline esp_err_t rc522_pcd_stop_active_command(const rc522_handle_t rc522)
{
return rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, RC522_PCD_IDLE_CMD);
}
inline esp_err_t rc522_pcd_clear_all_com_interrupts(const rc522_handle_t rc522)
{
return rc522_pcd_write(rc522, RC522_PCD_COM_INT_REQ_REG, (uint8_t)(~RC522_PCD_SET_1_BIT));
}
inline esp_err_t rc522_pcd_fifo_write(const rc522_handle_t rc522, const rc522_bytes_t *bytes)
{
return rc522_pcd_write_n(rc522, RC522_PCD_FIFO_DATA_REG, bytes);
}
inline esp_err_t rc522_pcd_fifo_read(const rc522_handle_t rc522, rc522_bytes_t *bytes)
{
return rc522_pcd_read_n(rc522, RC522_PCD_FIFO_DATA_REG, bytes);
}
inline esp_err_t rc522_pcd_fifo_flush(const rc522_handle_t rc522)
{
return rc522_pcd_write(rc522, RC522_PCD_FIFO_LEVEL_REG, RC522_PCD_FLUSH_BUFFER_BIT);
}
inline esp_err_t rc522_pcd_start_data_transmission(const rc522_handle_t rc522)
{
return rc522_pcd_set_bits(rc522, RC522_PCD_BIT_FRAMING_REG, RC522_PCD_START_SEND_BIT);
}
inline esp_err_t rc522_pcd_stop_data_transmission(const rc522_handle_t rc522)
{
return rc522_pcd_clear_bits(rc522, RC522_PCD_BIT_FRAMING_REG, RC522_PCD_START_SEND_BIT);
}
inline esp_err_t rc522_pcd_stop_crypto1(const rc522_handle_t rc522)
{
return rc522_pcd_clear_bits(rc522, RC522_PCD_STATUS_2_REG, RC522_PCD_MF_CRYPTO1_ON_BIT);
}
esp_err_t rc522_pcd_rw_test(const rc522_handle_t rc522)
{
RC522_CHECK(rc522 == NULL);
uint8_t tmp;
ESP_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &tmp), TAG, "Cannot read FIFO length");
ESP_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522), TAG, "Cannot flush FIFO");
uint8_t buffer1[] = { 0x13, 0x33, 0x37 };
const uint8_t buffer_size = sizeof(buffer1);
uint8_t buffer2[buffer_size];
ESP_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, &(rc522_bytes_t) { .ptr = buffer1, .length = buffer_size }),
TAG,
"Cannot write to FIFO");
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &tmp));
ESP_RETURN_ON_FALSE(tmp == buffer_size, ESP_FAIL, TAG, "FIFO length missmatch after write");
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_read(rc522, &(rc522_bytes_t) { .ptr = buffer2, .length = buffer_size }));
if (memcmp(buffer1, buffer2, buffer_size) != 0) {
RC522_LOGE("Buffers content missmatch");
RC522_LOGE("Buffer1: ");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer1, buffer_size, ESP_LOG_ERROR);
RC522_LOGE("Buffer2: ");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer2, buffer_size, ESP_LOG_ERROR);
return ESP_FAIL;
}
return ESP_OK;
}
inline esp_err_t rc522_pcd_write_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, const rc522_bytes_t *bytes)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK_BYTES(bytes);
if (RC522_LOG_LEVEL >= ESP_LOG_VERBOSE) {
char debug_buffer[64];
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
RC522_LOGV("pcd [0x%02" RC522_X "] <<< %s", addr, debug_buffer);
}
RC522_RETURN_ON_ERROR(rc522_driver_send(rc522->config->driver, addr, bytes));
return ESP_OK;
}
inline esp_err_t rc522_pcd_write(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t val)
{
return rc522_pcd_write_n(rc522, addr, &(rc522_bytes_t) { .ptr = &val, .length = 1 });
}
inline esp_err_t rc522_pcd_read_n(const rc522_handle_t rc522, rc522_pcd_register_t addr, rc522_bytes_t *bytes)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK_BYTES(bytes);
esp_err_t ret = rc522_driver_receive(rc522->config->driver, addr, bytes);
if (RC522_LOG_LEVEL >= ESP_LOG_VERBOSE) {
char debug_buffer[64];
rc522_buffer_to_hex_str(bytes->ptr, bytes->length, debug_buffer, sizeof(debug_buffer));
RC522_LOGV("pcd [0x%02" RC522_X "] >>> %s", addr, debug_buffer);
}
return ret;
}
inline esp_err_t rc522_pcd_read(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t *value_ref)
{
return rc522_pcd_read_n(rc522, addr, &(rc522_bytes_t) { .ptr = value_ref, .length = 1 });
}
inline esp_err_t rc522_pcd_set_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits)
{
uint8_t value;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, addr, &value));
return rc522_pcd_write(rc522, addr, value | bits);
}
inline esp_err_t rc522_pcd_clear_bits(const rc522_handle_t rc522, rc522_pcd_register_t addr, uint8_t bits)
{
uint8_t value;
ESP_RETURN_ON_ERROR(rc522_pcd_read(rc522, addr, &value), TAG, "");
return rc522_pcd_write(rc522, addr, value & (~bits));
}

View File

@@ -0,0 +1,858 @@
#include <esp_system.h>
#include <esp_check.h>
#include <string.h>
#include "rc522_internal.h"
#include "rc522_types_internal.h"
#include "rc522_helpers_internal.h"
#include "rc522_pcd_internal.h"
#include "rc522_picc_internal.h"
RC522_LOG_DEFINE_BASE();
struct rc522_picc_transaction_context
{
const rc522_picc_transaction_t *transaction;
uint8_t interrupts;
bool completed;
uint8_t error_reg;
};
esp_err_t rc522_picc_send(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
rc522_picc_transaction_context_t *out_context)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(transaction == NULL);
RC522_CHECK_BYTES(&transaction->bytes);
RC522_CHECK(
transaction->pcd_command != RC522_PCD_TRANSCEIVE_CMD && transaction->pcd_command != RC522_PCD_MF_AUTH_CMD);
RC522_CHECK(transaction->expected_interrupts == 0);
rc522_picc_transaction_context_t context = {
.transaction = transaction,
};
// Prepare values for bit framing
uint8_t bit_framing = (transaction->rx_align << 4) + transaction->valid_bits;
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
RC522_LOGD("rx_align=%d,tx_last_bits=%d, bit_framing=%d",
transaction->rx_align,
transaction->valid_bits,
bit_framing);
char debug_buffer[64];
rc522_buffer_to_hex_str(transaction->bytes.ptr, transaction->bytes.length, debug_buffer, sizeof(debug_buffer));
RC522_LOGD("picc << %s", debug_buffer);
}
RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_clear_all_com_interrupts(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522));
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, &transaction->bytes));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, bit_framing));
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, transaction->pcd_command));
if (transaction->pcd_command == RC522_PCD_TRANSCEIVE_CMD) {
RC522_RETURN_ON_ERROR(rc522_pcd_start_data_transmission(rc522));
}
// TAuto flag in TModeReg is set.
// This means the timer automatically starts when the PCD stops transmitting.
const uint32_t deadline = rc522_millis() + 36;
do {
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_COM_INT_REQ_REG, &context.interrupts));
if (context.interrupts & transaction->expected_interrupts) {
context.completed = true;
break;
}
// Timer interrupt - nothing received
if (context.interrupts & RC522_PCD_TIMER_IRQ_BIT) {
RC522_LOGD("timer interrupt (irq=0x%02" RC522_X ")", context.interrupts);
rc522_pcd_stop_active_command(rc522);
return RC522_ERR_RX_TIMER_TIMEOUT;
}
rc522_delay_ms(2);
}
while (rc522_millis() < deadline);
// Deadline reached and nothing happened (normal when no card is present).
if (!context.completed) {
rc522_pcd_stop_active_command(rc522);
return RC522_ERR_RX_TIMEOUT;
}
// Stop now if any errors except collisions were detected.
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_ERROR_REG, &context.error_reg));
if (context.error_reg & RC522_PCD_BUFFER_OVFL_BIT) {
return RC522_ERR_PCD_FIFO_BUFFER_OVERFLOW;
}
else if (context.error_reg & RC522_PCD_PARITY_ERR_BIT) {
RC522_LOGD("parity error detected");
return RC522_ERR_PCD_PARITY_CHECK_FAILED;
}
else if (context.error_reg & RC522_PCD_PROTOCOL_ERR_BIT) {
RC522_LOGD("protocol error detected");
return RC522_ERR_PCD_PROTOCOL_ERROR;
}
if (out_context) {
memcpy(out_context, &context, sizeof(context));
}
return ESP_OK;
}
static esp_err_t rc522_picc_receive(const rc522_handle_t rc522, const rc522_picc_transaction_context_t *context,
rc522_picc_transaction_result_t *out_result)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(context == NULL);
RC522_CHECK(context->transaction == NULL);
RC522_CHECK(out_result == NULL);
RC522_CHECK_BYTES(&context->transaction->bytes);
uint8_t fifo_level = 0;
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &fifo_level));
if (fifo_level < 1) {
RC522_LOGD("fifo empty (irq=0x%02" RC522_X ")", context->interrupts);
return RC522_ERR_PCD_FIFO_EMPTY;
}
RC522_CHECK(fifo_level > out_result->bytes.length);
rc522_picc_transaction_result_t result = {
.bytes = {
.ptr = out_result->bytes.ptr, // Use buffer provided by caller
.length = fifo_level,
},
};
RC522_RETURN_ON_ERROR(rc522_pcd_fifo_read(rc522, &result.bytes));
if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) {
char debug_buffer[64];
rc522_buffer_to_hex_str(result.bytes.ptr, result.bytes.length, debug_buffer, sizeof(debug_buffer));
RC522_LOGD("picc >> %s", debug_buffer);
}
if (context->transaction->rx_align) {
RC522_LOGD("applying mask (rx_align=%d)", context->transaction->rx_align);
// Apply mask for rx_align..7 of the first byte
result.bytes.ptr[0] &= (0xFF << context->transaction->rx_align);
}
// RxLastBits[2:0] indicates the number of valid bits in the last received byte.
// If this value is 0, the whole byte is valid.
RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CONTROL_REG, &result.valid_bits));
result.valid_bits &= 0x07;
if (result.valid_bits) {
RC522_LOGD("not full byte received, valid_bits=%d", result.valid_bits);
}
if (context->error_reg & RC522_PCD_COLL_ERR_BIT) {
return RC522_ERR_COLLISION;
}
// Perform CRC_A validation
if (context->transaction->check_crc) {
// We need at least the CRC_A value
// and all 8 bits of the last byte
RC522_CHECK_AND_RETURN(result.bytes.length < 3, ESP_ERR_INVALID_STATE);
RC522_CHECK_AND_RETURN(result.valid_bits != 0, ESP_ERR_INVALID_STATE);
// Verify CRC_A
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522,
&(rc522_bytes_t) { .ptr = result.bytes.ptr, .length = result.bytes.length - 2 },
&crc));
if (memcmp(result.bytes.ptr + result.bytes.length - 2, &crc, sizeof(crc)) != 0) {
return RC522_ERR_CRC_WRONG;
}
}
memcpy(out_result, &result, sizeof(result));
return ESP_OK;
}
esp_err_t rc522_picc_transceive(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction,
rc522_picc_transaction_result_t *out_result)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(transaction == NULL);
rc522_picc_transaction_t transaction_clone = { 0 };
memcpy(&transaction_clone, transaction, sizeof(transaction_clone));
transaction_clone.pcd_command = RC522_PCD_TRANSCEIVE_CMD;
transaction_clone.expected_interrupts = RC522_PCD_RX_IRQ_BIT | RC522_PCD_IDLE_IRQ_BIT;
rc522_picc_transaction_context_t context = { 0 };
RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_send(rc522, &transaction_clone, &context));
if (out_result) {
if (!(context.interrupts & RC522_PCD_RX_IRQ_BIT)) {
return RC522_ERR_RX_TIMEOUT;
}
RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_receive(rc522, &context, out_result));
}
return ESP_OK;
}
inline static esp_err_t rc522_picc_parse_atqa(uint16_t atqa, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(out_atqa == NULL);
out_atqa->source = atqa;
out_atqa->rfu4 = (atqa >> 12) & 0x0F;
out_atqa->prop_coding = (atqa >> 8) & 0x0F;
out_atqa->uid_size = (atqa >> 6) & 0x03;
out_atqa->rfu1 = (atqa >> 5) & 0x01;
out_atqa->anticollision = atqa & 0x1F;
return ESP_OK;
}
static esp_err_t rc522_picc_reqa_or_wupa(const rc522_handle_t rc522, uint8_t picc_cmd, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc_cmd != RC522_PICC_CMD_REQA && picc_cmd != RC522_PICC_CMD_WUPA);
RC522_CHECK(out_atqa == NULL);
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT));
uint8_t buffer[2] = { 0 };
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = &picc_cmd, .length = 1 },
.valid_bits = 7, // REQA and WUPA use short frame format
};
rc522_picc_transaction_result_t transaction_result = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &transaction_result);
if (ret != ESP_OK) {
// Timeouts are expected if no PICC are in the field, log other errors
if (ret != RC522_ERR_RX_TIMER_TIMEOUT && ret != RC522_ERR_RX_TIMEOUT) {
RC522_LOGD("non-timeout error: %04" RC522_X, ret);
}
return ret;
}
if (transaction_result.bytes.length != 2 || transaction_result.valid_bits != 0) {
return RC522_ERR_INVALID_ATQA;
}
uint16_t atqa = (buffer[0] << 8) | buffer[1];
RC522_RETURN_ON_ERROR(rc522_picc_parse_atqa(atqa, out_atqa));
return ESP_OK;
}
inline esp_err_t rc522_picc_reqa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(out_atqa == NULL);
RC522_LOGD("REQA");
return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_REQA, out_atqa);
}
inline esp_err_t rc522_picc_wupa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(out_atqa == NULL);
RC522_LOGD("WUPA");
return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_WUPA, out_atqa);
}
/**
* Resolve collision and SELECT a PICC
*/
esp_err_t rc522_picc_select(const rc522_handle_t rc522, rc522_picc_uid_t *out_uid, uint8_t *out_sak, bool skip_anticoll)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(skip_anticoll && (out_uid == NULL || out_uid->length < RC522_PICC_UID_SIZE_MIN));
bool uid_complete;
bool select_done;
bool use_cascade_tag;
uint8_t cascade_level = 1;
esp_err_t ret;
uint8_t count;
uint8_t check_bit;
uint8_t index;
uint8_t uid_index; // The first index in uid->uidByte[] that is used in the current Cascade Level.
int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level.
uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A
uint8_t buffer_used; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO.
uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received.
uint8_t tx_last_bits = 0; // Used in BitFramingReg. The number of valid bits in the last transmitted byte.
uint8_t *response_buffer;
uint8_t response_length;
rc522_picc_uid_t uid;
if (skip_anticoll) {
memcpy(&uid, out_uid, sizeof(rc522_picc_uid_t));
}
else {
memset(&uid, 0, sizeof(uid));
}
uint8_t sak;
// Description of buffer structure:
// Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
// Byte 1: NVB Number of Valid Bits (in complete command, not just the UID):
// High nibble: complete bytes, Low nibble: Extra bits.
// Byte 2: UID-data or CT See explanation below. CT means Cascade Tag.
// Byte 3: UID-data
// Byte 4: UID-data
// Byte 5: UID-data
// Byte 6: BCC Block Check Character - XOR of bytes 2-5
// Byte 7: CRC_A
// Byte 8: CRC_A
//
// The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level.
//
// Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels)
// UID size Cascade level Byte2 Byte3 Byte4 Byte5
// ======== ============= ===== ===== ===== =====
// 4 bytes 1 uid0 uid1 uid2 uid3
// 7 bytes 1 CT uid0 uid1 uid2
// 2 uid3 uid4 uid5 uid6
// 10 bytes 1 CT uid0 uid1 uid2
// 2 CT uid3 uid4 uid5
// 3 uid6 uid7 uid8 uid9
// Prepare MFRC522
RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT));
// Repeat Cascade Level loop until we have a complete UID.
uid_complete = false;
while (!uid_complete) {
RC522_LOGD("cascade_level=%d, uid.length=%d", cascade_level, uid.length);
// Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2.
switch (cascade_level) {
case 1:
buffer[0] = RC522_PICC_CMD_SEL_CL1;
uid_index = 0;
// When we know that the UID has more than 4 bytes
use_cascade_tag = uid.length > 4;
break;
case 2:
buffer[0] = RC522_PICC_CMD_SEL_CL2;
uid_index = 3;
// When we know that the UID has more than 7 bytes
use_cascade_tag = uid.length > 7;
break;
case 3:
buffer[0] = RC522_PICC_CMD_SEL_CL3;
uid_index = 6;
use_cascade_tag = false; // Never used in CL3.
break;
default:
return ESP_FAIL; // TODO: use custom err
break;
}
RC522_LOGD("cl=%d, uid_index=%d, use_cascade_tag=%d", cascade_level, uid_index, use_cascade_tag);
// How many UID bits are known in this Cascade Level?
current_level_known_bits = skip_anticoll ? (4 * 8) : 0;
// Copy the known bits from uid.uidByte[] to buffer[]
index = 2; // destination index in buffer[]
if (use_cascade_tag) {
buffer[index++] = RC522_PICC_CMD_CT;
}
// The number of bytes needed to represent the known bits for this level.
uint8_t bytes_to_copy = current_level_known_bits / 8 + (current_level_known_bits % 8 ? 1 : 0);
if (bytes_to_copy) {
// Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag
uint8_t max_bytes = use_cascade_tag ? 3 : 4;
if (bytes_to_copy > max_bytes) {
bytes_to_copy = max_bytes;
}
for (count = 0; count < bytes_to_copy; count++) {
buffer[index++] = uid.value[uid_index + count];
}
}
// Now that the data has been copied we need to include the 8 bits in CT in current_level_known_bits
if (use_cascade_tag) {
current_level_known_bits += 8;
}
// Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations.
select_done = false;
while (!select_done) {
// Find out how many bits and bytes to send and receive.
if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT.
RC522_LOGD("SELECT (cl=%d)", cascade_level);
// NVB - Number of Valid Bits: Seven whole bytes
buffer[1] = 0x70;
// Calculate BCC - Block Check Character
buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5];
// Calculate CRC_A
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 7 }, &crc));
buffer[7] = crc.lsb;
buffer[8] = crc.msb;
tx_last_bits = 0; // 0 => All 8 bits are valid.
buffer_used = 9;
// Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx)
response_buffer = &buffer[6];
response_length = 3;
}
else { // This is an ANTICOLLISION.
RC522_LOGD("ANTICOLLISION (cl=%d)", cascade_level);
tx_last_bits = current_level_known_bits % 8;
count = current_level_known_bits / 8; // Number of whole bytes in the UID part.
index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs
buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits
buffer_used = index + (tx_last_bits ? 1 : 0);
// Store response in the unused part of buffer
response_buffer = &buffer[index];
response_length = sizeof(buffer) - index;
}
// Set bit adjustments
// Having a separate variable is overkill. But it makes the next line easier to read.
rx_align = tx_last_bits;
// RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0]
RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, (rx_align << 4) + tx_last_bits));
// Transmit the buffer and receive the response.
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = buffer_used },
.rx_align = rx_align,
.valid_bits = tx_last_bits,
};
rc522_picc_transaction_result_t transaction_result = {
.bytes = { .ptr = response_buffer, .length = response_length },
};
ret = rc522_picc_transceive(rc522, &transaction, &transaction_result);
if (ret == ESP_OK) {
response_length = transaction_result.bytes.length;
tx_last_bits = transaction_result.valid_bits;
}
if (ret == RC522_ERR_COLLISION) { // More than one PICC in the field => collision.
RC522_LOGD("collision detected (cl=%d, skip_anticoll=%d)", cascade_level, skip_anticoll);
if (skip_anticoll) {
// If we are skipping anticoll, we should not have collisions
RC522_LOGD("unexpected collision detected");
return ret;
}
// CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0]
uint8_t value_of_coll_reg;
rc522_pcd_read(rc522, RC522_PCD_COLL_REG, &value_of_coll_reg);
if (value_of_coll_reg & RC522_PCD_COLL_POS_NOT_VALID_BIT) {
// Without a valid collision position we cannot continue
RC522_LOGD("collision position not valid, coll_poss[4:0] out of range");
return RC522_ERR_COLLISION_UNSOLVABLE;
}
uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32.
if (collision_pos == 0) {
collision_pos = 32;
}
if (collision_pos <= current_level_known_bits) { // No progress - should not happen
RC522_LOGD("collision_pos (%d) <= current_level_known_bits (%d)",
collision_pos,
current_level_known_bits);
return RC522_ERR_COLLISION_UNSOLVABLE;
}
// Choose the PICC with the bit set.
current_level_known_bits = collision_pos;
count = current_level_known_bits % 8; // The bit to modify
check_bit = (current_level_known_bits - 1) % 8;
index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First byte is index 0.
buffer[index] |= (1 << check_bit);
}
else if (ret != ESP_OK) {
RC522_LOGD("transceive failed");
return ret;
}
else { // ESP_OK
if (current_level_known_bits >= 32) { // This was a SELECT.
// No more anticollision
// We continue below outside the while.
select_done = true;
}
else { // This was an ANTICOLLISION.
// We now have all 32 bits of the UID in this Cascade Level
current_level_known_bits = 32;
// Run loop again to do the SELECT.
}
}
} // End of while (!selectDone)
RC522_LOGD("SELECT (cl=%d) done", cascade_level);
// We do not check the CBB - it was constructed by us above.
// Copy the found UID bytes from buffer[] to uid.uidByte[]
index = (buffer[2] == RC522_PICC_CMD_CT) ? 3 : 2; // source index in buffer[]
bytes_to_copy = (buffer[2] == RC522_PICC_CMD_CT) ? 3 : 4;
for (count = 0; count < bytes_to_copy; count++) {
uid.value[uid_index + count] = buffer[index++];
}
// Check response SAK (Select Acknowledge)
if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A).
RC522_LOGD("invalid sak");
return RC522_ERR_INVALID_SAK;
}
// Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed
// anymore.
// compiler complains about uninitialized response_buffer even is
// no chance that response_buffer is NULL here, so ignore warning here
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(
rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = response_buffer, .length = 1 }, &crc));
if (memcmp(response_buffer + 1, &crc, sizeof(crc)) != 0) {
RC522_LOGD("crc wrong");
return RC522_ERR_CRC_WRONG;
}
buffer[2] = crc.lsb;
buffer[3] = crc.msb;
if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes
cascade_level++;
}
else {
uid_complete = true;
sak = response_buffer[0];
}
#pragma GCC diagnostic pop
} // End of while (!uidComplete)
// Set correct uid.size
uid.length = 3 * cascade_level + 1;
RC522_LOGD("sak=0x%02" RC522_X, sak);
if (out_uid) {
memcpy(out_uid, &uid, sizeof(uid));
}
if (out_sak) {
*out_sak = sak;
}
return ESP_OK;
}
/**
* Checks if PICC is still in the PCD field
*/
esp_err_t rc522_picc_heartbeat(
const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_uid_t *out_uid, uint8_t *out_sak)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_CHECK(picc->state != RC522_PICC_STATE_ACTIVE && picc->state != RC522_PICC_STATE_ACTIVE_H);
esp_err_t ret = ESP_OK;
const uint8_t retries = 5;
uint8_t retry = 1;
do {
rc522_picc_atqa_desc_t atqa;
if (retry <= 2) {
if (picc->state == RC522_PICC_STATE_ACTIVE) {
ret = rc522_picc_reqa(rc522, &atqa);
}
else if (picc->state == RC522_PICC_STATE_ACTIVE_H) {
ret = rc522_picc_wupa(rc522, &atqa);
}
}
else {
if ((ret = rc522_picc_reqa(rc522, &atqa)) != ESP_OK) {
ret = rc522_picc_wupa(rc522, &atqa);
}
}
if (ret == ESP_OK) {
break;
}
rc522_delay_ms(5);
}
while (retry++ < retries);
if (ret != ESP_OK) {
return ret;
}
rc522_picc_uid_t uid;
uint8_t sak;
memcpy(&uid, &picc->uid, sizeof(rc522_picc_uid_t));
ret = rc522_picc_select(rc522, &uid, &sak, true);
if (ret != ESP_OK) {
return ret;
}
if (picc->sak != sak) {
return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH;
}
for (uint8_t i = 0; i < uid.length; i++) {
if (picc->uid.value[i] != uid.value[i]) {
return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH;
}
}
if (out_uid) {
memcpy(out_uid, &uid, sizeof(uid));
}
if (out_sak) {
*out_sak = sak;
}
return ESP_OK;
}
esp_err_t rc522_picc_uid_to_str(const rc522_picc_uid_t *uid, char *buffer, uint8_t buffer_size)
{
RC522_CHECK(uid == NULL);
RC522_CHECK(buffer == NULL);
RC522_CHECK(buffer_size < RC522_PICC_UID_STR_BUFFER_SIZE_MAX);
return rc522_buffer_to_hex_str(uid->value, uid->length, buffer, buffer_size);
}
esp_err_t rc522_picc_halta(const rc522_handle_t rc522, rc522_picc_t *picc)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
RC522_LOGD("HALTA");
uint8_t buffer[4] = { 0 };
buffer[0] = RC522_PICC_CMD_HLTA;
buffer[1] = 0;
rc522_pcd_crc_t crc = { 0 };
RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 2 }, &crc));
buffer[2] = crc.lsb;
buffer[3] = crc.msb;
rc522_picc_transaction_t transaction = {
.bytes = { .ptr = buffer, .length = sizeof(buffer) },
};
esp_err_t ret = rc522_picc_transceive(rc522, &transaction, NULL);
// If the PICC responds with any modulation during a period of 1 ms after the HLTA,
// response shall be interpreted as 'not acknowledge', so timeout is not an error.
if (ret == ESP_OK) {
return RC522_ERR_HLTA_NOT_ACKED;
}
else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_RX_TIMEOUT) {
RC522_RETURN_ON_ERROR(rc522_picc_set_state(rc522, picc, RC522_PICC_STATE_HALT, true));
return ESP_OK;
}
return ret;
}
esp_err_t rc522_picc_set_state(
const rc522_handle_t rc522, rc522_picc_t *picc, rc522_picc_state_t new_state, bool fire_event)
{
RC522_CHECK(rc522 == NULL);
RC522_CHECK(picc == NULL);
esp_err_t ret = ESP_OK;
if (picc->state == new_state) {
return ESP_OK;
}
RC522_LOGD("changing state from %d to %d (fire=%d)", picc->state, new_state, fire_event);
rc522_picc_state_t old_state = picc->state;
picc->state = new_state;
if (fire_event) {
rc522_picc_state_changed_event_t event_data = {
.old_state = old_state,
.picc = picc,
};
if ((ret = rc522_dispatch_event(rc522, RC522_EVENT_PICC_STATE_CHANGED, &event_data, sizeof(event_data)))
!= ESP_OK) {
RC522_LOGW("picc_state_changed event dispatch failed (err=%04" RC522_X ")", ret);
}
}
return ret;
}
rc522_picc_type_t rc522_picc_get_type(const rc522_picc_t *picc)
{
RC522_CHECK(picc == NULL);
uint8_t sak = picc->sak;
// http://www.nxp.com/documents/application_note/AN10833.pdf
// Section: Coding of Select Acknowledge (SAK)
// ignore 8th (iso14443 starts with LSBit = bit 1)
// fixes wrong type for manufacturer Infineon (http://nfc-tools.org/index.php?title=ISO14443A)
sak &= 0x7F;
switch (sak) {
case 0x09:
return RC522_PICC_TYPE_MIFARE_MINI;
case 0x08:
return RC522_PICC_TYPE_MIFARE_1K;
case 0x18:
return RC522_PICC_TYPE_MIFARE_4K;
case 0x00:
return RC522_PICC_TYPE_MIFARE_UL;
case 0x10:
case 0x11:
return RC522_PICC_TYPE_MIFARE_PLUS;
case 0x01:
return RC522_PICC_TYPE_TNP3XXX;
case 0x20:
return picc->atqa.source == 0x4400 ? RC522_PICC_TYPE_MIFARE_DESFIRE : RC522_PICC_TYPE_ISO_14443_4;
case 0x40:
return RC522_PICC_TYPE_ISO_18092;
default:
return RC522_PICC_TYPE_UNKNOWN;
}
}
char *rc522_picc_type_name(rc522_picc_type_t type)
{
switch (type) {
/* SAK-determined */
case RC522_PICC_TYPE_ISO_14443_4:
return "PICC compliant with ISO/IEC 14443-4";
case RC522_PICC_TYPE_ISO_18092:
return "PICC compliant with ISO/IEC 18092 (NFC)";
case RC522_PICC_TYPE_MIFARE_MINI:
return "MIFARE Mini, 320 bytes";
case RC522_PICC_TYPE_MIFARE_1K:
return "MIFARE 1K";
case RC522_PICC_TYPE_MIFARE_4K:
return "MIFARE 4K";
case RC522_PICC_TYPE_MIFARE_UL:
// SAK=0x00 for both of these families
return "MIFARE Ultralight or NXP NTAG";
case RC522_PICC_TYPE_MIFARE_PLUS:
return "MIFARE Plus";
case RC522_PICC_TYPE_MIFARE_DESFIRE:
return "MIFARE DESFire";
case RC522_PICC_TYPE_TNP3XXX:
return "MIFARE TNP3XXX";
/* Further ID processing */
case RC522_PICC_TYPE_MIFARE_UL_:
return "MIFARE Ultralight";
case RC522_PICC_TYPE_MIFARE_UL_C:
return "MIFARE Ultralight C";
case RC522_PICC_TYPE_MIFARE_UL_EV1_1:
return "MIFARE Ultralight EV1 (80 bytes)";
case RC522_PICC_TYPE_MIFARE_UL_EV1_2:
return "MIFARE Ultralight EV1 (164 bytes)";
case RC522_PICC_TYPE_MIFARE_UL_NANO:
return "MIFARE Ultralight NANO";
case RC522_PICC_TYPE_MIFARE_UL_AES:
return "MIFARE Ultralight AES";
case RC522_PICC_TYPE_NTAG2xx:
return "NTAG2xx";
case RC522_PICC_TYPE_NTAG213:
return "NTAG213";
case RC522_PICC_TYPE_NTAG215:
return "NTAG215";
case RC522_PICC_TYPE_NTAG216:
return "NTAG216";
case RC522_PICC_TYPE_UNDEFINED:
case RC522_PICC_TYPE_UNKNOWN:
default:
return "unknown";
}
}
esp_err_t rc522_picc_print(const rc522_picc_t *picc)
{
RC522_CHECK(picc == NULL);
char uid_str[RC522_PICC_UID_STR_BUFFER_SIZE_MAX];
RC522_RETURN_ON_ERROR(rc522_picc_uid_to_str(&picc->uid, uid_str, sizeof(uid_str)));
ESP_LOGI(TAG, "");
ESP_LOGI(TAG, "╔══════════════╗");
ESP_LOGI(TAG, "║ ║ Type: %s", rc522_picc_type_name(picc->type));
ESP_LOGI(TAG, "║ RFID ║ UID: %s", uid_str);
ESP_LOGI(TAG, "║ CARD ║ ATQA: 0x%04" RC522_X, picc->atqa.source);
ESP_LOGI(TAG, "║ ║ SAK: 0x%02" RC522_X, picc->sak);
ESP_LOGI(TAG, "╚══════════════╝");
ESP_LOGI(TAG, "");
return ESP_OK;
}