1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
16782: drivers/mfrc522: add new driver r=benpicco a=HendrikVE

### Contribution description

This PR adds support for the MFRC522. It is quite common in the Arduino world and it is quite cheap. The driver connects to the MFRC522 via SPI and is heavily based on the Arduino driver available [here](https://github.com/miguelbalboa/rfid). Basically it was ported, but with several improvements in readability and documentation.

### Testing procedure

The given (manual) test provides single commands for some driver functions.

19201: cpu/gd32v: add periph_i2c support r=benpicco a=gschorcht

### Contribution description

This PR provides the `periph_i2c` support and is one of a bunch of PRs that complete the peripheral drivers for GD32VF103.

The driver is a modified version of the driver for STM32F1 with some changes that were necessary to get it working on GD32V. As  for STM32F1, the driver is using polling instead of interrupts for now. It will be implemented interrupt-driven later.

### Testing procedure

`tests/periph_i2c` as well as a test with any I2C sensor should work. The driver was tested with `tests/driver_l3gxxxx` and `tests/driver_bmp180`.

### Issues/PRs references


Co-authored-by: Hendrik van Essen <hendrik.ve@fu-berlin.de>
Co-authored-by: Gunar Schorcht <gunar@schorcht.net>
This commit is contained in:
bors[bot] 2023-01-31 23:15:41 +00:00 committed by GitHub
commit 718e4a8340
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 5220 additions and 14 deletions

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2023 Gunar Schorcht <gunar@schorcht.net>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup boards_common_gd32v
* @{
*
* @file
* @brief Default I2C configuration for GD32VF103 boards
*
* @author Gunar Schorcht <gunar@schorcht.net>
*/
#ifndef CFG_I2C_DEFAULT_H
#define CFG_I2C_DEFAULT_H
#include "periph_cpu.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name I2C configuration
*
* @note This board may require external pullup resistors for i2c operation.
* @{
*/
static const i2c_conf_t i2c_config[] = {
{
.dev = I2C0,
.speed = I2C_SPEED_NORMAL,
.scl_pin = GPIO_PIN(PORT_B, 6),
.sda_pin = GPIO_PIN(PORT_B, 7),
.rcu_mask = RCU_APB1EN_I2C0EN_Msk,
.irqn = I2C0_EV_IRQn,
},
{
.dev = I2C1,
.speed = I2C_SPEED_NORMAL,
.scl_pin = GPIO_PIN(PORT_B, 10),
.sda_pin = GPIO_PIN(PORT_B, 11),
.rcu_mask = RCU_APB1EN_I2C1EN_Msk,
.irqn = I2C1_EV_IRQn,
}
};
#define I2C_NUMOF ARRAY_SIZE(i2c_config)
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* CFG_I2C_DEFAULT_H */
/** @} */

View File

@ -14,6 +14,7 @@ config BOARD_SEEEDSTUDIO_GD32
select CPU_MODEL_GD32VF103VBT6
select BOARD_HAS_HXTAL
select BOARD_HAS_LXTAL
select HAS_PERIPH_I2C
select HAS_PERIPH_PWM
select HAS_PERIPH_TIMER
select HAS_PERIPH_UART

View File

@ -1,6 +1,7 @@
CPU_MODEL = gd32vf103vbt6
# Put defined MCU peripherals here (in alphabetical order)
FEATURES_PROVIDED += periph_i2c
FEATURES_PROVIDED += periph_pwm
FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_uart

View File

@ -42,7 +42,7 @@ on-board components:
| UART | 2 | yes |
| USART | 3 | yes |
| SPI | 3 | no |
| I2C | 2 x Fast Mode 400 kHz | no |
| I2C | 2 x Fast Mode 400 kHz | yes |
| I2S | 2 | no |
| CAN | 2 x CAN 2.0B with up to 1 Mbps | no |
| PWM | 6 Channels | yes |
@ -64,15 +64,19 @@ MCU pins and their configuration in RIOT.
| MCU Pin | MCU Peripheral | RIOT Peripheral | Board Function | Remark |
|:--------|:---------------|:-----------------|:---------------|:-----------------------------|
| PA0 | | | BTN0 | |
| PA0 | | BTN0 | KEY1 | |
| PA9 | USART0 TX | UART_DEV(0) TX | UART TX | |
| PA10 | USART0 RX | UART_DEV(0) RX | UART RX | |
| PB0 | | PWM_DEV(0) CH0 | LED1 green | |
| PB1 | | PWM_DEV(0) CH1 | LED2 blue | |
| PB5 | | | LED0 red | |
| PB6 | I2C0 SCL | I2C_DEV(0) SCL | | |
| PB7 | I2C0 SDA | I2C_DEV(0) SDA | | |
| PB8 | | PWM_DEV(1) CH0 | | N/A if CAN is used |
| PB9 | | PWM_DEV(2) CH1 | | N/A if CAN is used |
| PC13 | | | BTN1 | |
| PB9 | | PWM_DEV(1) CH1 | | N/A if CAN is used |
| PB10 | I2C1 SCL | I2C_DEV(1) SCL | | |
| PB11 | I2C1 SDA | I2C_DEV(1) SDA | | |
| PC13 | | BTN1 | KEY2 | |
## Flash the board

View File

@ -36,6 +36,7 @@
#include "periph_cpu.h"
#include "periph_common_conf.h"
#include "cfg_i2c_default.h"
#include "cfg_timer_default.h"
#include "cfg_uart_default.h"

View File

@ -14,6 +14,7 @@ config BOARD_SIPEED_LONGAN_NANO
select CPU_MODEL_GD32VF103CBT6
select BOARD_HAS_HXTAL
select BOARD_HAS_LXTAL
select HAS_PERIPH_I2C
select HAS_PERIPH_PWM
select HAS_PERIPH_TIMER
select HAS_PERIPH_UART

View File

@ -1,6 +1,7 @@
CPU_MODEL = gd32vf103cbt6
# Put defined MCU peripherals here (in alphabetical order)
FEATURES_PROVIDED += periph_i2c
FEATURES_PROVIDED += periph_pwm
FEATURES_PROVIDED += periph_timer
FEATURES_PROVIDED += periph_uart

View File

@ -36,7 +36,7 @@ on-board components:
| UART | - | yes |
| USART | 3 | yes |
| SPI | 3 | no |
| I2C | 2 x Fast Mode 400 kHz | no |
| I2C | 2 x Fast Mode 400 kHz | yes |
| I2S | 2 | no |
| CAN | 2 x CAN 2.0B with up to 1 Mbps | no |
| PWM | 6 Channels | yes |
@ -58,12 +58,17 @@ MCU pins and their configuration in RIOT.
| MCU Pin | MCU Peripheral | RIOT Peripheral | Board Function | Remark |
|:--------|:---------------|:-----------------|:---------------|:-----------------------------|
| PA0 | BOOT0 | | BTN0 | |
| PA1 | | PWM_DEV(0) CH0 | LED1 green | |
| PA2 | | PWM_DEV(0) CH1 | LED2 blue | |
| PA9 | USART0 TX | UART_DEV(0) TX | UART TX | |
| PA10 | USART0 RX | UART_DEV(0) RX | UART RX | |
| PB6 | I2C0 SCL | I2C_DEV(0) SCL | | |
| PB7 | I2C0 SDA | I2C_DEV(0) SDA | | |
| PB8 | | PWM_DEV(1) CH0 | | N/A if CAN is used |
| PB9 | | PWM_DEV(2) CH1 | | N/A if CAN is used |
| PB9 | | PWM_DEV(1) CH1 | | N/A if CAN is used |
| PB10 | I2C1 SCL | I2C_DEV(1) SCL | | |
| PB11 | I2C1 SDA | I2C_DEV(1) SDA | | |
| PC13 | | | LED0 red | |
## Flashing the Device

View File

@ -36,6 +36,7 @@
#include "periph_cpu.h"
#include "periph_common_conf.h"
#include "cfg_i2c_default.h"
#include "cfg_timer_default.h"
#include "cfg_uart_default.h"

View File

@ -24,6 +24,7 @@
#include "cpu.h"
#include "clic.h"
#include "kernel_defines.h"
#include "macros/units.h"
#ifdef __cplusplus
extern "C" {
@ -243,8 +244,10 @@ typedef struct {
*/
#define HAVE_I2C_SPEED_T
typedef enum {
I2C_SPEED_NORMAL, /**< normal mode: ~100kbit/s */
I2C_SPEED_FAST, /**< fast mode: ~400kbit/s */
I2C_SPEED_LOW = KHZ(10), /**< low speed mode: ~10kit/s */
I2C_SPEED_NORMAL = KHZ(100), /**< normal mode: ~100kbit/s */
I2C_SPEED_FAST = KHZ(400), /**< fast mode: ~400kbit/s */
I2C_SPEED_FAST_PLUS = MHZ(1), /**< fast plus mode: ~1Mbit/s */
} i2c_speed_t;
/** @} */
#endif /* ndef DOXYGEN */
@ -253,10 +256,12 @@ typedef enum {
* @brief I2C configuration options
*/
typedef struct {
uint32_t addr; /**< device base address */
gpio_t scl; /**< SCL pin */
gpio_t sda; /**< SDA pin */
i2c_speed_t speed; /**< I2C speed */
I2C_Type *dev; /**< i2c device */
i2c_speed_t speed; /**< i2c bus speed */
gpio_t scl_pin; /**< scl pin number */
gpio_t sda_pin; /**< sda pin number */
uint32_t rcu_mask; /**< bit in clock enable register */
uint8_t irqn; /**< I2C event interrupt number */
} i2c_conf_t;
/**

View File

@ -11549,8 +11549,8 @@ typedef struct { /*!< (@ 0x40002C00) WWDGT Struct
//#define GPIOC_BASE 0x40011000UL
//#define GPIOD_BASE 0x40011400UL
//#define GPIOE_BASE 0x40011800UL
//#define I2C0_BASE 0x40005400UL
//#define I2C1_BASE 0x40005800UL
#define I2C0_BASE 0x40005400UL
#define I2C1_BASE 0x40005800UL
//#define ECLIC_BASE 0xD2000000UL
//#define PMU_BASE 0x40007000UL
//#define RCU_BASE 0x40021000UL

415
cpu/gd32v/periph/i2c.c Normal file
View File

@ -0,0 +1,415 @@
/*
* Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de>
* 2014 FU Berlin
* 2018 Inria
* 2018 HAW Hamburg
* 2023 Gunar Schorcht <gunar@schorcht.net>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_gd32v
* @ingroup drivers_periph_i2c
* @{
*
* @file
* @brief Low-level I2C driver implementation
*
* This driver is a modified copy of the I2C driver for the STM32F1 family.
*
* @note This implementation only implements the 7-bit addressing polling mode.
*
* @author Peter Kietzmann <peter.kietzmann@haw-hamburg.de>
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Thomas Eichinger <thomas.eichinger@fu-berlin.de>
* @author Kaspar Schleiser <kaspar@schleiser.de>
* @author Toon Stegen <toon.stegen@altran.com>
* @author Vincent Dupont <vincent@otakeys.com>
* @author Víctor Ariño <victor.arino@triagnosys.com>
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
* @author Kevin Weiss <kevin.weiss@haw-hamburg.de>
* @author Gunar Schorcht <gunar@schorcht.net>
*
* @}
*/
#include <assert.h>
#include <stdint.h>
#include <errno.h>
#include "cpu.h"
#include "irq.h"
#include "mutex.h"
#include "pm_layered.h"
#include "panic.h"
#include "periph/i2c.h"
#include "periph/gpio.h"
#include "periph_conf.h"
/* Some DEBUG statements may cause delays that alter i2c functionality */
#define ENABLE_DEBUG 0
#include "debug.h"
#define TICK_TIMEOUT (0xFFFF)
#define I2C_IRQ_PRIO (1)
#define I2C_FLAG_READ (I2C_READ)
#define I2C_FLAG_WRITE (0)
#define ERROR_FLAGS (I2C_STAT0_AERR_Msk | I2C_STAT0_LOSTARB_Msk | I2C_STAT0_BERR_Msk)
/* static function definitions */
static void _init(i2c_t dev);
static void _init_pins(i2c_t dev);
static void _init_clk(I2C_Type *i2c, uint32_t speed);
static void _deinit_pins(i2c_t dev);
static int _start(I2C_Type *dev, uint8_t address_byte, uint8_t flags,
size_t length);
static int _stop(I2C_Type *dev);
static int _is_sr1_mask_set(I2C_Type *i2c, uint32_t mask, uint8_t flags);
static inline int _wait_for_bus(I2C_Type *i2c);
/**
* @brief Array holding one pre-initialized mutex for each I2C device
*/
static mutex_t locks[I2C_NUMOF];
void i2c_init(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_init(&locks[dev]);
assert(i2c_config[dev].dev != NULL);
/* Configure pins in idle state as open drain outputs to keep the bus lines
* in HIGH state */
_deinit_pins(dev);
periph_clk_en(APB1, i2c_config[dev].rcu_mask);
_init(dev);
periph_clk_dis(APB1, i2c_config[dev].rcu_mask);
}
static void _init_pins(i2c_t dev)
{
/* This is needed in case the remapped pins are used */
if (i2c_config[dev].scl_pin == GPIO_PIN(PORT_B, 8) ||
i2c_config[dev].sda_pin == GPIO_PIN(PORT_B, 9)) {
/* The remapping periph clock must first be enabled */
RCU->APB2EN |= RCU_APB2EN_AFEN_Msk;
/* Then the remap can occur */
AFIO->PCF0 |= AFIO_PCF0_I2C0_REMAP_Msk;
}
gpio_init_af(i2c_config[dev].scl_pin, GPIO_AF_OUT_OD);
gpio_init_af(i2c_config[dev].sda_pin, GPIO_AF_OUT_OD);
}
static void _init_clk(I2C_Type *i2c, uint32_t speed)
{
/* disable device and set ACK bit */
i2c->CTL0 = I2C_CTL0_ACKEN_Msk;
/* configure I2C clock */
i2c->CTL1 = (CLOCK_APB1 / MHZ(1)) | I2C_CTL1_ERRIE_Msk;
i2c->CKCFG = CLOCK_APB1 / (2 * speed);
i2c->RT = (CLOCK_APB1 / 1000000) + 1;
/* configure device */
i2c->SADDR0 |= (1 << 14); /* datasheet: bit 14 should be kept 1 */
i2c->SADDR0 &= ~I2C_SADDR0_ADDFORMAT_Msk; /* make sure we are in 7-bit address mode */
/* Clear flags */
i2c->STAT0 &= ~ERROR_FLAGS;
/* enable device */
i2c->CTL0 |= I2C_CTL0_I2CEN_Msk;
}
static void _init(i2c_t dev)
{
I2C_Type *i2c = i2c_config[dev].dev;
/* make peripheral soft reset */
i2c->CTL0 |= I2C_CTL0_SRESET_Msk;
i2c->CTL0 &= ~I2C_CTL0_SRESET_Msk;
/* configure I2C clock */
_init_clk(i2c, i2c_config[dev].speed);
}
static void _deinit_pins(i2c_t dev)
{
/* GD32V doesn't support GPIO_OD_PU mode, i.e. external pull-ups required */
gpio_init(i2c_config[dev].scl_pin, GPIO_OD);
gpio_init(i2c_config[dev].sda_pin, GPIO_OD);
gpio_set(i2c_config[dev].scl_pin);
gpio_set(i2c_config[dev].sda_pin);
}
void i2c_acquire(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_lock(&locks[dev]);
/* block DEEP_SLEEP mode */
pm_block(GD32V_PM_DEEPSLEEP);
periph_clk_en(APB1, i2c_config[dev].rcu_mask);
/* set the alternate function of the pins */
_init_pins(dev);
/* enable device */
i2c_config[dev].dev->CTL0 |= I2C_CTL0_I2CEN_Msk;
}
void i2c_release(i2c_t dev)
{
assert(dev < I2C_NUMOF);
/* disable device */
i2c_config[dev].dev->CTL0 &= ~(I2C_CTL0_I2CEN_Msk);
_wait_for_bus(i2c_config[dev].dev);
/* Disabling the clock switches off the I2C controller, which results in
* LOW bus lines. To avoid that the used GPIOs then draw some milliamps
* of current via the pull-up resistors, the used GPIOs are set back to
* GPIO_OD mode and HIGH. */
_deinit_pins(dev);
periph_clk_dis(APB1, i2c_config[dev].rcu_mask);
/* unblock DEEP_SLEEP mode */
pm_unblock(GD32V_PM_DEEPSLEEP);
mutex_unlock(&locks[dev]);
}
int i2c_read_bytes(i2c_t dev, uint16_t address, void *data, size_t length,
uint8_t flags)
{
assert(dev < I2C_NUMOF);
I2C_Type *i2c = i2c_config[dev].dev;
DEBUG("[i2c] read_bytes: Starting\n");
/* Repeated start of read operations is not supported. This is exactly the
* case if the previous transfer was a read operation (I2C_STAT1_TR == 0)
* and was not terminated by a STOP condition (I2C_STAT1_I2CBSY == 1) and
* the START condition is to be used (I2C_NOSTART == 0).
*/
if (((i2c->STAT1 & (I2C_STAT1_I2CBSY_Msk | I2C_STAT1_TR_Msk)) == I2C_STAT1_I2CBSY_Msk) &&
!(flags & I2C_NOSTART)) {
return -EOPNOTSUPP;
}
int ret = _start(i2c, (address << 1) | I2C_FLAG_READ, flags, length);
if (ret < 0) {
if (ret == -ETIMEDOUT) {
_init(dev);
}
return ret;
}
for (size_t i = 0; i < length; i++) {
if (i + 1 == length && !(flags & I2C_NOSTOP)) {
/* If data is already in the buffer we must clear before sending
* a stop. If I2C_NOSTOP was called up to two extra bytes may be
* clocked out on the line however they get ignored in the firmware.*/
if ((i2c->STAT0 & I2C_STAT0_RBNE_Msk) && (length == 1)) {
((uint8_t*)data)[i] = i2c->DATA;
return _stop(i2c);
}
/* STOP must also be sent before final read */
ret = _stop(i2c);
if (ret < 0) {
return ret;
}
}
/* Wait for reception to complete */
ret = _is_sr1_mask_set(i2c, I2C_STAT0_RBNE_Msk, flags);
if (ret < 0) {
return ret;
}
((uint8_t*)data)[i] = i2c->DATA;
}
DEBUG("[i2c] read_bytes: Finished reading bytes\n");
if (flags & I2C_NOSTOP) {
return 0;
}
return _wait_for_bus(i2c);
}
int i2c_write_bytes(i2c_t dev, uint16_t address, const void *data,
size_t length, uint8_t flags)
{
assert(dev < I2C_NUMOF);
int ret;
I2C_Type *i2c = i2c_config[dev].dev;
assert(i2c != NULL);
DEBUG("[i2c] write_bytes: Starting\n");
/* Length is 0 in start since we don't need to preset the stop bit */
ret = _start(i2c, (address << 1) | I2C_FLAG_WRITE, flags, 0);
if (ret < 0) {
if (ret == -ETIMEDOUT) {
_init(dev);
}
return ret;
}
/* Send out data bytes */
for (size_t i = 0; i < length; i++) {
DEBUG("[i2c] write_bytes: Waiting for TX reg to be free\n");
ret = _is_sr1_mask_set(i2c, I2C_STAT0_TBE_Msk, flags);
if (ret < 0) {
return ret;
}
DEBUG("[i2c] write_bytes: TX is free so send byte\n");
i2c->DATA = ((uint8_t*)data)[i];
}
/* Wait for tx reg to be empty so other calls will no interfere */
ret = _is_sr1_mask_set(i2c, I2C_STAT0_TBE_Msk, flags);
if (ret < 0) {
return ret;
}
if (flags & I2C_NOSTOP) {
return 0;
}
else {
/* End transmission */
DEBUG("[i2c] write_bytes: Ending transmission\n");
ret = _stop(i2c);
if (ret < 0) {
return ret;
}
DEBUG("[i2c] write_bytes: STOP condition was send out\n");
}
return _wait_for_bus(i2c);
}
static int _start(I2C_Type *i2c, uint8_t address_byte, uint8_t flags,
size_t length)
{
assert(i2c != NULL);
if ((flags & I2C_ADDR10) ||
(!(i2c->STAT1 & I2C_STAT1_I2CBSY_Msk) && (flags & I2C_NOSTART))) {
return -EOPNOTSUPP;
}
/* Clear flags */
i2c->STAT0 &= ~ERROR_FLAGS;
if (!(flags & I2C_NOSTART)) {
DEBUG("[i2c] start: Generate start condition\n");
/* Generate start condition */
i2c->CTL0 |= I2C_CTL0_START_Msk | I2C_CTL0_ACKEN_Msk;
/* Wait for SB flag to be set */
int ret = _is_sr1_mask_set(i2c, I2C_STAT0_SBSEND_Msk, flags & ~I2C_NOSTOP);
if (ret < 0) {
return ret;
}
DEBUG("[i2c] start: Start condition generated\n");
DEBUG("[i2c] start: Generating address\n");
/* Send address and read/write flag */
if ((i2c->STAT0 & I2C_STAT0_SBSEND_Msk)) {
i2c->DATA = (address_byte);
}
if (!(flags & I2C_NOSTOP) && length == 1) {
i2c->CTL0 &= ~(I2C_CTL0_ACKEN_Msk);
}
/* Wait for ADDR flag to be set */
ret = _is_sr1_mask_set(i2c, I2C_STAT0_ADDSEND_Msk, flags & ~I2C_NOSTOP);
if (ret == -EIO){
/* Since NACK happened during start it means no device connected */
return -ENXIO;
}
/* Wait until I2C_STAT0_ADDSEND is cleared. To clear I2C_STAT0_ADDSEND
* it is necessary to read STAT0 followed by reading STAT1 */
while ((i2c->STAT0 & I2C_STAT0_ADDSEND_Msk) && i2c->STAT1) { }
if (!(flags & I2C_NOSTOP) && length == 1) {
/* Stop must also be sent before final read */
i2c->CTL0 |= (I2C_CTL0_STOP_Msk);
}
DEBUG("[i2c] start: Address generated\n");
return ret;
}
return 0;
}
static int _is_sr1_mask_set(I2C_Type *i2c, uint32_t mask, uint8_t flags)
{
DEBUG("[i2c] _is_sr1_mask_set: waiting to set %04X\n", (uint16_t)mask);
uint16_t tick = TICK_TIMEOUT;
while (tick--) {
uint32_t sr1 = i2c->STAT0;
if (sr1 & I2C_STAT0_AERR_Msk) {
DEBUG("[i2c] is_sr1_mask_set: NACK received\n");
i2c->STAT0 &= ~ERROR_FLAGS;
if (!(flags & I2C_NOSTOP)) {
_stop(i2c);
}
return -EIO;
}
if ((sr1 & I2C_STAT0_LOSTARB_Msk) || (sr1 & I2C_STAT0_BERR_Msk)) {
DEBUG("[i2c] is_sr1_mask_set: arb lost or bus ERROR_FLAGS\n");
i2c->STAT0 &= ~ERROR_FLAGS;
_stop(i2c);
return -EAGAIN;
}
if (sr1 & mask) {
i2c->STAT0 &= ~ERROR_FLAGS;
return 0;
}
}
/*
* If timeout occurs this means a problem that must be handled on a higher
* level. A SWRST is recommended by the datasheet.
*/
i2c->STAT0 &= ~ERROR_FLAGS;
_stop(i2c);
return -ETIMEDOUT;
}
static int _stop(I2C_Type *i2c)
{
/* send STOP condition */
DEBUG("[i2c] stop: Generate stop condition\n");
i2c->CTL0 &= ~(I2C_CTL0_ACKEN_Msk);
i2c->CTL0 |= I2C_CTL0_STOP_Msk;
uint16_t tick = TICK_TIMEOUT;
while ((i2c->CTL0 & I2C_CTL0_STOP_Msk) && tick--) {}
if (!tick) {
return -ETIMEDOUT;
}
DEBUG("[i2c] stop: Stop condition succeeded\n");
if (_wait_for_bus(i2c) < 0) {
return -ETIMEDOUT;
}
DEBUG("[i2c] stop: Bus is free\n");
return 0;
}
static inline int _wait_for_bus(I2C_Type *i2c)
{
uint16_t tick = TICK_TIMEOUT;
while ((i2c->STAT1 & I2C_STAT1_I2CBSY_Msk) && tick--) {}
if (!tick) {
return -ETIMEDOUT;
}
return 0;
}

View File

@ -20,6 +20,8 @@ warning: Member HDC1000_PARAM_ADDR \(macro definition\) of
warning: Member HDC1000_PARAM_I2C \(macro definition\) of
warning: Member HDC1000_PARAM_RENEW_INTERVAL \(macro definition\) of
warning: Member HDC1000_PARAM_RES \(macro definition\) of
warning: Member i2c_config\[\] \(variable\) of
warning: Member I2C_NUMOF \(macro definition\) of
warning: Member LED[0-9]_ENABLE_PORT \(macro definition\) of
warning: Member LED[0-9]_IS_INVERTED \(macro definition\) of
warning: Member LED[0-9]_MASK \(macro definition\) of

View File

@ -110,6 +110,7 @@ rsource "lsm303agr/Kconfig"
rsource "ltc4150/Kconfig"
rsource "mag3110/Kconfig"
rsource "matrix_keypad/Kconfig"
rsource "mfrc522/Kconfig"
rsource "mhz19/Kconfig"
rsource "mma8x5x/Kconfig"
rsource "mma7660/Kconfig"

878
drivers/include/mfrc522.h Normal file
View File

@ -0,0 +1,878 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup drivers_mfrc522 MFRC522 RFID controller
* @ingroup drivers_actuators
* @brief Device driver for the NXP MFRC522 RFID controller
*
* With this driver MIFARE and NTAG tags/keys can be read and written contactless.
*
* Supported features:
* - Communication (Crypto1) with MIFARE Classic (1k, 4k, Mini)
* - Communication (Crypto1) with MIFARE Classic compatible PICCs
* - Firmware self check of MFRC522
* - Set the UID, write to sector 0, and unbrick Chinese UID changeable MIFARE cards
*
* Partially supported features:
* - Communication with MIFARE Ultralight
* - Other PICCs (Ntag216)
*
* Data sheet available here: https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf
*
* This driver is a port of a driver originally written for Arduino, you can find
* the original here: https://github.com/miguelbalboa/rfid (currently based on
* commit 0a568b45baf1852883630e90ea125786e88f5322)
*
* @{
*
* @author Hendrik van Essen <hendrik.ve@fu-berlin.de>
* @file
*/
#ifndef MFRC522_H
#define MFRC522_H
#ifdef __cplusplus
extern "C"
{
#endif
#include "stdbool.h"
#include "periph/spi.h"
#include "mfrc522_regs.h"
/**
* @brief Maximum size for uid byte array
*/
#define MFRC522_UID_MAX_SIZE 10
/**
* @brief The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK
*/
#define MFRC522_MF_ACK 0xA
/**
* @brief A Mifare Crypto1 key is 6 bytes
*/
#define MFRC522_MF_KEY_SIZE 6
/**
* @brief MFRC522 device initialization parameters
*/
typedef struct {
spi_t spi_dev; /**< SPI bus the controller is connected to */
spi_clk_t spi_clk; /**< SPI clock speed */
gpio_t sck_pin; /**< SCK pin */
gpio_t miso_pin; /**< MISO pin */
gpio_t mosi_pin; /**< MOSI pin */
gpio_t cs_pin; /**< Slave select pin */
gpio_t rst_pin; /**< Reset pin */
} mfrc522_params_t;
/**
* @brief MFRC522 device data structure type
*/
typedef struct {
mfrc522_params_t params; /**< Device initialization parameters */
} mfrc522_t;
/**
* @brief A struct used for passing the UID of a PICC
*/
typedef struct {
uint8_t size; /**< Number of bytes in the UID. 4, 7 or 10 */
uint8_t uid_byte[MFRC522_UID_MAX_SIZE]; /**< UID */
uint8_t sak; /**< The SAK (Select acknowledge) byte returned from
the PICC after successful selection */
} mfrc522_uid_t;
/**
* @brief A struct used for passing a MIFARE Crypto1 key
*/
typedef struct {
uint8_t key_byte[MFRC522_MF_KEY_SIZE]; /**< Crypto1 key bytes */
} mfrc522_mifare_key_t;
/**
* @brief PICC types
*/
typedef enum {
MFRC522_PICC_TYPE_ISO_14443_4, /**< PICC compliant with ISO/IEC 14443-4 */
MFRC522_PICC_TYPE_ISO_18092, /**< PICC compliant with ISO/IEC 18092 (NFC) */
MFRC522_PICC_TYPE_MIFARE_MINI, /**< MIFARE Classic protocol, 320 bytes */
MFRC522_PICC_TYPE_MIFARE_1K, /**< MIFARE Classic protocol, 1KB */
MFRC522_PICC_TYPE_MIFARE_4K, /**< MIFARE Classic protocol, 4KB */
MFRC522_PICC_TYPE_MIFARE_UL, /**< MIFARE Ultralight or Ultralight C */
MFRC522_PICC_TYPE_MIFARE_PLUS, /**< MIFARE Plus */
MFRC522_PICC_TYPE_MIFARE_DESFIRE, /**< MIFARE DESFire */
MFRC522_PICC_TYPE_TNP3XXX, /**< Only mentioned in NXP AN 10833 MIFARE
Type Identification Procedure */
MFRC522_PICC_TYPE_NOT_COMPLETE, /**< SAK indicates UID is not complete */
MFRC522_PICC_TYPE_UNKNOWN /**< unknown type */
} mfrc522_picc_type_t;
/**
* @brief Sets the bits given in mask in register reg
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] reg Register to write to
* @param[in] mask Bitmask with the bits to set
*/
void mfrc522_pcd_set_register_bitmask(mfrc522_t *dev, mfrc522_pcd_register_t reg, uint8_t mask);
/**
* @brief Clears the bits given in mask from register reg
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] reg Register to write to
* @param[in] mask Bitmask with the bits to clear
*/
void mfrc522_pcd_clear_register_bitmask(mfrc522_t *dev, mfrc522_pcd_register_t reg, uint8_t mask);
/**
* @brief Use the CRC coprocessor in the MFRC522 to calculate a CRC_A
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] data Data to transfer to the FIFO for CRC calculation
* @param[in] length Number of bytes to transfer
* @param[out] result Result buffer. Result is written to result[0..1], low
* byte first.
*
* @retval 0 on success
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_pcd_calculate_crc(mfrc522_t *dev, const uint8_t *data, uint8_t length, uint8_t *result);
/**
* @brief Initialization
*
* @param[out] dev Device descriptor of the MFRC522
* @param[in] params Parameters for device initialization
*
* @retval 0 Success
* @retval -EINVAL Invalid CS pin/line
* @retval -ENXIO Invalid device
*/
int mfrc522_pcd_init(mfrc522_t *dev, const mfrc522_params_t *params);
/**
* @brief Performs a soft reset on the MFRC522 chip and waits for it to be ready
* again
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_pcd_reset(mfrc522_t *dev);
/**
* @brief Turns the antenna on by enabling pins TX1 and TX2.
* After a reset these pins are disabled.
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_pcd_antenna_on(mfrc522_t *dev);
/**
* @brief Turns the antenna off by disabling pins TX1 and TX2
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_pcd_antenna_off(mfrc522_t *dev);
/**
* @brief Get the current MFRC522 receiver gain
*
* @param[in] dev Device descriptor of the MFRC522
*
* @return Receiver gain
*/
mfrc522_pcd_rx_gain_t mfrc522_pcd_get_antenna_gain(mfrc522_t *dev);
/**
* @brief Set the MFRC522 receiver gain
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] rx_gain Antenna gain
*/
void mfrc522_pcd_set_antenna_gain(mfrc522_t *dev, mfrc522_pcd_rx_gain_t rx_gain);
/**
* @brief Set the MFRC522 to soft power-down mode
*
* @note Only soft power down mode is available through software.
*
* @note Calling any other function that uses MFRC522_REG_COMMAND will disable
* soft power down mode! For more details about power control, refer to
* the datasheet - page 33 (8.6)
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_pcd_soft_power_down(mfrc522_t *dev);
/**
* @brief Set the MFRC522 to leave soft power-down mode
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_pcd_soft_power_up(mfrc522_t *dev);
/**
* @brief Executes MFRC522_CMD_TRANSCEIVE.
*
* @note CRC validation can only be done if back_data and back_len are specified
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] send_data Data to transfer to the FIFO
* @param[in] send_len Number of bytes to transfer to the FIFO
* @param[out] back_data Buffer if data should be read back after executing
* the command, otherwise NULL
* @param[inout] back_len Max number of bytes to write to *back_data.
* Returns number of bytes returned.
* @param[inout] valid_bits Number of valid bits in the last byte. 0 for 8
* valid bits
* @param[in] rx_align Defines the bit position in back_data[0] for the
* first bit received
* @param[in] check_crc If true, the last two bytes of the response are
* assumed to be a CRC_A that must be validated
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_pcd_transceive_data(mfrc522_t *dev,
const uint8_t *send_data, uint8_t send_len,
uint8_t *back_data, uint8_t *back_len,
uint8_t *valid_bits, uint8_t rx_align, bool check_crc);
/**
* @brief Transfers data to MFRC522's FIFO, executes a command, waits for
* completion and transfers data back from the FIFO
*
* @note CRC validation can only be done if back_data and back_len are specified
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] command The command to execute
* @param[in] wait_irq The bits in the ComIrqReg register that signals
* successful completion of the command
* @param[in] send_data Data to transfer to the FIFO
* @param[in] send_len Number of bytes to transfer to the FIFO
* @param[out] back_data Buffer if data should be read back after executing
* the command, otherwise NULL
* @param[inout] back_len Max number of bytes to write to *back_data.
* Returns number of bytes returned.
* @param[inout] valid_bits Number of valid bits in the last byte. 0 for 8
* valid bits.
* @param[in] rx_align Defines the bit position in back_data[0] for the
* first bit received
* @param[in] check_crc True => The last two bytes of the response is
* assumed to be a CRC_A that must be validated
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_pcd_communicate_with_picc(mfrc522_t *dev, mfrc522_pcd_command_t command,
uint8_t wait_irq,
const uint8_t *send_data, uint8_t send_len,
uint8_t *back_data, uint8_t *back_len,
uint8_t *valid_bits, uint8_t rx_align, bool check_crc);
/**
* @brief Transmits REQA, Type A. Invites PICCs in state IDLE to go to READY and
* prepare for anti-collision or selection. 7 bit frame.
*
* @note When two PICCs are in the field at the same time we often get
* -ETIMEDOUT - probably due do bad antenna design.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[out] buffer_atqa Buffer to store the ATQA in
* @param[inout] buffer_size Buffer size, at least two bytes. Also number of
* bytes returned on success.
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_picc_request_a(mfrc522_t *dev, uint8_t *buffer_atqa, uint8_t *buffer_size);
/**
* @brief Transmits WUPA, Type A. Invites PICCs in state IDLE and HALT to go to
* READY(*) and prepare for anti-collision or selection. 7 bit frame.
*
* @note When two PICCs are in the field at the same time we often get
* -ETIMEDOUT - probably due do bad antenna design.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[out] buffer_atqa Buffer to store the ATQA in
* @param[inout] buffer_size Buffer size, at least two bytes. Also number of
* bytes returned on success.
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_picc_wakeup_a(mfrc522_t *dev, uint8_t *buffer_atqa, uint8_t *buffer_size);
/**
* @brief Transmits REQA (Type A) or WUPA (Type A) commands
*
* @note When two PICCs are in the field at the same time we often get
* -ETIMEDOUT - probably due do bad antenna design.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] command Command to send - MFRC522_PICC_CMD_ISO_14443_REQA
* or MFRC522_PICC_CMD_ISO_14443_WUPA
* @param[out] buffer_atqa Buffer to store the ATQA in
* @param[inout] buffer_size Buffer size, at least two bytes. Also number of
* bytes returned on success.
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL if command was neither MFRC522_PICC_CMD_ISO_14443_REQA nor
* MFRC522_PICC_CMD_ISO_14443_WUPA
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_picc_reqa_or_wupa(mfrc522_t *dev, mfrc522_picc_command_t command,
uint8_t *buffer_atqa, uint8_t *buffer_size);
/**
* @brief Transmits SELECT/ANTICOLLISION commands to select a single PICC
*
* @note Before calling this function the PICCs must be placed in the READY(*)
* state by calling mfrc522_picc_request_a() or mfrc522_picc_wakeup_a().
* On success:
* - The chosen PICC is in state ACTIVE(*) and all other PICCs have
* returned to state IDLE/HALT. (Figure 7 of the ISO/IEC 14443-3 draft.)
* - The UID size and value of the chosen PICC is returned in *uid along
* with the SAK.
*
* A PICC UID consists of 4, 7 or 10 bytes.
* Only 4 bytes can be specified in a SELECT command, so for the longer UIDs two
* or three iterations are used:
*
* UID size Number of UID bytes Cascade levels Example of PICC
* ======== =================== ============== ===============
* single 4 1 MIFARE Classic
* double 7 2 MIFARE Ultralight
* triple 10 3 Not currently in use?
*
* @param[in] dev Device descriptor of the MFRC522
* @param[inout] uid Normally output, but can also be used to supply a
* known UID
* @param[in] valid_bits Number of known UID bits supplied in *uid.
* Normally 0. If set you must also supply uid->size.
*
* @retval 0 on success
* @retval -ECANCELED on internal error
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_picc_select(mfrc522_t *dev, mfrc522_uid_t *uid, uint8_t valid_bits);
/**
* @brief Instructs a PICC in state ACTIVE(*) to go to state HALT
*
* @param[in] dev Device descriptor of the MFRC522
*
* @retval 0 on success
* @retval -EIO on communication error
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_picc_halt_a(mfrc522_t *dev);
/**
* @brief Executes the MFRC522 MFAuthent command
*
* This command manages MIFARE authentication to enable a secure communication
* to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. The authentication is
* described in the MFRC522 datasheet section 10.3.1.9 and
* http://www.nxp.com/documents/data_sheet/MF1S503x.pdf section 10.1. For use
* with MIFARE Classic PICCs. The PICC must be selected - i.e. in state
* ACTIVE(*) - before calling this function. Remember to call
* mfrc522_pcd_stop_crypto1() after communicating with the authenticated PICC -
* otherwise no new communications can start.
*
* All keys are set to FFFFFFFFFFFFh at chip delivery.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] command MFRC522_PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B
* @param[in] block_addr Block number. See numbering in the comments in the .h file.
* @param[in] key Crypteo1 key to use (6 bytes)
* @param[in] uid PICC's UID
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_pcd_authenticate(mfrc522_t *dev, mfrc522_picc_command_t command,
uint8_t block_addr, const mfrc522_mifare_key_t *key,
const mfrc522_uid_t *uid);
/**
* @brief Used to exit the PCD from its authenticated state
*
* @note Remember to call this function after communicating with an
* authenticated PICC - otherwise no new communications can start.
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_pcd_stop_crypto1(mfrc522_t *dev);
/**
* @brief Read 16 bytes (+ 2 bytes CRC_A) from the active PICC
*
* @note For MIFARE Classic the sector containing the block must be
* authenticated before calling this function.
*
* For MIFARE Ultralight only addresses 00h to 0Fh are decoded. The MF0ICU1
* returns a NAK for higher addresses. The MF0ICU1 responds to the READ command
* by sending 16 bytes starting from the page address defined by the command
* argument. For example; if block_addr is 03h then pages 03h, 04h, 05h, 06h are
* returned. A roll-back is implemented: If block_addr is 0Eh, then the contents
* of pages 0Eh, 0Fh, 00h and 01h are returned.
*
* The buffer must be at least 18 bytes because a CRC_A is also returned.
* Checks the CRC_A before returning.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr MIFARE Classic: The block (0-0xff) number.
* MIFARE Ultralight: The first page to return data from.
* @param[out] buffer Buffer to store the data in
* @param[inout] buffer_size Buffer size, at least 18 bytes. Also number of
* bytes returned on success.
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_read(mfrc522_t *dev, uint8_t block_addr, uint8_t *buffer, uint8_t *buffer_size);
/**
* @brief Write 16 bytes to the active PICC
*
* @note For MIFARE Classic the sector containing the block must be
* authenticated before calling this function.
*
* For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". Even
* though 16 bytes are transferred to the Ultralight PICC, only the least
* significant 4 bytes (bytes 0 to 3) are written to the specified address. It
* is recommended to set the remaining bytes 04h to 0Fh to all logic 0.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr MIFARE Classic: The block (0-0xff) number.
* MIFARE Ultralight: The page (2-15) to write to.
* @param[in] buffer The 16 bytes to write to the PICC
* @param[in] buffer_size Buffer size, must be at least 16 bytes. Exactly 16
* bytes are written.
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_write(mfrc522_t *dev, uint8_t block_addr,
const uint8_t *buffer, uint8_t buffer_size);
/**
* @brief Write a 4 byte page to the active MIFARE Ultralight PICC
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] page The page (2-15) to write to
* @param[in] buffer The 4 bytes to write to the PICC
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_ultralight_write(mfrc522_t *dev, uint8_t page, const uint8_t *buffer);
/**
* @brief Subtract the operand from the value of the addressed block, and store
* the result in the Transfer Buffer
*
* MIFARE Decrement subtracts the delta from the value of the addressed block,
* and stores the result in a volatile memory. For MIFARE Classic only. The
* sector containing the block must be authenticated before calling this
* function. Only for blocks in "value block" mode, ie with access bits
* [C1 C2 C3] = [110] or [001]. Use MIFARE_Transfer() to store the result in a
* block.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr Block (0-0xff) number
* @param[in] delta Number to be subtracted from the value of block block_addr
*
* @retval 0 on success
* @retval -ECANCELED on internal error
*/
int mfrc522_mifare_decrement(mfrc522_t *dev, uint8_t block_addr, int32_t delta);
/**
* @brief Add the operand to the value of the addressed block, and store the
* result in the Transfer Buffer
*
* MIFARE Increment adds the delta to the value of the addressed block, and
* stores the result in a volatile memory. For MIFARE Classic only. The sector
* containing the block must be authenticated before calling this function. Only
* for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001].
* Use MIFARE_Transfer() to store the result in a block.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr Block (0-0xff) number
* @param[in] delta Number to be added to the value of block block_addr
*
* @retval 0 on success
* @retval -ECANCELED on internal error
*/
int mfrc522_mifare_increment(mfrc522_t *dev, uint8_t block_addr, int32_t delta);
/**
* @brief Copies the value of the addressed block into the Transfer Buffer
*
* MIFARE Restore copies the value of the addressed block into a volatile memory.
* For MIFARE Classic only. The sector containing the block must be
* authenticated before calling this function. Only for blocks in "value block"
* mode, i.e. with access bits [C1 C2 C3] = [110] or [001]. Use
* mfrc522_mifare_transfer() to store the result in a block.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr Block (0-0xff) number
*
* @retval 0 on success
* @retval -ECANCELED on internal error
*/
int mfrc522_mifare_restore(mfrc522_t *dev, uint8_t block_addr);
/**
* @brief Write the value from the Transfer Buffer into destination block
*
* MIFARE Transfer writes the value stored in the volatile memory into one
* MIFARE Classic block. For MIFARE Classic only. The sector containing the
* block must be authenticated before calling this function. Only for blocks in
* "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001].
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr Block (0-0xff) number
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_transfer(mfrc522_t *dev, uint8_t block_addr);
/**
* @brief Helper routine to read the current value from a Value Block
*
* @note Only for MIFARE Classic and only for blocks in "value block" mode,
* that is with access bits [C1 C2 C3] = [110] or [001]. The sector
* containing the block must be authenticated before calling this function.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr Block (0x00-0xff) number
* @param[out] value Current value of the Value Block
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_get_value(mfrc522_t *dev, uint8_t block_addr, int32_t *value);
/**
* @brief Helper routine to write a specific value into a Value Block
*
* @note Only for MIFARE Classic and only for blocks in "value block" mode,
* that is with access bits [C1 C2 C3] = [110] or [001]. The sector
* containing the block must be authenticated before calling this function.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] block_addr Block (0x00-0xff) number
* @param[in] value New value of the Value Block
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_set_value(mfrc522_t *dev, uint8_t block_addr, int32_t value);
/**
* @brief Authenticate with a NTAG216
*
* @note Only for NTAG216
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] password Password (must have a size of exactly 4 bytes)
* @param[out] p_ack result (must have a size of exactly 2 bytes)
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_pcd_ntag216_auth(mfrc522_t *dev, const uint8_t *password, uint8_t p_ack[]);
/**
* @brief Wrapper for MIFARE protocol communication
*
* Adds CRC_A, executes the Transceive command and checks that the response is
* MFRC522_MF_ACK or a timeout.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] send_data Data to transfer to the FIFO. Do NOT include the CRC_A.
* @param[in] send_len Number of bytes in send_data
* @param[in] accept_timeout If true, then a timeout is also a success
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_pcd_mifare_transceive(mfrc522_t *dev,
const uint8_t *send_data, uint8_t send_len,
bool accept_timeout);
/**
* @brief Translates the SAK (Select Acknowledge) to a PICC type
*
* @param[in] sak The SAK byte returned from mfrc522_picc_select()
*
* @return PICC type
*/
mfrc522_picc_type_t mfrc522_picc_get_type(uint8_t sak);
/**
* @brief Returns the name for the PICC type.
*
* @param[in] picc_type PICC type enum
*
* @return PICC type name
*/
const char *mfrc522_picc_get_type_string(mfrc522_picc_type_t picc_type);
/**
* @brief Calculates the bit pattern needed for the specified access bits. In
* the [C1 C2 C3] tuples C1 is MSB (=4) and C3 is LSB (=1).
*
* @param[out] access_bit_buffer Pointer to byte 6, 7 and 8 in the sector
* trailer. Bytes [0..2] will be set.
* @param[in] g0 Access bits [C1 C2 C3] for block 0 (for
* sectors 0-31) or blocks 0-4 (for sectors 32-39)
* @param[in] g1 Access bits [C1 C2 C3] for block 1 (for
* sectors 0-31) or blocks 5-9 (for sectors 32-39)
* @param[in] g2 Access bits [C1 C2 C3] for block 2 (for
* sectors 0-31) or blocks 10-14 (for sectors 32-39)
* @param[in] g3 Access bits [C1 C2 C3] for the sector trailer,
* block 3 (for sectors 0-31) or block 15 (for sectors 32-39)
*/
void mfrc522_mifare_set_access_bits(uint8_t *access_bit_buffer,
uint8_t g0, uint8_t g1, uint8_t g2, uint8_t g3);
/**
* @brief Performs the "magic sequence" needed to get Chinese UID changeable
* Mifare cards to allow writing to sector 0, where the card UID is stored.
*
* @note Note that you do not need to have selected the card through REQA or WUPA,
* this sequence works immediately when the card is in the reader vicinity.
* This means you can use this method even on "bricked" cards that your
* reader does not recognise anymore (see mfrc522_mifare_unbrick_uid_sector).
* Of course with non-bricked devices, you are free to select them before
* calling this function.
*
* @param[in] dev Device descriptor of the MFRC522
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_open_uid_backdoor(mfrc522_t *dev);
/**
* @brief Read entire block 0, including all manufacturer data, and overwrites
* that block with the new UID, a freshly calculated BCC, and the original
* manufacturer data.
*
* @note It assumes a default KEY A of 0xFFFFFFFFFFFF. Make
* sure to have selected the PICC before this function is called and that
* the UID of the PICC is changeable.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] uid PICC's UID
* @param[in] new_uid New UID to set on PICC
* @param[in] new_uid_size Size of new UID
*
* @retval 0 on success
* @retval -ECANCELED on internal error
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_set_uid(mfrc522_t *dev, mfrc522_uid_t *uid,
const uint8_t *new_uid, uint8_t new_uid_size);
/**
* @brief Reset entire sector 0 to zeroes, so the card can be read again by readers
*
* @param[in] dev Device descriptor of the MFRC522
*
* @retval 0 on success
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_mifare_unbrick_uid_sector(mfrc522_t *dev);
/**
* @brief Checks whether a new card could be detected
*
* @note Only "new" cards in state IDLE are invited. Sleeping cards in state
* HALT are ignored.
*
* @param[in] dev Device descriptor of the MFRC522
*
* @return true if new card was detected, false otherwise
*/
bool mfrc522_picc_is_new_card_present(mfrc522_t *dev);
/**
* @brief Simple wrapper around mfrc522_picc_select()
*
* @note Remember to call mfrc522_picc_is_new_card_present(),
* mfrc522_picc_request_a() or mfrc522_picc_wakeup_a() first.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] uid PICC's UID
*
* @retval 0 on success
* @retval -ECANCELED on internal error
* @retval -ECONNABORTED when a collision occurred
* @retval -EINVAL on invalid argument
* @retval -EIO on communication error
* @retval -ENOBUFS when a buffer is too small to receive data
* @retval -ETIMEDOUT on timeout
*/
int mfrc522_picc_read_card_serial(mfrc522_t *dev, mfrc522_uid_t *uid);
/**
* @brief Dump debug info about the connected PCD to stdout
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_pcd_dump_version_to_serial(mfrc522_t *dev);
/**
* @brief Dump debug info about the selected PICC to stdout
*
* @note On success the PICC is halted after dumping the data.
* For MIFARE Classic the factory default key of 0xFFFFFFFFFFFF is tried.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] uid PICC's UID
*/
void mfrc522_picc_dump_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid);
/**
* @brief Dump card info (UID, SAK, Type) about the selected PICC to stdout
*
* @param[in] uid PICC's UID
*/
void mfrc522_picc_dump_details_to_serial(mfrc522_uid_t *uid);
/**
* @brief Dump memory contents of a MIFARE Classic PICC to stdout
*
* @note On success the PICC is halted after dumping the data.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] uid PICC's UID
* @param[in] picc_type PICC type enum
* @param[in] key Key A used for all sectors.
*/
void mfrc522_picc_dump_mifare_classic_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid,
mfrc522_picc_type_t picc_type,
mfrc522_mifare_key_t *key);
/**
* @brief Dump memory contents of a sector of a MIFARE Classic PICC to stdout
*
* Uses mfrc522_pcd_authenticate(), mfrc522_mifare_read() and
* mfrc522_pcd_stop_crypto1(). Always uses MFRC522_PICC_CMD_MF_AUTH_KEY_A
* because only Key A can always read the sector trailer access bits.
*
* @param[in] dev Device descriptor of the MFRC522
* @param[in] uid PICC's UID
* @param[in] key Key A for the sector.
* @param[in] sector The sector to dump, 0..39
*/
void mfrc522_picc_dump_mifare_classic_sector_to_serial(mfrc522_t *dev, mfrc522_uid_t *uid,
mfrc522_mifare_key_t *key, uint8_t sector);
/**
* @brief Dump memory contents of a MIFARE Ultralight PICC to stdout
*
* @param[in] dev Device descriptor of the MFRC522
*/
void mfrc522_picc_dump_mifare_ultralight_to_serial(mfrc522_t *dev);
/**
* @brief Perform a self-test of the MFRC522
*
* (See section 16.1.1)
*
* @param[in] dev Device descriptor of the MFRC522
*
* @return true if the test passed, false otherwise and if no firmware reference is available.
*/
bool mfrc522_pcd_perform_self_test(mfrc522_t *dev);
#ifdef __cplusplus
}
#endif
#endif /* MFRC522_H */
/** @} */

19
drivers/mfrc522/Kconfig Normal file
View File

@ -0,0 +1,19 @@
# Copyright (c) 2022 Freie Universität Berlin
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
config MODULE_MFRC522
bool "MFRC522 RFID controller"
depends on HAS_PERIPH_GPIO
depends on HAS_PERIPH_SPI
depends on TEST_KCONFIG
select MODULE_PERIPH_GPIO
select MODULE_PERIPH_SPI
select MODULE_ZTIMER
select MODULE_ZTIMER_USEC
select MODULE_ZTIMER_MSEC
help
Device driver for the NXP MFRC522 RFID controller

1
drivers/mfrc522/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,5 @@
FEATURES_REQUIRED += periph_gpio
FEATURES_REQUIRED += periph_spi
USEMODULE += ztimer_usec
USEMODULE += ztimer_msec

View File

@ -0,0 +1,2 @@
USEMODULE_INCLUDES_mfrc522 := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mfrc522)

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_actuators
* @brief Default configuration for the MFRC522 controller
*
* @{
*
* @author Hendrik van Essen <hendrik.ve@fu-berlin.de>
* @file
*/
#ifndef MFRC522_PARAMS_H
#define MFRC522_PARAMS_H
#include <stdbool.h>
#include "periph/spi.h"
#include "mfrc522_regs.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @name Default configuration parameters for MFRC522 driver
* @{
*/
#ifndef MFRC522_PARAM_SPI_DEV
#define MFRC522_PARAM_SPI_DEV SPI_DEV(0) /**< Default SPI device */
#endif
#ifndef MFRC522_PARAM_SPI_CLK
#define MFRC522_PARAM_SPI_CLK SPI_CLK_5MHZ /**< Default SPI speed */
#endif
#ifndef MFRC522_PARAM_SCK_PIN
#define MFRC522_PARAM_SCK_PIN GPIO_PIN(0, 18) /**< Default SCK pin */
#endif
#ifndef MFRC522_PARAM_MISO_PIN
#define MFRC522_PARAM_MISO_PIN GPIO_PIN(0, 19) /**< Default MISO pin */
#endif
#ifndef MFRC522_PARAM_MOSI_PIN
#define MFRC522_PARAM_MOSI_PIN GPIO_PIN(0, 23) /**< Default MOSI pin */
#endif
#ifndef MFRC522_PARAM_CS_PIN
#define MFRC522_PARAM_CS_PIN GPIO_PIN(0, 5) /**< Default CS pin */
#endif
#ifndef MFRC522_PARAM_RST_PIN
#define MFRC522_PARAM_RST_PIN GPIO_PIN(0, 17) /**< Default RST pin */
#endif
#ifndef MFRC522_PARAMS
#define MFRC522_PARAMS \
{ \
.spi_dev = MFRC522_PARAM_SPI_DEV, \
.spi_clk = MFRC522_PARAM_SPI_CLK, \
.sck_pin = MFRC522_PARAM_SCK_PIN, \
.miso_pin = MFRC522_PARAM_MISO_PIN, \
.mosi_pin = MFRC522_PARAM_MOSI_PIN, \
.cs_pin = MFRC522_PARAM_CS_PIN, \
.rst_pin = MFRC522_PARAM_RST_PIN, \
} /**< Struct with default configuration parameters */
#endif /* MFRC522_PARAMS */
/**@}*/
/**
* @brief Allocate some memory to store the actual configuration
*/
static const mfrc522_params_t mfrc522_params[] =
{
MFRC522_PARAMS
};
#ifdef __cplusplus
}
#endif
#endif /* MFRC522_PARAMS_H */
/** @} */

File diff suppressed because it is too large Load Diff

2207
drivers/mfrc522/mfrc522.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
include ../Makefile.tests_common
USEMODULE += mfrc522
USEMODULE += ztimer
USEMODULE += ztimer_msec
USEMODULE += shell
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,11 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
nucleo-l011k4 \
samd10-xmini \
stm32f030f4-demo \
#

View File

@ -0,0 +1,20 @@
# MFRC522 RFID Card Reader
## Overview
This test application demonstrates the usage of the MFRC522 driver interface
and can be used to test a MFRC522 RFID card reader with shell commands.
## Usage
The following shell commands are available:
* **reset** Reset the device.
* **antenna_on** Turn the antenna on.
* **antenna_off** Turn the antenna off.
* **set_antenna_gain** Set antenna gain.
* **get_antenna_gain** Get antenna gain.
* **firmware_version** Print the firmware version.
* **picc_dump** Dumps debug info about a scanned PICC.
* **scan_uid** Scan repeatedly until a PICC's UID is scanned.
* **set_uid** Set the UID of a scanned PICC.
* **self_test** Run the self test.

302
tests/driver_mfrc522/main.c Normal file
View File

@ -0,0 +1,302 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @brief Test application for the MFRC522 controller
* @author Hendrik van Essen <hendrik.ve@fu-berlin.de>
* @file
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ztimer.h"
#include "shell.h"
#include "mfrc522.h"
#include "mfrc522_params.h"
static mfrc522_t mfrc522_dev;
/* Stores scanned ID read from RFID Module */
static uint8_t read_card[4];
int get_uid(mfrc522_uid_t *uid, bool stop_read)
{
int rc = 0;
/* Getting ready for Reading PICCs
* If a new PICC placed to RFID reader continue
*/
if (!(rc = mfrc522_picc_is_new_card_present(&mfrc522_dev))) {
puts("No card detected ...");
return 0;
}
puts("Card detected. Read card serial ...");
/* Since a PICC placed get Serial and continue */
rc = mfrc522_picc_read_card_serial(&mfrc522_dev, uid);
if (rc != 0) {
return 0;
}
printf("Scanned PICC's UID: ");
for (uint8_t i = 0; i < uid->size; i++) {
read_card[i] = uid->uid_byte[i];
printf("%x", read_card[i]);
}
printf("\n");
/* Stop reading */
if (stop_read) {
mfrc522_picc_halt_a(&mfrc522_dev);
}
return 1;
}
int scan_uid(int argc, char **argv)
{
(void)argc;
(void)argv;
mfrc522_uid_t uid;
uint8_t success_read;
int i = 0;
do {
if (i >= 1) {
printf("Repeat scan (%d/20) ...\n", i + 1);
}
success_read = get_uid(&uid, true);
ztimer_sleep(ZTIMER_MSEC, 500);
i++;
} while (!success_read && i < 20);
return 0;
}
int self_test(int argc, char **argv)
{
(void)argc;
(void)argv;
bool success = mfrc522_pcd_perform_self_test(&mfrc522_dev);
success ? printf("SUCCESS\n") : printf("FAILED\n");
/* After self-test the device is unusable => repeat the initialization routine */
int rc = 0;
if ((rc = mfrc522_pcd_init(&mfrc522_dev, &mfrc522_params[0])) != 0) {
printf("Initialization failed! rc = %d", rc);
return 1;
}
return 0;
}
int reset(int argc, char **argv)
{
(void)argc;
(void)argv;
mfrc522_pcd_reset(&mfrc522_dev);
return 0;
}
int antenna_on(int argc, char **argv)
{
(void)argc;
(void)argv;
mfrc522_pcd_antenna_on(&mfrc522_dev);
return 0;
}
int antenna_off(int argc, char **argv)
{
(void)argc;
(void)argv;
mfrc522_pcd_antenna_off(&mfrc522_dev);
return 0;
}
int set_antenna_gain(int argc, char **argv)
{
(void)argc;
(void)argv;
if (argc != 2) {
puts("usage: set_antenna_gain <gain (0-5)>");
puts(" 0: 18db, 1: 23db, 2: 33db, 3: 38db, 4: 43db, 5: 48db");
return 1;
}
else {
uint8_t input = atoi(argv[1]);
mfrc522_pcd_rx_gain_t gain;
switch (input) {
case 0:
gain = MFRC522_RXGAIN_18_DB;
break;
case 1:
gain = MFRC522_RXGAIN_23_DB;
break;
case 2:
gain = MFRC522_RXGAIN_33_DB;
break;
case 3:
gain = MFRC522_RXGAIN_38_DB;
break;
case 4:
gain = MFRC522_RXGAIN_43_DB;
break;
case 5:
gain = MFRC522_RXGAIN_48_DB;
break;
default:
puts("gain needs to be one of [0-5]");
puts("0: 18db, 1: 23db, 2: 33db, 3: 38db, 4: 43db, 5: 48db");
return 1;
}
mfrc522_pcd_set_antenna_gain(&mfrc522_dev, gain);
}
return 0;
}
int get_antenna_gain(int argc, char **argv)
{
(void)argc;
(void)argv;
mfrc522_pcd_rx_gain_t gain = mfrc522_pcd_get_antenna_gain(&mfrc522_dev);
switch (gain) {
case MFRC522_RXGAIN_18_DB:
/* fall-through */
case MFRC522_RXGAIN_18_DB_2:
printf("gain = MFRC522_RXGAIN_18_DB\n");
break;
case MFRC522_RXGAIN_23_DB:
/* fall-through */
case MFRC522_RXGAIN_23_DB_2:
printf("gain = MFRC522_RXGAIN_23_DB\n");
break;
case MFRC522_RXGAIN_33_DB:
printf("gain = MFRC522_RXGAIN_33_DB\n");
break;
case MFRC522_RXGAIN_38_DB:
printf("gain = MFRC522_RXGAIN_38_DB\n");
break;
case MFRC522_RXGAIN_43_DB:
printf("gain = MFRC522_RXGAIN_43_DB\n");
break;
case MFRC522_RXGAIN_48_DB:
printf("gain = MFRC522_RXGAIN_48_DB\n");
break;
default:
printf("gain = UNKNOWN\n");
break;
}
return 0;
}
int firmware_version(int argc, char **argv)
{
(void)argc;
(void)argv;
mfrc522_pcd_dump_version_to_serial(&mfrc522_dev);
return 0;
}
int picc_dump(int argc, char **argv)
{
(void)argc;
(void)argv;
mfrc522_uid_t uid;
if (get_uid(&uid, false) != 1) {
return 1;
}
mfrc522_picc_dump_to_serial(&mfrc522_dev, &uid);
return 0;
}
int set_uid(int argc, char **argv)
{
(void)argc;
(void)argv;
if (argc != 5) {
puts("usage: set_uid <byte1> <byte2> <byte3> <byte4>");
puts(" <byte> as hex value (e.g. 0xA9 or A9)");
return 1;
}
else {
mfrc522_uid_t uid;
if (get_uid(&uid, false) != 1) {
return 1;
}
uint8_t byte_1 = (uint8_t)strtol(argv[1], NULL, 16);
uint8_t byte_2 = (uint8_t)strtol(argv[2], NULL, 16);
uint8_t byte_3 = (uint8_t)strtol(argv[3], NULL, 16);
uint8_t byte_4 = (uint8_t)strtol(argv[4], NULL, 16);
uint8_t uid_buf[4] = { byte_1, byte_2, byte_3, byte_4 };
int status = mfrc522_mifare_set_uid(&mfrc522_dev, &uid, uid_buf, 4);
printf("status = %d\n", status);
}
return 0;
}
static const shell_command_t shell_commands[] = {
{ "reset", "Reset the device.", reset },
{ "antenna_on", "Turn the antenna on.", antenna_on },
{ "antenna_off", "Turn the antenna off.", antenna_off },
{ "set_antenna_gain", "Set antenna gain.", set_antenna_gain },
{ "get_antenna_gain", "Get antenna gain.", get_antenna_gain },
{ "firmware_version", "Print the firmware version.", firmware_version },
{ "picc_dump", "Dumps debug info about a scanned PICC.", picc_dump },
{ "scan_uid", "Scan repeatedly until a PICC's UID is scanned.", scan_uid },
{ "set_uid", "Set the UID of a scanned PICC.", set_uid },
{ "self_test", "Run the self test.", self_test },
{ NULL, NULL, NULL }
};
int main(void)
{
int rc = 0;
if ((rc = mfrc522_pcd_init(&mfrc522_dev, &mfrc522_params[0])) != 0) {
printf("Initialization failed! rc = %d", rc);
return 1;
}
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}