Skip to content
This repository has been archived by the owner on Sep 20, 2023. It is now read-only.

Commit

Permalink
mfrc522: reworked public/private API. (#349)
Browse files Browse the repository at this point in the history
Fixed the wait for edge block, removed synchronization code.
Added concurrent execution protection via config.
Updated API docs.
  • Loading branch information
jdevelop authored and maruel committed Dec 8, 2018
1 parent 4fc6a0f commit 6f5bd6c
Show file tree
Hide file tree
Showing 6 changed files with 399 additions and 372 deletions.
19 changes: 10 additions & 9 deletions experimental/cmd/mfrc522/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"strconv"
"strings"
"time"

"periph.io/x/periph/conn/gpio/gpioreg"
"periph.io/x/periph/conn/spi/spireg"
Expand Down Expand Up @@ -85,16 +86,20 @@ func mainImpl() error {
return err
}

data, err := rfid.ReadCard(currentAccessMethod, *sector, *block, currentAccessKey)
timeout := 10 * time.Second

data, err := rfid.ReadCard(timeout, currentAccessMethod, *sector, *block, currentAccessKey)
if err != nil {
return err
}
auth, err := rfid.ReadAuth(currentAccessMethod, *sector, currentAccessKey)
auth, err := rfid.ReadAuth(timeout, currentAccessMethod, *sector, currentAccessKey)
if err != nil {
return err
}

access := mfrc522.ParseBlockAccess(auth[6:10])
var access mfrc522.BlocksAccess

access.Init(auth[6:10])

fmt.Printf("RFID sector %d, block %d : %v, auth: %v\n", *sector, *block, data, auth)
fmt.Printf("Permissions: B0: %s, B1: %s, B2: %s, B3/A: %s\n",
Expand All @@ -105,7 +110,7 @@ func mainImpl() error {
)

if *keyCommand {
err = rfid.WriteSectorTrail(commands.PICC_AUTHENT1A,
err = rfid.WriteSectorTrail(timeout, commands.PICC_AUTHENT1A,
*sector,
[6]byte{1, 2, 3, 4, 5, 6},
[6]byte{6, 5, 4, 3, 2, 1},
Expand Down Expand Up @@ -134,11 +139,7 @@ func mainImpl() error {
}
defaultDataBytes[i] = byte(intVal)
}
err = rfid.WriteCard(currentAccessMethod,
*sector,
*block,
defaultDataBytes,
currentAccessKey)
err = rfid.WriteCard(timeout, currentAccessMethod, *sector, *block, defaultDataBytes, currentAccessKey)
if err != nil {
return err
}
Expand Down
106 changes: 106 additions & 0 deletions experimental/devices/mfrc522/blockaccess.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2018 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// Package mfrc522 controls a Mifare RFID card reader.
//
// Datasheet
//
// https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf
package mfrc522

import "fmt"

// BlockAccess defines the block access bits.
type BlockAccess byte

// SectorTrailerAccess defines the sector trailing block access bits.
type SectorTrailerAccess byte

// Access bits for the sector data.
const (
AnyKeyRWID BlockAccess = 0x0 // Any key (A or B) can read, write, increment and decrement block.
RAB_WN_IN_DN BlockAccess = 0x02 // Read (A or B), Write (None), Increment (None), Decrement (None)
RAB_WB_IN_DN BlockAccess = 0x04 // Read (A orB), Write (B), Increment (None), Decrement (None)
RAB_WB_IB_DAB BlockAccess = 0x06 // Read (A or B), Write (B), Icrement (B), Decrement (A or B)
RAB_WN_IN_DAB BlockAccess = 0x01 // Read (A or B), Write (None), Increment (None), Decrment (A or B)
RB_WB_IN_DN BlockAccess = 0x03 // Read (B), Write (B), Increment (None), Decrement (None)
RB_WN_IN_DN BlockAccess = 0x05 // Read (B), Write (None), Increment (None), Decrement (None)
RN_WN_IN_DN BlockAccess = 0x07 // Read (None), Write (None), Increment (None), Decrement (None)
)

// Access bits for the sector trail.
// Every trail sector has the options for controlling the access to the trailing sector bits.
// For example :
//
// KeyA_R[Key]_W[Key]_BITS_R[Key]_W[Key]_KeyB_R[Key]_W[Key]
//
// - KeyA
// - could be Read by providing [Key] ( where [Key] could be KeyA or KeyB )
// - could be Written by Providing [Key] ( where [Key] is KeyA or KeyB )
// - access bits for the sector data (see above)
// - could be Read by providing [Key] ( where [Key] could be KeyA or KeyB )
// - could be Written by Providing [Key] ( where [Key] is KeyA or KeyB )
// - KeyB
// - could be Read by providing [Key] ( where [Key] could be KeyA or KeyB )
// - could be Written by Providing [Key] ( where [Key] is KeyA or KeyB )
//
// example:
//
// KeyA_RN_WA_BITS_RA_WA_KeyB_RA_WA means
// - KeyA could not be read but could be overwriten if KeyA is provided
// - Access bits could be read and overwritten if KeyA is provided during the card authentication
// - KeyB could be read and overriten if KeyA is provided during the card authentication
// more on the matter: https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf
const (
KeyA_RN_WA_BITS_RA_WN_KeyB_RA_WA SectorTrailerAccess = 0x0
KeyA_RN_WN_BITS_RA_WN_KeyB_RA_WN SectorTrailerAccess = 0x02
KeyA_RN_WB_BITS_RAB_WN_KeyB_RN_WB SectorTrailerAccess = 0x04
KeyA_RN_WN_BITS_RAB_WN_KeyB_RN_WN SectorTrailerAccess = 0x06
KeyA_RN_WA_BITS_RA_WA_KeyB_RA_WA SectorTrailerAccess = 0x01
KeyA_RN_WB_BITS_RAB_WB_KeyB_RN_WB SectorTrailerAccess = 0x03
KeyA_RN_WN_BITS_RAB_WB_KeyB_RN_WN SectorTrailerAccess = 0x05
KeyA_RN_WN_BITS_RAB_WN_KeyB_RN_WN_EXTRA SectorTrailerAccess = 0x07
)

// BlocksAccess defines the access structure for first 3 blocks of the sector and the access bits for the sector trail.
type BlocksAccess struct {
B0, B1, B2 BlockAccess
B3 SectorTrailerAccess
}

func (ba *BlocksAccess) String() string {
return fmt.Sprintf("B0: %d, B1: %d, B2: %d, B3: %d", ba.B0, ba.B1, ba.B2, ba.B3)
}

// serialize calculates the block access and stores it into the passed slice, that must be at least 4 bytes wide.
func (ba *BlocksAccess) serialize(dst []byte) error {
if len(dst) != 4 {
return wrapf("serialized array must be of size at least 4")
}
dst[0] = ((^ba.getBits(2) & 0x0F) << 4) | (^ba.getBits(1) & 0x0F)
dst[1] = ((ba.getBits(1) & 0x0F) << 4) | (^ba.getBits(3) & 0x0F)
dst[2] = ((ba.getBits(3) & 0x0F) << 4) | (ba.getBits(2) & 0x0F)
dst[3] = dst[0] ^ dst[1] ^ dst[2]
return nil
}

// Init parses the given byte array into the block access structure.
func (ba *BlocksAccess) Init(ad []byte) {
ba.B0 = BlockAccess(((ad[1] & 0x10) >> 2) | ((ad[2] & 0x01) << 1) | ((ad[2] & 0x10) >> 5))
ba.B1 = BlockAccess(((ad[1] & 0x20) >> 3) | (ad[2] & 0x02) | ((ad[2] & 0x20) >> 5))
ba.B2 = BlockAccess(((ad[1] & 0x40) >> 4) | ((ad[2] & 0x04) >> 1) | ((ad[2] & 0x40) >> 6))
ba.B3 = SectorTrailerAccess(((ad[1] & 0x80) >> 5) | ((ad[2] & 0x08) >> 2) | ((ad[2] & 0x80) >> 7))
}

func (ba *BlocksAccess) getBits(bitNum uint) byte {
shift := 3 - bitNum
bit := byte(1 << shift)
return (byte(ba.B0)&bit)>>shift | ((byte(ba.B1)&bit)>>shift)<<1 | ((byte(ba.B2)&bit)>>shift)<<2 | ((byte(ba.B3)&bit)>>shift)<<3
}

func calcBlockAddress(sector int, block int) byte {
return byte(sector*4 + block)
}

var _ fmt.Stringer = &BlocksAccess{}
158 changes: 137 additions & 21 deletions experimental/devices/mfrc522/commands/low_level.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@ import (
"periph.io/x/periph/conn/spi"
)

// AuthStatus indicates the authentication response, could be one of AuthOk,
// AuthReadFailure or AuthFailure
type AuthStatus byte

// Card authentication status enum.
const (
AuthOk AuthStatus = iota
AuthReadFailure
AuthFailure
)

// LowLevel is a low-level handler of a MFRC522 RFID reader.
type LowLevel struct {
resetPin gpio.PinOut
irqPin gpio.PinIn
spiDev spi.Conn
antennaGain int
stop chan struct{}
}

// AuthStatus indicates the authentication response, could be one of AuthOk,
// AuthReadFailure or AuthFailure
type AuthStatus byte

// NewLowLevelSPI creates and initializes the RFID card reader attached to SPI.
//
// spiPort - the SPI device to use.
Expand All @@ -33,34 +42,73 @@ func NewLowLevelSPI(spiPort spi.Port, resetPin gpio.PinOut, irqPin gpio.PinIn) (
if resetPin == nil {
return nil, wrapf("reset pin is not set")
}
if irqPin == nil {
return nil, wrapf("IRQ pin is not set")
}
spiDev, err := spiPort.Connect(10*physic.MegaHertz, spi.Mode0, 8)
if err != nil {
return nil, err
}
if err := resetPin.Out(gpio.High); err != nil {
return nil, err
}
if err := irqPin.In(gpio.PullUp, gpio.FallingEdge); err != nil {
return nil, err
if irqPin != nil {
if err := irqPin.In(gpio.PullUp, gpio.FallingEdge); err != nil {
return nil, err
}
}

dev := &LowLevel{
spiDev: spiDev,
irqPin: irqPin,
resetPin: resetPin,
spiDev: spiDev,
irqPin: irqPin,
resetPin: resetPin,
antennaGain: 4,
stop: make(chan struct{}, 1),
}

return dev, nil
}

// LowLevel is a low-level handler of a MFRC522 RFID reader.
type LowLevel struct {
resetPin gpio.PinOut
irqPin gpio.PinIn
spiDev spi.Conn
// Reset resets the RFID chip to initial state.
func (r *LowLevel) Reset() error {
return r.DevWrite(CommandReg, PCD_RESETPHASE)
}

// SetAntennaGain sets the antenna gain for the driver.
// This method does not update the gain on the device itself.
// A subsequent call to SetAntenna is necessary to have an effect.
func (r *LowLevel) SetAntennaGain(gain int) {
r.antennaGain = gain
}

// Init initializes the RFID chip.
func (r *LowLevel) Init() error {
if err := r.Reset(); err != nil {
return err
}
if err := r.writeCommandSequence(sequenceCommands.init); err != nil {
return err
}

gain := byte(r.antennaGain) << 4

if err := r.DevWrite(int(RFCfgReg), gain); err != nil {
return err
}

return r.SetAntenna(true)
}

// setAntenna configures the antenna state, on/off.
func (r *LowLevel) SetAntenna(state bool) error {
if state {
current, err := r.DevRead(TxControlReg)
if err != nil {
return err
}
if current&0x03 != 0 {
return wrapf("can not set the bitmask for antenna")
}
return r.SetBitmask(TxControlReg, 0x03)
}
return r.ClearBitmask(TxControlReg, 0x03)
}

// String implements conn.Resource.
Expand Down Expand Up @@ -145,9 +193,37 @@ func (r *LowLevel) StopCrypto() error {
return r.ClearBitmask(Status2Reg, 0x08)
}

// WaitForEdge waits for an IRQ pin to strobe.
func (r *LowLevel) WaitForEdge(timeout time.Duration) bool {
return r.irqPin.WaitForEdge(timeout)
// WaitForEdge waits for an IRQ pin to strobe. If IRQ pin is not set, then always returns false immediately.
func (r *LowLevel) WaitForEdge(timeout time.Duration) error {
irqChannel := make(chan bool)
go func() {
defer close(irqChannel)
irqChannel <- r.irqPin.WaitForEdge(timeout)
}()

if err := r.Init(); err != nil {
return err
}
if err := r.writeCommandSequence(sequenceCommands.waitInit); err != nil {
return err
}

for {
if err := r.writeCommandSequence(sequenceCommands.waitLoop); err != nil {
return err
}
select {
case <-r.stop:
return wrapf("halt")
case irqResult := <-irqChannel:
if !irqResult {
return wrapf("timeout waiting for IRQ edge: %v", timeout)
}
return nil
case <-time.After(100 * time.Millisecond):
// do nothing
}
}
}

// Auth authenticate the card fof the sector/block using the provided data.
Expand Down Expand Up @@ -288,6 +364,46 @@ func (r *LowLevel) CardWrite(command byte, data []byte) ([]byte, int, error) {
return backData, backLength, nil
}

// Halt stops the card and cleans up resources.
func (r *LowLevel) Halt() error {
close(r.stop)
return r.DevWrite(CommandReg, 16)
}

func (r *LowLevel) writeCommandSequence(commands [][]byte) error {
for _, cmdData := range commands {
if err := r.DevWrite(int(cmdData[0]), cmdData[1]); err != nil {
return err
}
}
return nil
}

func wrapf(format string, a ...interface{}) error {
return fmt.Errorf("mfrc522: "+format, a...)
return fmt.Errorf("mfrc522 lowlevel: "+format, a...)
}

// the command batches for card init and wait loop.
var sequenceCommands = struct {
init [][]byte
waitInit [][]byte
waitLoop [][]byte
}{
init: [][]byte{
{TModeReg, 0x8D},
{TPrescalerReg, 0x3E},
{TReloadRegL, 30},
{TReloadRegH, 0},
{TxAutoReg, 0x40},
{ModeReg, 0x3D},
},
waitInit: [][]byte{
{CommIrqReg, 0x00},
{CommIEnReg, 0xA0},
},
waitLoop: [][]byte{
{FIFODataReg, 0x26},
{CommandReg, 0x0C},
{BitFramingReg, 0x87},
},
}
2 changes: 1 addition & 1 deletion experimental/devices/mfrc522/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func Example() {

for {
// Trying to read data from sector 1 block 0
data, err := rfid.ReadCard(byte(commands.PICC_AUTHENT1B), 1, 0, key)
data, err := rfid.ReadCard(10*time.Second, byte(commands.PICC_AUTHENT1B), 1, 0, key)

// If main thread timed out just exiting.
if timedOut {
Expand Down
Loading

0 comments on commit 6f5bd6c

Please sign in to comment.