1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-24 13:48:49 +08:00

SPI using GPIO's plus driver for MFRC522

This commit is contained in:
Thomas Kohler 2023-01-05 19:04:32 +01:00 committed by GitHub
parent fdf4f4b194
commit 6c7ecbe584
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2595 additions and 229 deletions

View File

@ -7,9 +7,11 @@
[![Go Report Card](https://goreportcard.com/badge/hybridgroup/gobot)](https://goreportcard.com/report/hybridgroup/gobot)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/hybridgroup/gobot/blob/master/LICENSE.txt)
Gobot (https://gobot.io/) is a framework using the Go programming language (https://golang.org/) for robotics, physical computing, and the Internet of Things.
Gobot (https://gobot.io/) is a framework using the Go programming language (https://golang.org/) for robotics, physical
computing, and the Internet of Things.
It provides a simple, yet powerful way to create solutions that incorporate multiple, different hardware devices at the same time.
It provides a simple, yet powerful way to create solutions that incorporate multiple, different hardware devices at the
same time.
Want to run Go directly on microcontrollers? Check out our sister project TinyGo (https://tinygo.org/)
@ -87,7 +89,8 @@ func main() {
#### "Metal" Gobot
You can use the entire Gobot framework as shown in the examples above ("Classic" Gobot), or you can pick and choose from the various Gobot packages to control hardware with nothing but pure idiomatic Golang code ("Metal" Gobot). For example:
You can use the entire Gobot framework as shown in the examples above ("Classic" Gobot), or you can pick and choose from
the various Gobot packages to control hardware with nothing but pure idiomatic Golang code ("Metal" Gobot). For example:
```go
package main
@ -114,7 +117,8 @@ func main() {
#### "Master" Gobot
You can also use the full capabilities of the framework aka "Master Gobot" to control swarms of robots or other features such as the built-in API server. For example:
You can also use the full capabilities of the framework aka "Master Gobot" to control swarms of robots or other features
such as the built-in API server. For example:
```go
package main
@ -180,7 +184,9 @@ func main() {
```
## Hardware Support
Gobot has a extensible system for connecting to hardware devices. The following robotics and physical computing platforms are currently supported:
Gobot has a extensible system for connecting to hardware devices. The following robotics and physical computing
platforms are currently supported:
- [Arduino](http://www.arduino.cc/) <=> [Package](https://github.com/hybridgroup/gobot/tree/master/platforms/firmata)
- Audio <=> [Package](https://github.com/hybridgroup/gobot/tree/master/platforms/audio)
@ -311,13 +317,15 @@ a shared set of drivers provided using the `gobot/drivers/spi` package:
- MCP3204 Analog/Digital Converter
- MCP3208 Analog/Digital Converter
- MCP3304 Analog/Digital Converter
- MFRC522 RFID Card Reader
- SSD1306 OLED Display Controller
More platforms and drivers are coming soon...
## API:
## API
Gobot includes a RESTful API to query the status of any robot running within a group, including the connection and device status, and execute device commands.
Gobot includes a RESTful API to query the status of any robot running within a group, including the connection and
device status, and execute device commands.
To activate the API, import the `gobot.io/x/gobot/api` package and instantiate the `API` like this:
@ -327,6 +335,7 @@ To activate the API, import the `gobot.io/x/gobot/api` package and instantiate t
```
You can also specify the api host and port, and turn on authentication:
```go
master := gobot.NewMaster()
server := api.NewAPI(master)
@ -339,29 +348,37 @@ You may access the [robeaux](https://github.com/hybridgroup/robeaux) React.js in
## CLI
Gobot uses the Gort [http://gort.io](http://gort.io) Command Line Interface (CLI) so you can access important features right from the command line. We call it "RobotOps", aka "DevOps For Robotics". You can scan, connect, update device firmware, and more!
Gobot uses the Gort [http://gort.io](http://gort.io) Command Line Interface (CLI) so you can access important features
right from the command line. We call it "RobotOps", aka "DevOps For Robotics". You can scan, connect, update device
firmware, and more!
Gobot also has its own CLI to generate new platforms, adaptors, and drivers. You can check it out in the `/cli` directory.
## Documentation
We're always adding documentation to our web site at https://gobot.io/ please check there as we continue to work on Gobot
Thank you!
## Need help?
* Issues: https://github.com/hybridgroup/gobot/issues
* Twitter: [@gobotio](https://twitter.com/gobotio)
* Slack: [https://gophers.slack.com/messages/C0N5HDB08](https://gophers.slack.com/messages/C0N5HDB08)
* Mailing list: https://groups.google.com/forum/#!forum/gobotio
## Contributing
For our contribution guidelines, please go to [https://github.com/hybridgroup/gobot/blob/master/CONTRIBUTING.md
](https://github.com/hybridgroup/gobot/blob/master/CONTRIBUTING.md
).
Gobot is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. [You can read about it here](https://github.com/hybridgroup/gobot/tree/master/CODE_OF_CONDUCT.md).
Gobot is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
[You can read about it here](https://github.com/hybridgroup/gobot/tree/master/CODE_OF_CONDUCT.md).
## License
Copyright (c) 2013-2020 The Hybrid Group. Licensed under the Apache 2.0 license.
The Contributor Covenant is released under the Creative Commons Attribution 4.0 International Public License, which requires that attribution be included.
The Contributor Covenant is released under the Creative Commons Attribution 4.0 International Public License, which
requires that attribution be included.

View File

@ -76,16 +76,33 @@ type PWMPinnerProvider interface {
PWMPin(id string) (PWMPinner, error)
}
// I2cSystemDevicer is the interface to a i2c bus at system level.
type I2cSystemDevicer interface {
I2cOperations
SetAddress(int) error
}
// SpiSystemDevicer is the interface to a SPI bus at system level.
type SpiSystemDevicer interface {
TxRx(tx []byte, rx []byte) error
// Close the SPI connection.
Close() error
}
// BusOperations are functions provided by a bus device, e.g. SPI, i2c.
type BusOperations interface {
// ReadByteData reads a byte from the given register of bus device.
ReadByteData(reg uint8) (uint8, error)
// ReadBlockData fills the given buffer with reads starting from the given register of bus device.
ReadBlockData(reg uint8, data []byte) error
// WriteByteData writes the given byte value to the given register of bus device.
WriteByteData(reg uint8, val uint8) error
// WriteBlockData writes the given data starting from the given register of bus device.
WriteBlockData(reg uint8, data []byte) error
// WriteByte writes the given byte value to the current register of a bus device.
// WriteByte writes the given byte value to the current register of bus device.
WriteByte(val byte) error
// WriteBytes writes the given data starting from the current register of an bus device.
// WriteBytes writes the given data starting from the current register of bus device.
WriteBytes(data []byte) error
// Close the connection.
Close() error
}
// I2cOperations represents the i2c methods according to I2C/SMBus specification.
@ -105,8 +122,14 @@ type BusOperations interface {
// Count (8 bits): A data byte containing the length of a block operation.
// [..]: Data sent by I2C device, as opposed to data sent by the host adapter.
//
// ReadByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Data] NA P"
// ReadBlockData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P"
// WriteByte must be implemented as the sequence:
// "S Addr Wr [A] Data [A] P"
// WriteByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Data [A] P"
// WriteBlockData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] ... [A] Data [A] P"
type I2cOperations interface {
@ -117,22 +140,10 @@ type I2cOperations interface {
// "S Addr Rd [A] [Data] NA P"
ReadByte() (byte, error)
// ReadByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Data] NA P"
ReadByteData(reg uint8) (uint8, error)
// ReadWordData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [DataLow] A [DataHigh] NA P"
ReadWordData(reg uint8) (uint16, error)
// ReadBlockData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Sr Addr Rd [A] [Count] A [Data] A [Data] A ... A [Data] NA P"
ReadBlockData(reg uint8, b []byte) error
// WriteByteData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] Data [A] P"
WriteByteData(reg uint8, val uint8) error
// WriteWordData must be implemented as the sequence:
// "S Addr Wr [A] Comm [A] DataLow [A] DataHigh [A] P"
WriteWordData(reg uint8, val uint16) error
@ -143,6 +154,8 @@ type SpiOperations interface {
BusOperations
// ReadCommandData uses the SPI device TX to send/receive data.
ReadCommandData(command []byte, data []byte) error
// Close the connection.
Close() error
}
// Adaptor is the interface that describes an adaptor in gobot

View File

@ -0,0 +1,39 @@
package mfrc522
import "fmt"
func (d *MFRC522Common) readByteData(reg uint8) (uint8, error) {
if d.connection == nil {
return 0, fmt.Errorf("not connected")
}
return d.connection.ReadByteData(reg)
}
func (d *MFRC522Common) writeByteData(reg uint8, data uint8) error {
if d.connection == nil {
return fmt.Errorf("not connected")
}
return d.connection.WriteByteData(reg, data)
}
func (d *MFRC522Common) setRegisterBitMask(reg uint8, mask uint8) error {
val, err := d.readByteData(reg)
if err != nil {
return err
}
if err := d.writeByteData(reg, val|mask); err != nil {
return err
}
return nil
}
func (d *MFRC522Common) clearRegisterBitMask(reg uint8, mask uint8) error {
val, err := d.readByteData(reg)
if err != nil {
return err
}
if err := d.writeByteData(reg, val&^mask); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,382 @@
package mfrc522
import (
"fmt"
"time"
)
// PCD: proximity coupling device (reader unit)
// PICC: proximity integrated circuit card (the card or chip)
const (
pcdDebug = false
initTime = 50 * time.Millisecond
// at least 5 ms are needed after switch on, see AN10834
antennaOnTime = 10 * time.Millisecond
)
type busConnection interface {
ReadByteData(reg byte) (byte, error)
WriteByteData(reg byte, data byte) error
}
var versions = map[uint8]string{0x12: "Counterfeit", 0x88: "FM17522", 0x89: "FM17522E",
0x90: "MFRC522 0.0", 0x91: "MFRC522 1.0", 0x92: "MFRC522 2.0", 0xB2: "FM17522 1"}
// MFRC522Common is the Gobot Driver for MFRC522 RFID.
// datasheet:
// https://www.nxp.com/docs/en/data-sheet/MFRC522.pdf
//
// reference implementations:
// * https://github.com/OSSLibraries/ArduinoRegMFRC522v2
// * https://github.com/jdevelop/golang-rpi-extras
// * https://github.com/pimylifeup/MFRC522-python
// * https://periph.io/device/mf-rc522/
type MFRC522Common struct {
connection busConnection
firstCardAccess bool
}
// NewMFRC522Common creates a new Gobot Driver for MFRC522 RFID with specified bus connection
// The device supports SPI, I2C and UART (not implemented yet at gobot system level).
//
// Params:
// c BusConnection - the bus connection to use with this driver
func NewMFRC522Common() *MFRC522Common {
d := &MFRC522Common{}
return d
}
// Initialize sets the connection and initializes the driver.
func (d *MFRC522Common) Initialize(c busConnection) error {
d.connection = c
if err := d.softReset(); err != nil {
return err
}
initSequence := [][]byte{
{regTxMode, rxtxModeRegReset},
{regRxMode, rxtxModeRegReset},
{regModWidth, modWidthRegReset},
{regTMode, tModeRegTAutoBit | 0x0F}, // timer starts automatically at the end of the transmission
{regTPrescaler, 0xFF},
{regTReloadL, tReloadRegValue25ms & 0xFF},
{regTReloadH, tReloadRegValue25ms >> 8},
{regTxASK, txASKRegForce100ASKBit},
{regMode, modeRegTxWaitRFBit | modeRegPolMFinBit | modeRegCRCPreset6363},
}
for _, init := range initSequence {
d.writeByteData(init[0], init[1])
}
if err := d.switchAntenna(true); err != nil {
return err
}
if err := d.setAntennaGain(rfcCfgRegRxGain38dB); err != nil {
return err
}
return nil
}
// PrintReaderVersion gets and prints the reader (pcd) version.
func (d *MFRC522Common) PrintReaderVersion() error {
version, err := d.getVersion()
if err != nil {
return err
}
fmt.Printf("PCD version: '%s' (0x%X)\n", versions[version], version)
return nil
}
func (d *MFRC522Common) getVersion() (uint8, error) {
return d.readByteData(regVersion)
}
func (d *MFRC522Common) switchAntenna(targetState bool) error {
val, err := d.readByteData(regTxControl)
if err != nil {
return err
}
maskForOn := uint8(txControlRegTx2RFEn1outputBit | txControlRegTx1RFEn1outputBit)
currentState := val&maskForOn == maskForOn
if targetState == currentState {
return nil
}
if targetState {
val = val | maskForOn
} else {
val = val & ^maskForOn
}
if err := d.writeByteData(regTxControl, val); err != nil {
return err
}
if targetState {
time.Sleep(antennaOnTime)
}
return nil
}
func (d *MFRC522Common) setAntennaGain(val uint8) error {
return d.writeByteData(regRFCfg, val)
}
func (d *MFRC522Common) softReset() error {
if err := d.writeByteData(regCommand, commandRegSoftReset); err != nil {
return err
}
// The datasheet does not mention how long the SoftReset command takes to complete. According to section 8.8.2 of the
// datasheet the oscillator start-up time is the start up time of the crystal + 37.74 us.
// TODO: this can be done better by wait until the power down bit is cleared
time.Sleep(initTime)
val, err := d.readByteData(regCommand)
if err != nil {
return err
}
if val&commandRegPowerDownBit > 1 {
return fmt.Errorf("initialization takes longer than %s", initTime)
}
return nil
}
func (d *MFRC522Common) stopCrypto1() error {
return d.clearRegisterBitMask(regStatus2, status2RegMFCrypto1OnBit)
}
func (d *MFRC522Common) communicateWithPICC(command uint8, sendData []byte, backData []byte, txLastBits uint8,
checkCRC bool) error {
irqEn := 0x00
waitIRq := uint8(0x00)
switch command {
case commandRegMFAuthent:
irqEn = comIEnRegIdleIEnBit | comIEnRegErrIEnBit
waitIRq = comIrqRegIdleIRqBit
case commandRegTransceive:
irqEn = comIEnRegTimerIEnBit | comIEnRegErrIEnBit | comIEnRegLoAlertIEnBit
irqEn = irqEn | comIEnRegIdleIEnBit | comIEnRegRxIEnBit | comIEnRegTxIEnBit
waitIRq = uint8(comIrqRegIdleIRqBit | comIrqRegRxIRqBit)
}
// TODO: this is not used at the moment (propagation of IRQ pin)
if err := d.writeByteData(regComIEn, uint8(irqEn|comIEnRegIRqInv)); err != nil {
return err
}
if err := d.writeByteData(regComIrq, comIrqRegClearAll); err != nil {
return err
}
if err := d.writeByteData(regFIFOLevel, fifoLevelRegFlushBufferBit); err != nil {
return err
}
// stop any active command
if err := d.writeByteData(regCommand, commandRegIdle); err != nil {
return err
}
// prepare and start communication
if err := d.writeFifo(sendData); err != nil {
return err
}
if err := d.writeByteData(regBitFraming, txLastBits); err != nil {
return err
}
if err := d.writeByteData(regCommand, command); err != nil {
return err
}
if command == commandRegTransceive {
if err := d.setRegisterBitMask(regBitFraming, bitFramingRegStartSendBit); err != nil {
return err
}
}
// Wait for the command to complete. On initialization the TAuto flag in TMode register is set. This means the timer
// automatically starts when the PCD stops transmitting.
const maxTries = 5
i := 0
for ; i < maxTries; i++ {
irqs, err := d.readByteData(regComIrq)
if err != nil {
return err
}
if irqs&waitIRq > 0 {
// One of the interrupts that signal success has been set.
break
}
if irqs&comIrqRegTimerIRqBit == comIrqRegTimerIRqBit {
return fmt.Errorf("the timer interrupt occurred")
}
time.Sleep(time.Millisecond)
}
if err := d.clearRegisterBitMask(regBitFraming, bitFramingRegStartSendBit); err != nil {
return err
}
if i >= maxTries {
return fmt.Errorf("no data available after %d tries", maxTries)
}
errorRegValue, err := d.readByteData(regError)
if err != nil {
return err
}
// stop if any errors except collisions were detected.
if err := d.getFirstError(errorRegValue &^ errorRegCollErrBit); err != nil {
return err
}
backLen := len(backData)
var rxLastBits uint8
if backLen > 0 {
rxLastBits, err = d.readFifo(backData)
if err != nil {
return err
}
if pcdDebug {
fmt.Printf("rxLastBits: 0x%02x\n", rxLastBits)
}
}
if err := d.getFirstError(errorRegValue & errorRegCollErrBit); err != nil {
return err
}
if backLen > 2 && checkCRC {
// the last 2 bytes of data contains the CRC
if backLen == 3 && rxLastBits == 0x04 {
return fmt.Errorf("CRC: MIFARE Classic NAK is not OK")
}
if backLen < 4 || backLen == 4 && rxLastBits != 0 {
return fmt.Errorf("CRC: at least the 2 byte CRCRegA value and all 8 bits of the last byte must be received")
}
crcResult := []byte{0x00, 0x00}
crcData := backData[:backLen-2]
dataCrc := backData[backLen-2:]
err := d.calculateCRC(crcData, crcResult)
if err != nil {
return err
}
if dataCrc[0] != crcResult[0] || dataCrc[1] != crcResult[1] {
return fmt.Errorf("CRC: values not match %v - %v", crcResult, dataCrc)
}
}
return nil
}
// 16 bit CRC will be calculated for the given data
func (d *MFRC522Common) calculateCRC(data []byte, result []byte) error {
// Stop any active command.
if err := d.writeByteData(regCommand, commandRegIdle); err != nil {
return err
}
if err := d.writeByteData(regDivIrq, divIrqRegCRCIRqBit); err != nil {
return err
}
if err := d.writeByteData(regFIFOLevel, fifoLevelRegFlushBufferBit); err != nil {
return err
}
if err := d.writeFifo(data); err != nil {
return err
}
if err := d.writeByteData(regCommand, commandRegCalcCRC); err != nil {
return err
}
const maxTries = 3
for i := 0; i < maxTries; i++ {
irqs, err := d.readByteData(regDivIrq)
if err != nil {
return err
}
if irqs&divIrqRegCRCIRqBit == divIrqRegCRCIRqBit {
if err := d.writeByteData(regCommand, commandRegIdle); err != nil {
return err
}
result[0], err = d.readByteData(regCRCResultL)
if err != nil {
return err
}
result[1], err = d.readByteData(regCRCResultH)
if err != nil {
return err
}
return nil
}
time.Sleep(time.Millisecond)
}
return fmt.Errorf("no CRC available after %d tries", maxTries)
}
func (d *MFRC522Common) writeFifo(fifoData []byte) error {
// the register command is always the same, the pointer in FIFO is incremented automatically after each write
for _, b := range fifoData {
if err := d.writeByteData(regFIFOData, b); err != nil {
return err
}
}
return nil
}
func (d *MFRC522Common) readFifo(backData []byte) (uint8, error) {
n, err := d.readByteData(regFIFOLevel) // Number of bytes in the FIFO
if n > uint8(len(backData)) {
return 0, fmt.Errorf("more data in FIFO (%d) than expected (%d)", n, len(backData))
}
if n < uint8(len(backData)) {
return 0, fmt.Errorf("less data in FIFO (%d) than expected (%d)", n, len(backData))
}
// the register command is always the same, the pointer in FIFO is incremented automatically after each read
for i := 0; i < int(n); i++ {
byteVal, err := d.readByteData(regFIFOData)
if err != nil {
return 0, err
}
backData[i] = byteVal
}
rxLastBits, err := d.readByteData(regControl)
if err != nil {
return 0, err
}
return rxLastBits & controlRegRxLastBits, nil
}
func (d *MFRC522Common) getFirstError(errorRegValue uint8) error {
if errorRegValue == 0 {
return nil
}
if errorRegValue&errorRegProtocolErrBit == errorRegProtocolErrBit {
return fmt.Errorf("a protocol error occurred")
}
if errorRegValue&errorRegParityErrBit == errorRegParityErrBit {
return fmt.Errorf("a parity error occurred")
}
if errorRegValue&errorRegCRCErrBit == errorRegCRCErrBit {
return fmt.Errorf("a CRC error occurred")
}
if errorRegValue&errorRegCollErrBit == errorRegCollErrBit {
return fmt.Errorf("a collision error occurred")
}
if errorRegValue&errorRegBufferOvflBit == errorRegBufferOvflBit {
return fmt.Errorf("a buffer overflow error occurred")
}
if errorRegValue&errorRegTempErrBit == errorRegTempErrBit {
return fmt.Errorf("a temperature error occurred")
}
if errorRegValue&errorRegWrErrBit == errorRegWrErrBit {
return fmt.Errorf("a temperature error occurred")
}
return fmt.Errorf("an unknown error occurred")
}

View File

@ -0,0 +1,422 @@
package mfrc522
// Page 0: Command and status
const (
// 0x00 // reserved for future use
regCommand = 0x01 // starts and stops command execution
// ------------ values --------------------
// commands, see chapter 10.3, table 149 of the datasheet (only 4 lower bits are used for writing)
commandRegIdle = 0x00 // no action, cancels current command execution
commandRegMem = 0x01 // stores 25 bytes into the internal buffer
commandRegGenerateRandomID = 0x02 // generates a 10-byte random ID number
commandRegCalcCRC = 0x03 // activates the CRC coprocessor or performs a self-test
commandRegTransmit = 0x04 // transmits data from the FIFO buffer
// 0x05, 0x06 not used
commandRegNoCmdChange = 0x07 // no command change, can be used to modify the Command register bits without
commandRegReceive = 0x08 // activates the receiver circuits
// 0x09..0x0B not used
commandRegTransceive = 0x0C // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission
// 0x0D reserved
commandRegMFAuthent = 0x0E // performs the MIFARE standard authentication as a reader
commandRegSoftReset = 0x0F // resets the MFRC522
// starts the wake up procedure during which this bit is read as a logic 1; it is read as a logic 0 when the
// is ready; Remark: The PowerDown bit cannot be set when the SoftReset command is activated
commandRegPowerDownBit = 0x10 // Soft power-down mode entered, if 1
commandRegRcvOffBit = 0x20 // analog part of the receiver is switched off, if 1
// commandRegReserved67 = 0xC0
)
const (
regComIEn = 0x02 // enable and disable the passing of interrupt requests to IRQ pin
// ------------ values --------------------
comIEnRegReset = 0x80 // see table 25 of data sheet
comIEnRegTimerIEnBit = 0x01 // bit 0: allows the timer interrupt request (TimerIRq bit) to be propagated
comIEnRegErrIEnBit = 0x02 // bit 1: allows the error interrupt request (ErrIRq bit) to be propagated
comIEnRegLoAlertIEnBit = 0x04 // bit 2: allows the low alert interrupt request (LoAlertIRq bit) to be propagated
comIEnRegHiAlertIEnBit = 0x08 // bit 3: allows the high alert interrupt request (HiAlertIRq bit) to be propagated
comIEnRegIdleIEnBit = 0x10 // bit 4: allows the idle interrupt request (IdleIRq bit) to be propagated
comIEnRegRxIEnBit = 0x20 // bit 5: allows the receiver interrupt request (RxIRq bit) to be propagated
comIEnRegTxIEnBit = 0x40 // bit 6: allows the transmitter interrupt request (TxIRq bit) to be propagated
// 1: signal on pin IRQ is inverted with respect to the Status1 registers IRq bit
// 0: signal on pin IRQ is equal to the IRq bit; in combination with the DivIEn registers IRqPushPull bit, the
// default value of logic 1 ensures that the output level on pin IRQ is 3-state
comIEnRegIRqInv = 0x80 // bit 7: see above
)
const (
// ------------ unused commands --------------------
regDivIEn = 0x03 // enable and disable the passing of interrupt requests to IRQ pin
)
const (
regComIrq = 0x04 // interrupt request bits for communication
// ------------ values --------------------
comIrqRegReset = 0x14 // see table 29 of data sheet
comIrqRegClearAll = 0x7F // all bits are set to clear, except the Set1Bit (0 indicates the reset)
comIrqRegTimerIRqBit = 0x01 // bit 0: the timer decrements the timer value in register TCounterVal to zero, if 1
comIrqRegErrIRq1anyBit = 0x02 // bit 1: error bit in the Error register is set, if 1
// Status1 registers 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
comIrqRegLoAlertIRqBit = 0x04 // bit 2: if 1, see above
// the Status1 registers 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
comIrqRegHiAlertIRqBit = 0x08 // bit 3: if 1, see above
// If a command terminates, for example, when the Command register changes its value from any command to Idle command.
// If an unknown command is started, the Command 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.
comIrqRegIdleIRqBit = 0x10 // bit 4: if 1, see above
// receiver has detected the end of a valid data stream, if the RxMode registers RxNoErr bit is set to logic 1,
// the RxIRq bit is only set to logic 1 when data bytes are available in the FIFO
comIrqRegRxIRqBit = 0x20 // bit 5: if 1, see above
comIrqRegTxIRqBit = 0x40 // bit 6: set to 1, immediately after the last bit of the transmitted data was sent out
// 1: indicates that the marked bits in the register are set
// 0: indicates that the marked bits in the register are cleared
comIrqRegSet1Bit = 0x80 // bit 7: see above
)
const (
regDivIrq = 0x05 // diverse interrupt request bits
// ------------ values --------------------
divIrqRegReset = 0x00 // see table 31 of data sheet
//divIrqRegReserved01 = 0x03
divIrqRegCRCIRqBit = 0x04 // bit 2: the CalcCRC command is active and all data is processed
//divIrqRegReservedBit3 = 0x08
// this interrupt is set when either a rising or falling signal edge is detected
divIrqRegMfinActIRqBit = 0x10 // bit 4: MFIN is active; see above
//divIrqRegReserved56 = 0x60
// 1: indicates that the marked bits in the register are set
// 0: indicates that the marked bits in the register are cleared
divIrqRegSet2Bit = 0x80 // bit 7: see above
)
const (
regError = 0x06 // error bits showing the error status of the last command executed
// ------------ values --------------------
errorRegReset = 0x00 // see table 33 of data sheet
// 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
errorRegProtocolErrBit = 0x01 // bit 0: see above
// automatically cleared during receiver start-up phase; only valid for ISO/IEC 14443 A/MIFARE communication at 106 kBd
errorRegParityErrBit = 0x02 // bit 1: parity check failed, see above
// the RxMode registers RxCRCEn bit is set and the CRC calculation fails automatically; cleared to logic 0 during
// receiver start-up phase
errorRegCRCErrBit = 0x04 // bit 2: see above
// 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
errorRegCollErrBit = 0x08 // bit 3: a bit-collision is detected, see above
//the host or a MFRC522s internal state machine (e.g. receiver) tries to write data to the FIFO buffer even though
// it is already full
errorRegBufferOvflBit = 0x10 // bit 4: FIFO is full, see above
//errorRegReservedBit5 = 0x20
// the antenna drivers are automatically switched off
errorRegTempErrBit = 0x40 // bit 6: internal temperature sensor detects overheating, see above
// 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
errorRegWrErrBit = 0x80 // bit 7: see above
)
const (
// ------------ unused commands --------------------
regStatus1 = 0x07 // communication status bits
)
const (
regStatus2 = 0x08 // receiver and transmitter status bits
// ------------ values --------------------
status2RegReset = 0x00 // see table 37 of data sheet
// bit 0..2 shows the state of the transmitter and receiver state machines
status2RegModemStateIdle = 0x00 // idle
status2RegModemStateWait = 0x01 // wait for the BitFraming registers StartSend bit
// the minimum time for TxWait is defined by the TxWait register
status2RegModemStateTxWait = 0x02 // wait until RF field is present if the TMode registers TxWaitRF bit is set to logic 1
status2RegModemStateTransmitting = 0x03
// the minimum time for RxWait is defined by the RxWait register
status2RegModemStateRxWait = 0x04 // wait until RF field is present if the TMode registers TxWaitRF bit is set to logic 1
status2RegModemStateWaitForData = 0x05
status2RegModemStateReceiving = 0x06
// 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
status2RegMFCrypto1OnBit = 0x08 // bit 3: indicates that the MIFARE Crypto1 unit is switched on and, see above
//status2RegReserved45 = 0x30
// 1: the I2C-bus input filter is set to the High-speed mode independent of the I2C-bus protocol
// 0: the I2C-bus input filter is set to the I2C-bus protocol used
status2RegI2cForceHSBit = 0x40 // I2C-bus input filter settings, see above
status2RegTempSensClear1Bit = 0x80 // clears the temperature error if the temperature is below the alarm limit of 125C
)
const (
regFIFOData = 0x09 // input and output of 64 byte FIFO buffer
)
const (
regFIFOLevel = 0x0A // number of bytes stored in the FIFO buffer
// ------------ values --------------------
fifoLevelRegReset = 0x00 // see table 41 of data sheet
// indicates the number of bytes stored in the FIFO buffer writing to the FIFOData register increments and reading
// decrements the FIFOLevel value
fifoLevelRegValue = 0x7F // bit 0..6: see above
// immediately clears the internal FIFO buffers read and write pointer and Error registers BufferOvfl bit reading
// this bit always returns 0
fifoLevelRegFlushBufferBit = 0x80 // bit 7: see above
)
const (
// ------------ unused commands --------------------
regWaterLevel = 0x0B // level for FIFO underflow and overflow warning
)
const (
regControl = 0x0C // miscellaneous control registers
// ------------ values --------------------
controlRegReset = 0x10 // see table 45 of data sheet
// indicates the number of valid bits in the last received byte
// if this value is 000b, the whole byte is valid
controlRegRxLastBits = 0x07 // bit 0..2: see above
//controlRegReserved3to5 = 0x38
controlRegTStartNowBit = 0x40 // bit 6: timer starts immediately, if 1; reading always returns logic 0
controlRegTStopNow = 0x80 // bit 7: timer stops immediately, if 1; reading always returns logic 0
)
const (
regBitFraming = 0x0D // adjustments for bit-oriented frames
// ------------ values --------------------
bitFramingRegReset = 0x00 // see table 47 of data sheet
// used for transmission of bit oriented frames: defines the number of bits of the last byte that will be transmitted
// 000b indicates that all bits of the last byte will be transmitted
bitFramingRegTxLastBits = 0x07 // bit 0..2: see above
//bitFramingRegReservedBit3 = 0x08
// used for reception of bit-oriented frames: defines the bit position for the first bit received to be stored in the
// FIFO buffer, example:
// 0: LSB of the received bit is stored at bit position 0, the second received bit is stored at bit position 1
// 1: LSB of the received bit is stored at bit position 1, the second received bit is stored at bit position 2
// 7: LSB of the received bit is stored at bit position 7, the second received bit is stored in the next byte that
// follows at bit position 0
// These bits are only to be used for bitwise anticollision at 106 kBd, for all other modes they are set to 0
bitFramingRegRxAlign = 0x70 // bit 4..6: see above
//starts the transmission of data, only valid in combination with the Transceive command
bitFramingRegStartSendBit = 0x80 // bit 7: see above
)
const (
regColl = 0x0E // bit position of the first bit-collision detected on the RF interface
// 4 to 0 CollPos[4:0]-
// shows the bit position of the first detected collision in a received frame only data bits are interpreted example:
// 00: indicates a bit-collision in the 32nd bit
// 01: indicates a bit-collision in the 1st bit
// 08: indicates a bit-collision in the 8th bit
// These bits will only be interpreted if the CollPosNotValid bit is set to logic 0
collRegCollPos = 0x1F // bit 0..4: read-only, see above
// no collision detected or the position of the collision is out of the range of CollPos[4:0], if set to 1
collRegCollPosNotValidBit = 0x20 // bit 5: read-only, see above
//collRegReservedBit6 = 0x40
// all received bits will be cleared after a collision only used during bitwise anticollision at 106 kBd, otherwise it
// is set to logic 1
collRegValuesAfterCollBit = 0x80 // bit 7: see above
)
// 0x0F // reserved for future use
// Page 1: Command
// 0x10 // reserved for future use
const (
regMode = 0x11 // defines general modes for transmitting and receiving
// ------------ values --------------------
modeRegReset = 0x3F // see table 55 of data sheet
// bit 0..1: defines the preset value for the CRC coprocessor for the CalcCRC command; Remark: during any
// communication, the preset values are selected automatically according to the definition of bits in the rxModeReg
// and TxMode registers
modeRegCRCPreset0000 = 0x00 // 0x0000
modeRegCRCPreset6363 = 0x01 // 0x6363
modeRegCRCPresetA671 = 0x10 // 0xA671
modeRegCRCPresetFFFF = 0x11 // 0xFFFF
//modeRegReservedBit2 = 0x04
// defines the polarity of pin MFIN; Remark: the internal envelope signal is encoded active LOW, changing this bit
// generates a MFinActIRq event
modeRegPolMFinBit = 0x08 // bit 3: polarity of pin MFIN is active HIGH, is set to 1
//modeRegReservedBit4 = 0x10
modeRegTxWaitRFBit = 0x20 // bit 5: transmitter can only be started if an RF field is generated, if set to 1
//modeRegReservedBit6 = 0x40
// CRC coprocessor calculates the CRC with MSB first 0 in the CRCResult register the values for the CRCResultMSB[7:0]
// bits and the CRCResultLSB[7:0] bits are bit reversed; Remark: during RF communication this bit is ignored
modeRegMSBFirstBit = 0x80 // bit 7: see above, if set to 1
)
const (
regTxMode = 0x12 // defines transmission data rate and framing
regRxMode = 0x13 // defines reception data rate and framing
// ------------ values --------------------
rxtxModeRegReset = 0x00
//txModeRegReserved = 0x07 // bit 0..2 reserved for TX
//rxModeRegReserved = 0x03 // bit 0,1 reserved for RX
// 0: receiver is deactivated after receiving a data frame
// 1: able to receive more than one data frame; only valid for data rates above 106 kBd in order to handle the
// polling command; after setting this bit the Receive and Transceive commands will not terminate automatically.
// Multiple reception can only be deactivated by writing any command (except the Receive command) to the commandReg
// register, or by the host clearing the bit if set to logic 1, an error byte is added to the FIFO buffer at the
// end of a received data stream which is a copy of the Error register value. For the version 2.0 the
// CRC status is reflected in the signal CRCOk, which indicates the actual status of the CRC coprocessor. For the
// version 1.0 the CRC status is reflected in the signal CRCErr.
rxModeRegRxMultipleBit = 0x04
// an invalid received data stream (less than 4 bits received) will be ignored and the receiver remains active
rxModeRegRxNoErrBit = 0x08 // bit 3
txModeRegInvModBit = 0x08 // bit 3: modulation of transmitted data is inverted, if 1
// bit 4..6: defines the bit rate during data transmission; the handles transfer speeds up to 848 kBd
rxtxModeRegSpeed106kBd = 0x00 //106 kBd
rxtxModeRegSpeed212kBd = 0x10 //212 kBd
rxtxModeRegSpeed424kBd = 0x20 //424 kBd
rxtxModeRegSpeed848kBd = 0x30 //848 kBd
rxtxModeRegSpeedRes1 = 0x40 //reserved
rxtxModeRegSpeedRes2 = 0x50 //reserved
rxtxModeRegSpeedRes3 = 0x60 //reserved
rxtxModeRegSpeedRes4 = 0x70 //reserved
// RX: enables the CRC calculation during reception
// TX: enables CRC generation during data transmission
rxtxModeRegTxCRCEnBit = 0x80 // bit 7: can only be set to logic 0 at 106 kBd
)
const (
regTxControl = 0x14 // controls the logical behavior of the antenna driver pins TX1 and TX2
// ------------ values --------------------
regtxControlRegReset = 0x80 // see table 61 of data sheet
// signal on pin TX1 delivers the 13.56 MHz energy carrier modulated by the transmission data
txControlRegTx1RFEn1outputBit = 0x01 // bit 0: see above
// signal on pin TX2 delivers the 13.56 MHz energy carrier modulated by the transmission data
txControlRegTx2RFEn1outputBit = 0x02 // bit 1: see above
//txControlRegReservedBit2 = 0x04
// signal on pin TX2 continuously delivers the unmodulated 13.56 MHz energy carrier0Tx2CW bit is enabled to modulate
// the 13.56 MHz energy carrier
txControlRegTx2CW1outputBit = 0x08 // bit 3: see above
txControlRegInvTx1RFOffBit = 0x10 // bit 4: output signal on pin TX1 inverted if driver TX1 is disabled, if 1
txControlRegInvTx2RFOffBit = 0x20 // bit 5: output signal on pin TX2 inverted if driver TX2 is disabled, if 1
txControlRegInvTx1RFOnBit = 0x40 // bit 6: output signal on pin TX1 inverted if driver TX1 is enabled, if 1
txControlRegInvTx2RFOnBit = 0x80 // bit 7: output signal on pin TX2 inverted if driver TX2 is enabled, if 1
)
const (
regTxASK = 0x15 // controls the setting of the transmission modulation
// ------------ values --------------------
txASKRegReset = 0x00 // see table 63 of data sheet
//txASKRegReserved = 0x3F // bit 0..5
txASKRegForce100ASKBit = 0x40 // bit 6: forces a 100 % ASK modulation independent of the ModGsP register
//txASKRegReservedBit7 = 0x80
)
const (
regTxSel = 0x16 // selects the internal sources for the antenna driver
regRxSel = 0x17 // selects internal receiver settings
regRxThreshold = 0x18 // selects thresholds for the bit decoder
regDemod = 0x19 // defines demodulator settings
// 0x1A // reserved for future use
// 0x1B // reserved for future use
regMfTx = 0x1C // controls some MIFARE communication transmit parameters
regMfRx = 0x1D // controls some MIFARE communication receive parameters
// 0x1E // reserved for future use
regSerialSpeed = 0x1F // selects the speed of the serial UART interface
// Page 2: Configuration
// 0x20 // reserved for future use
regCRCResultH = 0x21 // shows the MSB and LSB values of the CRC calculation
regCRCResultL = 0x22
// 0x23 // reserved for future use
)
const (
regModWidth = 0x24
// ------------ values --------------------
modWidthRegReset = 0x26 // see table 93 of data sheet
)
// 0x25 // reserved for future use
const (
regRFCfg = 0x26 // configures the receiver gain
// ------------ values --------------------
rfcCfgRegReset = 0x48 // see table 97 of data sheet
//rfcCfgRegReserved03 = 0x07
// bit 4..6: defines the receivers signal voltage gain factor
rfcCfgRegRxGain18dB = 0x00
rfcCfgRegRxGain23dB = 0x10
rfcCfgRegRxGain018dB = 0x20
rfcCfgRegRxGain023dB = 0x30
rfcCfgRegRxGain33dB = 0x40
rfcCfgRegRxGain38dB = 0x50
rfcCfgRegRxGain43dB = 0x60
rfcCfgRegRxGain48dB = 0x70
//rfcCfgRegReserved7 = 0x80
)
const (
// ------------ unused commands --------------------
regGsN = 0x27 // selects the conductance of the antenna driver pins TX1 and TX2 for modulation
regCWGsP = 0x28 // defines the conductance of the p-driver output during periods of no modulation
regModGsP = 0x29 // defines the conductance of the p-driver output during periods of modulation
)
const (
regTMode = 0x2A // defines settings for the internal timer
regTPrescaler = 0x2B // the lower 8 bits of the TPrescaler value. The 4 high bits are in tModeReg.
// ------------ values --------------------
tModeRegReset = 0x00 // see table 105 of data sheet
tPrescalerRegReset = 0x00 // see table 107 of data sheet
// timer starts automatically at the end of the transmission in all communication modes at all speeds; if the
// RxMode registers 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 Control registers TStopNow bit to logic 1
tModeRegTAutoBit = 0x80 // bit 7: see above
// bit 6,5: indicates that the timer is not influenced by the protocol; internal timer is running in
// gated mode; Remark: in gated mode, the Status1 registers TRunning bit is logic 1 when the timer is enabled by
// the TMode registers TGated bits; this bit does not influence the gating signal
tModeRegTGatedNon = 0x00 // non-gated mode
tModeRegTGatedMFIN = 0x20 // gated by pin MFIN
tModeRegTGatedAUX1 = 0x40 // gated by pin AUX1
// 1: timer automatically restarts its count-down from the 16-bit timer reload value instead of counting down to zero
// 0: timer decrements to 0 and the ComIrq registers TimerIRq bit is set to logic 1
tModeRegTAutoRestartBit = 0x10 // bit 4, see above
// defines the higher 4 bits of the TPrescaler value; The following formula is used to calculate the timer
// frequency if the Demod registers TPrescalEven bit in Demot registers set to logic 0:
// ftimer = 13.56 MHz / (2*TPreScaler+1); TPreScaler = [tPrescalerRegHi:tPrescalerRegLo]
// TPrescaler value on 12 bits) (Default TPrescalEven bit is logic 0)
// The following formula is used to calculate the timer frequency if the Demod registers TPrescalEven bit is set
// to logic 1: ftimer = 13.56 MHz / (2*TPreScaler+2).
tModeRegtPrescalerRegValue25us = 0x0A9 // 169 => fRegtimer=40kHz, timer period of 25μs.
tModeRegtPrescalerRegValue38us = 0x0FF // 255 => fRegtimer=26kHz, timer period of 38μs.
tModeRegtPrescalerRegValue500us = 0xD3E // 3390 => fRegtimer= 2kHz, timer period of 500us.
tModeRegtPrescalerRegValue604us = 0xFFF // 4095 => fRegtimer=1.65kHz, timer period of 604us.
)
const (
// defines the 16-bit timer reload value; on a start event, the timer loads the timer reload value changing this
// register affects the timer only at the next start event
regTReloadH = 0x2C
regTReloadL = 0x2D
// ------------ values --------------------
tReloadRegReset = 0x0000 // see table 109, 111
tReloadRegValue25ms = 0x03E8 // 1000, 25ms before timeout
tReloadRegValue833ms = 0x001E // 30, 833ms before timeout
)
const (
// ------------ unused commands --------------------
regTCounterValueH = 0x2E // shows the 16-bit timer value
regTCounterValueL = 0x2F
// Page 3: Test Registers
// 0x30 // reserved for future use
regTestSel1 = 0x31 // general test signal configuration
regTestSel2 = 0x32 // general test signal configuration
regTestPinEn = 0x33 // enables pin output driver on pins D1 to D7
regTestPinValue = 0x34 // defines the values for D1 to D7 when it is used as an I/O bus
regTestBus = 0x35 // shows the status of the internal test bus
regAutoTest = 0x36 // controls the digital self-test
regVersion = 0x37 // shows the software version
regAnalogTest = 0x38 // controls the pins AUX1 and AUX2
regTestDAC1 = 0x39 // defines the test value for TestDAC1
regTestDAC2 = 0x3A // defines the test value for TestDAC2
regTestADC = 0x3B // shows the value of ADC I and Q channels
// 0x3C // reserved for production tests
// 0x3D // reserved for production tests
// 0x3E // reserved for production tests
// 0x3F // reserved for production tests
)

View File

@ -0,0 +1,216 @@
package mfrc522
import (
"testing"
"gobot.io/x/gobot/gobottest"
)
type busConnMock struct {
written []byte
readIdx int
simRead []byte
fifoIdx int
simFifo []byte
}
func (c *busConnMock) ReadByteData(reg uint8) (uint8, error) {
c.written = append(c.written, reg)
switch reg {
case regFIFOLevel:
return uint8(len(c.simFifo)), nil
case regFIFOData:
c.fifoIdx++
return c.simFifo[c.fifoIdx-1], nil
default:
if len(c.simRead) > 0 {
c.readIdx++
return c.simRead[c.readIdx-1], nil
}
return 0, nil
}
}
func (c *busConnMock) WriteByteData(reg uint8, data byte) error {
c.written = append(c.written, reg)
c.written = append(c.written, data)
return nil
}
func initTestMFRC522CommonWithStubbedConnector() (*MFRC522Common, *busConnMock) {
c := &busConnMock{}
d := NewMFRC522Common()
d.connection = c
return d, c
}
func TestNewMFRC522Common(t *testing.T) {
// act
d := NewMFRC522Common()
// assert
gobottest.Refute(t, d, nil)
}
func TestInitialize(t *testing.T) {
// arrange
wantSoftReset := []byte{0x01, 0x0F, 0x01}
wantInit := []byte{0x12, 0x00, 0x13, 0x00, 0x24, 0x26, 0x2A, 0x8F, 0x2B, 0xFF, 0x2D, 0xE8, 0x2C, 0x03, 0x15, 0x40, 0x11, 0x29}
wantAntennaOn := []byte{0x14, 0x14, 0x03}
wantGain := []byte{0x26, 0x50}
c := &busConnMock{}
d := NewMFRC522Common()
// act
err := d.Initialize(c)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, d.connection, c)
gobottest.Assert(t, c.written[:3], wantSoftReset)
gobottest.Assert(t, c.written[3:21], wantInit)
gobottest.Assert(t, c.written[21:24], wantAntennaOn)
gobottest.Assert(t, c.written[24:], wantGain)
}
func Test_getVersion(t *testing.T) {
// arrange
d, c := initTestMFRC522CommonWithStubbedConnector()
wantWritten := []byte{0x37}
const want = uint8(5)
c.simRead = []byte{want}
// act
got, err := d.getVersion()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, got, want)
gobottest.Assert(t, c.written, wantWritten)
}
func Test_switchAntenna(t *testing.T) {
var tests = map[string]struct {
target bool
simRead byte
wantWritten []byte
}{
"switch_on": {
target: true,
simRead: 0xFD,
wantWritten: []byte{0x14, 0x14, 0xFF},
},
"is_already_on": {
target: true,
simRead: 0x03,
wantWritten: []byte{0x14},
},
"switch_off": {
target: false,
simRead: 0x03,
wantWritten: []byte{0x14, 0x14, 0x00},
},
"is_already_off": {
target: false,
simRead: 0xFD,
wantWritten: []byte{0x14},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, c := initTestMFRC522CommonWithStubbedConnector()
c.simRead = []byte{tc.simRead}
// act
err := d.switchAntenna(tc.target)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, c.written, tc.wantWritten)
})
}
}
func Test_stopCrypto1(t *testing.T) {
// arrange
d, c := initTestMFRC522CommonWithStubbedConnector()
c.simRead = []byte{0xFF}
wantWritten := []byte{0x08, 0x08, 0xF7}
// act
err := d.stopCrypto1()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, c.written, wantWritten)
}
func Test_communicateWithPICC(t *testing.T) {
// arrange
d, c := initTestMFRC522CommonWithStubbedConnector()
dataToFifo := []byte{0xF1, 0xF2}
writtenPrepare := []byte{0x02, 0xF7, 0x04, 0x7F, 0x0A, 0x80, 0x01, 0x00}
writtenWriteFifo := []byte{0x09, 0xF1, 0x09, 0xF2}
writtenTransceive := []byte{0x0D, 0x00, 0x01, 0x0C}
writtenBitFramingStart := []byte{0x0D, 0x0D, 0x80}
writtenWaitAndFinish := []byte{0x04, 0x0D, 0x0D, 0x7F, 0x06}
writtenReadFifo := []byte{0x0A, 0x09, 0x09, 0x0C}
// read: bit framing register set, simulate calculation done, bit framing register clear, simulate no error,
// simulate control register 0x00
c.simRead = []byte{0x00, 0x30, 0xFF, 0x00, 0x00}
c.simFifo = []byte{0x11, 0x22}
backData := []byte{0x00, 0x00}
// act
// transceive, all 8 bits, no CRC
err := d.communicateWithPICC(0x0C, dataToFifo, backData, 0x00, false)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, c.written[:8], writtenPrepare)
gobottest.Assert(t, c.written[8:12], writtenWriteFifo)
gobottest.Assert(t, c.written[12:16], writtenTransceive)
gobottest.Assert(t, c.written[16:19], writtenBitFramingStart)
gobottest.Assert(t, c.written[19:24], writtenWaitAndFinish)
gobottest.Assert(t, c.written[24:], writtenReadFifo)
gobottest.Assert(t, backData, []byte{0x11, 0x22})
}
func Test_calculateCRC(t *testing.T) {
// arrange
d, c := initTestMFRC522CommonWithStubbedConnector()
dataToFifo := []byte{0x02, 0x03}
writtenPrepare := []byte{0x01, 0x00, 0x05, 0x04, 0x0A, 0x80}
writtenFifo := []byte{0x09, 0x02, 0x09, 0x03}
writtenCalc := []byte{0x01, 0x03, 0x05, 0x01, 0x00}
writtenGetResult := []byte{0x22, 0x21}
c.simRead = []byte{0x04, 0x11, 0x22} // calculation done, crcL, crcH
gotCrcBack := []byte{0x00, 0x00}
// act
err := d.calculateCRC(dataToFifo, gotCrcBack)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, c.written[:6], writtenPrepare)
gobottest.Assert(t, c.written[6:10], writtenFifo)
gobottest.Assert(t, c.written[10:15], writtenCalc)
gobottest.Assert(t, c.written[15:], writtenGetResult)
gobottest.Assert(t, gotCrcBack, []byte{0x11, 0x22})
}
func Test_writeFifo(t *testing.T) {
// arrange
d, c := initTestMFRC522CommonWithStubbedConnector()
dataToFifo := []byte{0x11, 0x22, 0x33}
wantWritten := []byte{0x09, 0x11, 0x09, 0x22, 0x09, 0x33}
// act
err := d.writeFifo(dataToFifo)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, c.written, wantWritten)
}
func Test_readFifo(t *testing.T) {
// arrange
d, c := initTestMFRC522CommonWithStubbedConnector()
c.simFifo = []byte{0x30, 0x20, 0x10}
wantWritten := []byte{0x0A, 0x09, 0x09, 0x09, 0x0C}
backData := []byte{0x00, 0x00, 0x00}
// act
_, err := d.readFifo(backData)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, c.written, wantWritten)
gobottest.Assert(t, backData, c.simFifo)
}

View File

@ -0,0 +1,403 @@
package mfrc522
import (
"fmt"
)
const piccDebug = false
// Commands sent to the PICC, used by the PCD to for communication with several PICCs (ISO 14443-3, Type A, section 6.4)
const (
// Activation
piccCommandRequestA = 0x26 // REQuest command type A, 7 bit frame, invites PICCs in state IDLE to go to READY
piccCommandWakeUpA = 0x52 // Wake-UP command type A, 7 bit frame, invites PICCs in state IDLE and HALT to go to READY
// Anticollision and SAK
piccCommandCascadeLevel1 = 0x93 // Select cascade level 1
piccCommandCascadeLevel2 = 0x95 // Select cascade Level 2
piccCommandCascadeLevel3 = 0x97 // Select cascade Level 3
piccCascadeTag = 0x88 // Cascade tag is used during anti collision
piccUIDNotComplete = 0x04 // used on SAK call
// Halt
piccCommandHLTA = 0x50 // Halt command, Type A. Instructs an active PICC to go to state HALT.
piccCommandRATS = 0xE0 // Request command for Answer To Reset.
// The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9)
// Use MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on
// the sector. The read/write commands can also be used for MIFARE Ultralight.
piccCommandMFRegAUTHRegKEYRegA = 0x60 // Perform authentication with Key A
piccCommandMFRegAUTHRegKEYRegB = 0x61 // Perform authentication with Key B
// Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight.
piccCommandMFRegREAD = 0x30
// Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight.
piccCommandMFRegWRITE = 0xA0
piccWriteAck = 0x0A // MIFARE Classic: 4 bit ACK, we use any other value as NAK (data sheet: 0h to 9h, Bh to Fh)
piccCommandMFRegDECREMENT = 0xC0 // Decrements the contents of a block and stores the result in the internal data register.
piccCommandMFRegINCREMENT = 0xC1 // Increments the contents of a block and stores the result in the internal data register.
piccCommandMFRegRESTORE = 0xC2 // Reads the contents of a block into the internal data register.
piccCommandMFRegTRANSFER = 0xB0 // Writes the contents of the internal data register to a block.
// The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/dataRegsheet/MF0ICU1.pdf, Section 8.6)
// The piccCommandMFRegREAD and piccCommandMFRegWRITE can also be used for MIFARE Ultralight.
piccCommandULRegWRITE = 0xA2 // Writes one 4 byte page to the PICC.
)
const piccReadWriteAuthBlock = uint8(11)
var piccKey = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
var piccUserBlockAddresses = []byte{8, 9, 10}
var piccCardFromSak = map[uint8]string{0x08: "Classic 1K, Plus 2K-SE-1K(SL1)", 0x18: "Classic 4K, Plus 4K(SL1)",
0x10: "Plus 2K(SL2)", 0x11: "Plus 4K(SL2)", 0x20: "Plus 2K-SE-1K(SL3), Plus 4K(SL3)"}
// IsCardPresent is used to poll for a card in range. After an successful request, the card is halted.
func (d *MFRC522Common) IsCardPresent() error {
d.firstCardAccess = true
if err := d.writeByteData(regTxMode, rxtxModeRegReset); err != nil {
return err
}
if err := d.writeByteData(regRxMode, rxtxModeRegReset); err != nil {
return err
}
if err := d.writeByteData(regModWidth, modWidthRegReset); err != nil {
return err
}
answer := []byte{0x00, 0x00} // also called ATQA
if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
return err
}
if piccDebug {
fmt.Printf("Card found: %v\n\n", answer)
}
if err := d.piccHalt(); err != nil {
return err
}
return nil
}
// ReadText reads a card with the dedicated workflow: REQA, Activate, Perform Transaction, Halt/Deselect.
// see "Card Polling" in https://www.nxp.com/docs/en/application-note/AN10834.pdf.
// and return the result as text string.
// TODO: make this more usable, e.g. by given length of text
func (d *MFRC522Common) ReadText() (string, error) {
answer := []byte{0x00, 0x00}
if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
return "", err
}
uid, err := d.piccActivate()
if err != nil {
return "", err
}
if piccDebug {
fmt.Printf("uid: %v\n", uid)
}
if err := d.piccAuthenticate(piccReadWriteAuthBlock, piccKey, uid); err != nil {
if piccDebug {
fmt.Println("authenticate failed for address", piccReadWriteAuthBlock)
}
return "", err
}
var content []byte
for _, block := range piccUserBlockAddresses {
blockData, err := d.piccRead(block)
if err != nil {
if piccDebug {
fmt.Println("read failed at block", block)
}
return "", err
}
content = append(content, blockData...)
}
if piccDebug {
fmt.Println("content:", string(content[:]), content)
}
if err := d.piccHalt(); err != nil {
return "", err
}
return string(content[:]), d.stopCrypto1()
}
// WriteText writes the given string to the card. All old values will be overwritten.
func (d *MFRC522Common) WriteText(text string) error {
answer := []byte{0x00, 0x00}
if err := d.piccRequest(piccCommandWakeUpA, answer); err != nil {
return err
}
uid, err := d.piccActivate()
if err != nil {
return err
}
if piccDebug {
fmt.Printf("uid: %v\n", uid)
}
if err := d.piccAuthenticate(piccReadWriteAuthBlock, piccKey, uid); err != nil {
if piccDebug {
fmt.Println("authenticate failed for address", piccReadWriteAuthBlock)
}
return err
}
// prepare data with text and trailing zero's
textData := append([]byte(text), make([]byte, len(piccUserBlockAddresses)*16)...)
for i, blockNum := range piccUserBlockAddresses {
blockData := textData[i*16 : (i+1)*16]
err := d.piccWrite(blockNum, blockData)
if err != nil {
if piccDebug {
fmt.Println("write failed at block", blockNum)
}
return err
}
}
if err := d.piccHalt(); err != nil {
return err
}
return d.stopCrypto1()
}
func (d *MFRC522Common) piccHalt() error {
if piccDebug {
fmt.Println("-halt-")
}
haltCommand := []byte{piccCommandHLTA, 0x00}
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(haltCommand, crcResult); err != nil {
return err
}
haltCommand = append(haltCommand, crcResult...)
txLastBits := uint8(0x00) // we use all 8 bits
if err := d.communicateWithPICC(commandRegTransceive, haltCommand, []byte{}, txLastBits, false); err != nil {
// an error is the sign for successful halt
if piccDebug {
fmt.Println("this is not treated as error:", err)
}
return nil
}
return fmt.Errorf("something went wrong with halt")
}
func (d *MFRC522Common) piccWrite(block uint8, blockData []byte) error {
if piccDebug {
fmt.Println("-write-")
fmt.Println("blockData:", blockData, len(blockData))
}
if len(blockData) != 16 {
return fmt.Errorf("the block to write needs to be exactly 16 bytes long, but has %d bytes", len(blockData))
}
// MIFARE Classic protocol requires two steps to perform a write.
// Step 1: Tell the PICC we want to write to block blockAddr.
writeDataCommand := []byte{piccCommandMFRegWRITE, block}
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(writeDataCommand, crcResult); err != nil {
return err
}
writeDataCommand = append(writeDataCommand, crcResult...)
txLastBits := uint8(0x00) // we use all 8 bits
backData := make([]byte, 1)
if err := d.communicateWithPICC(commandRegTransceive, writeDataCommand, backData, txLastBits, false); err != nil {
return err
}
if backData[0]&piccWriteAck != piccWriteAck {
return fmt.Errorf("preparation of write on MIFARE classic failed (%v)", backData)
}
if piccDebug {
fmt.Println("backData", backData)
}
// Step 2: Transfer the data
if err := d.calculateCRC(blockData, crcResult); err != nil {
return err
}
var writeData []byte
writeData = append(writeData, blockData...)
writeData = append(writeData, crcResult...)
if err := d.communicateWithPICC(commandRegTransceive, writeData, []byte{}, txLastBits, false); err != nil {
return err
}
return nil
}
func (d *MFRC522Common) piccRead(block uint8) ([]byte, error) {
if piccDebug {
fmt.Println("-read-")
}
readDataCommand := []byte{piccCommandMFRegREAD, block}
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(readDataCommand, crcResult); err != nil {
return nil, err
}
readDataCommand = append(readDataCommand, crcResult...)
txLastBits := uint8(0x00) // we use all 8 bits
backData := make([]byte, 18) // 16 data byte and 2 byte CRC
if err := d.communicateWithPICC(commandRegTransceive, readDataCommand, backData, txLastBits, true); err != nil {
return nil, err
}
return backData[:16], nil
}
func (d *MFRC522Common) piccAuthenticate(address uint8, key []byte, uid []byte) error {
if piccDebug {
fmt.Println("-authenticate-")
}
buf := []byte{piccCommandMFRegAUTHRegKEYRegA, address}
buf = append(buf, key...)
buf = append(buf, uid...)
if err := d.communicateWithPICC(commandRegMFAuthent, buf, []byte{}, 0, false); err != nil {
return err
}
return nil
}
// activate a card with the dedicated workflow: Anticollision and optional Request for "Answer To Select" (RATS) and
// "Protocol Parameter Selection" (PPS).
// see "Card Activation" in https://www.nxp.com/docs/en/application-note/AN10834.pdf.
// note: the card needs to be in ready state, e.g. by a request or wake up is done before
func (d *MFRC522Common) piccActivate() ([]byte, error) {
if err := d.clearRegisterBitMask(regColl, collRegValuesAfterCollBit); err != nil {
return nil, err
}
if err := d.writeByteData(regBitFraming, bitFramingRegReset); err != nil {
return nil, err
}
// start cascade level 1 (0x93) for return:
// * one size UID (4 byte): UID0..3 and one byte BCC or
// * cascade tag (0x88) and UID0..2 and BCC
// in the latter case the UID is incomplete and the next cascade level needs to be started.
// cascade level 2 (0x95) return:
// * double size UID (7 byte): UID3..6 and one byte BCC or
// * cascade tag (0x88) and UID3..5 and BCC
// cascade level 3 (0x97) return:
// * triple size UID (10 byte): UID6..9
// after each anticollision check (request of next UID) the SAK needs to be done (same level command)
// BCC: Block Check Character
// SAK: Select Acknowledge
var uid []byte
var sak uint8
for cascadeLevel := 1; cascadeLevel < 3; cascadeLevel++ {
var piccCommand uint8
switch cascadeLevel {
case 1:
piccCommand = piccCommandCascadeLevel1
case 2:
piccCommand = piccCommandCascadeLevel2
case 3:
piccCommand = piccCommandCascadeLevel3
default:
return nil, fmt.Errorf("unknown cascade level %d", cascadeLevel)
}
if piccDebug {
fmt.Println("-anti collision-")
}
txLastBits := uint8(0x00) // we use all 8 bits
numValidBits := uint8(4 * 8)
sendForAnticol := []byte{piccCommand, numValidBits}
backData := []byte{0x00, 0x00, 0x00, 0x00, 0x00} // 4 bytes CT/UID and BCC
if err := d.communicateWithPICC(commandRegTransceive, sendForAnticol, backData, txLastBits, false); err != nil {
return nil, err
}
// TODO: no real anticollision check yet
// check BCC
bcc := byte(0)
for _, v := range backData[:4] {
bcc = bcc ^ v
}
if bcc != backData[4] {
return nil, fmt.Errorf(fmt.Sprintf("BCC mismatch, expected %02x actual %02x", bcc, backData[4]))
}
if backData[0] == piccCascadeTag {
uid = append(uid, backData[1:3]...)
if piccDebug {
fmt.Printf("next cascade is needed after SAK, uid: %v", uid)
}
} else {
uid = append(uid, backData[:4]...)
if piccDebug {
fmt.Printf("backData: %v, uid: %v\n", backData, uid)
}
}
if piccDebug {
fmt.Println("-select acknowledge-")
}
sendCommand := []byte{piccCommand}
sendCommand = append(sendCommand, 0x70) // 7 bytes
sendCommand = append(sendCommand, backData...) // uid including BCC
crcResult := []byte{0x00, 0x00}
if err := d.calculateCRC(sendCommand, crcResult); err != nil {
return uid, err
}
sendCommand = append(sendCommand, crcResult...)
sakData := []byte{0x00, 0x00, 0x00}
if err := d.communicateWithPICC(commandRegTransceive, sendCommand, sakData, txLastBits, false); err != nil {
return nil, err
}
bcc = byte(0)
for _, v := range sakData[:2] {
bcc = bcc ^ v
}
if piccDebug {
fmt.Printf("sak data: %v\n", sakData)
}
if sakData[0] != piccUIDNotComplete {
sak = sakData[0]
break
}
if piccDebug {
fmt.Printf("next cascade called, SAK: %v\n", sakData[0])
}
}
if piccDebug || d.firstCardAccess {
d.firstCardAccess = false
fmt.Printf("card '%s' selected\n", piccCardFromSak[sak])
}
return uid, nil
}
func (d *MFRC522Common) piccRequest(reqMode uint8, answer []byte) error {
if len(answer) < 2 {
return fmt.Errorf("at least 2 bytes room needed for the answer")
}
if err := d.clearRegisterBitMask(regColl, collRegValuesAfterCollBit); err != nil {
return err
}
// for request A and wake up the short frame format is used - transmit only 7 bits of the last (and only) byte.
txLastBits := uint8(0x07 & bitFramingRegTxLastBits)
if err := d.communicateWithPICC(commandRegTransceive, []byte{reqMode}, answer, txLastBits, false); err != nil {
return err
}
return nil
}

View File

@ -34,12 +34,6 @@ const (
set = 0x01
)
// I2cDevice is the interface to a specific i2c bus
type I2cDevice interface {
gobot.I2cOperations
SetAddress(int) error
}
// Connection is a connection to an I2C device with a specified address
// on a specific bus. Used as an alternative to the I2c interface.
// Implements I2cOperations to talk to the device, wrapping the
@ -48,15 +42,15 @@ type I2cDevice interface {
type Connection gobot.I2cOperations
type i2cConnection struct {
bus I2cDevice
bus gobot.I2cSystemDevicer
address int
mutex *sync.Mutex
mutex sync.Mutex
}
// NewConnection creates and returns a new connection to a specific
// i2c device on a bus and address.
func NewConnection(bus I2cDevice, address int) (connection *i2cConnection) {
return &i2cConnection{bus: bus, address: address, mutex: &sync.Mutex{}}
func NewConnection(bus gobot.I2cSystemDevicer, address int) (connection *i2cConnection) {
return &i2cConnection{bus: bus, address: address}
}
// Read data from an i2c device.

View File

@ -9,6 +9,7 @@ import (
"syscall"
"unsafe"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
"gobot.io/x/gobot/system"
)
@ -41,7 +42,7 @@ func syscallImplFail(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errn
return 0, 0, 1
}
func initI2CDevice() I2cDevice {
func initI2CDevice() gobot.I2cSystemDevicer {
a := system.NewAccesser()
a.UseMockFilesystem([]string{dev})
msc := a.UseMockSyscall()
@ -51,7 +52,7 @@ func initI2CDevice() I2cDevice {
return d
}
func initI2CDeviceAddressError() I2cDevice {
func initI2CDeviceAddressError() gobot.I2cSystemDevicer {
a := system.NewAccesser()
a.UseMockFilesystem([]string{dev})
msc := a.UseMockSyscall()

View File

@ -0,0 +1,38 @@
package i2c
import (
"gobot.io/x/gobot/drivers/common/mfrc522"
)
const mfrc522DefaultAddress = 0x00
// MFRC522Driver is a wrapper for i2c bus usage. Please refer to the mfrc522.MFRC522Common package
// for implementation details.
type MFRC522Driver struct {
*Driver
*mfrc522.MFRC522Common
}
// NewMFRC522Driver creates a new Gobot Driver for MFRC522 RFID with i2c connection
//
// Params:
// c Connector - the Adaptor to use with this Driver
//
// Optional params:
// i2c.WithBus(int): bus to use with this driver
// i2c.WithAddress(int): address to use with this driver
func NewMFRC522Driver(c Connector, options ...func(Config)) *MFRC522Driver {
d := &MFRC522Driver{
Driver: NewDriver(c, "MFRC522", mfrc522DefaultAddress),
}
d.MFRC522Common = mfrc522.NewMFRC522Common()
d.afterStart = d.initialize
for _, option := range options {
option(d)
}
return d
}
func (d *MFRC522Driver) initialize() error {
return d.MFRC522Common.Initialize(d.connection)
}

View File

@ -1,22 +1,20 @@
# SPI
This package provides drivers for [spi](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus) devices.
It currently must be used along with platforms such as the [Raspberry Pi](https://gobot.io/documentation/platforms/raspi) and [Beaglebone Black](https://gobot.io/documentation/platforms/beaglebone) that have adaptors that implement the needed SPI interface.
The SPI implementation uses the awesome [periph.io](https://periph.io/) which currently only works on Linux systems.
This package provides drivers for [SPI](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus) devices.
## Getting Started
## Installing
```
```sh
go get -d -u gobot.io/x/gobot/...
```
## Hardware Support
Gobot has a extensible system for connecting to hardware devices.
The following spi Devices are currently supported:
Gobot has a extensible system for connecting to hardware devices.
The following SPI Devices are currently supported:
- APA102 Programmable LEDs
- MCP3002 Analog/Digital Converter
@ -26,12 +24,11 @@ The following spi Devices are currently supported:
- MCP3204 Analog/Digital Converter
- MCP3208 Analog/Digital Converter
- MCP3304 Analog/Digital Converter
- MFRC522 RFID Card Reader
- SSD1306 OLED Display Controller
- GoPiGo3 Robot
Drivers wanted! :)
The following SPI system drivers are currently supported:
The following spi Adaptors are currently supported:
- Raspberry Pi
Adaptors wanted too!
- SPI by `/dev/spidevX.Y` with the awesome [periph.io](https://periph.io/) which currently only works on Linux systems
- SPI via GPIO's

View File

@ -35,9 +35,9 @@ func (a *spiTestAdaptor) GetSpiConnection(busNum, chipNum, mode, bits int, maxSp
return nil, fmt.Errorf("Invalid SPI connection in helper")
}
//a.busNum = busNum
con, err := a.sys.NewSpiConnection(busNum, chipNum, mode, bits, maxSpeed)
a.connection = con
return con, err
sysdev, err := a.sys.NewSpiDevice(busNum, chipNum, mode, bits, maxSpeed)
a.connection = NewConnection(sysdev)
return a.connection, err
}
func (a *spiTestAdaptor) SpiDefaultBusNumber() int { return 0 }

View File

@ -0,0 +1,56 @@
package spi
import (
"gobot.io/x/gobot/drivers/common/mfrc522"
)
// MFRC522Driver is a wrapper for SPI bus usage. Please refer to the mfrc522.MFRC522Common package
// for implementation details.
type MFRC522Driver struct {
*Driver
*mfrc522.MFRC522Common
}
// NewMFRC522Driver creates a new Gobot Driver for MFRC522 RFID with SPI connection
//
// Params:
// a *Adaptor - the Adaptor to use with this Driver
//
// Optional params:
// spi.WithBusNumber(int): bus to use with this driver
// spi.WithChipNumber(int): chip to use with this driver
// spi.WithMode(int): mode to use with this driver
// spi.WithBitCount(int): number of bits to use with this driver
// spi.WithSpeed(int64): speed in Hz to use with this driver
//
func NewMFRC522Driver(a Connector, options ...func(Config)) *MFRC522Driver {
d := &MFRC522Driver{
Driver: NewDriver(a, "MFRC522"),
}
d.MFRC522Common = mfrc522.NewMFRC522Common()
d.afterStart = d.initialize
for _, option := range options {
option(d)
}
return d
}
func (d *MFRC522Driver) initialize() error {
wrapper := &conWrapper{origCon: d.connection}
return d.MFRC522Common.Initialize(wrapper)
}
// this is necessary due to special behavior of shift bytes and set first bit
type conWrapper struct {
origCon Connection
}
func (w *conWrapper) ReadByteData(reg uint8) (uint8, error) {
// MSBit=1 for reading, LSBit not used for first byte (address/register)
return w.origCon.ReadByteData(0x80 | (reg << 1))
}
func (w *conWrapper) WriteByteData(reg uint8, val uint8) error {
// LSBit not used for first byte (address/register)
return w.origCon.WriteByteData(reg<<1, val)
}

View File

@ -0,0 +1,44 @@
package spi
import (
"strings"
"testing"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)
// this ensures that the implementation is based on spi.Driver, which implements the gobot.Driver
// and tests all implementations, so no further tests needed here for gobot.Driver interface
var _ gobot.Driver = (*MFRC522Driver)(nil)
func initTestMFRC522DriverWithStubbedAdaptor() (*MFRC522Driver, *spiTestAdaptor) {
a := newSpiTestAdaptor()
d := NewMFRC522Driver(a)
if err := d.Start(); err != nil {
panic(err)
}
// reset the written bytes during Start()
a.spi.Reset()
return d, a
}
func TestNewMFRC522Driver(t *testing.T) {
var di interface{} = NewMFRC522Driver(newSpiTestAdaptor())
d, ok := di.(*MFRC522Driver)
if !ok {
t.Errorf("NewMFRC522Driver() should have returned a *MFRC522Driver")
}
gobottest.Refute(t, d.Driver, nil)
gobottest.Assert(t, strings.HasPrefix(d.Name(), "MFRC522"), true)
}
func TestMFRC522WriteByteData(t *testing.T) {
// arrange
d, a := initTestMFRC522DriverWithStubbedAdaptor()
// act
err := d.connection.WriteByteData(0x00, 0x00)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, a.spi.Written(), []byte{0x00, 0x00})
}

View File

@ -0,0 +1,140 @@
package spi
import (
"fmt"
"sync"
"gobot.io/x/gobot"
)
const (
spiDebugByte = false
spiDebugBlock = false
)
// spiConnection is the common implementation of the SPI bus interface.
type spiConnection struct {
spiSystem gobot.SpiSystemDevicer
mutex sync.Mutex
}
// NewConnection uses the given SPI system device and provides it as gobot.SpiOperations
// and Implements gobot.BusOperations.
func NewConnection(spiSystem gobot.SpiSystemDevicer) *spiConnection {
return &spiConnection{spiSystem: spiSystem}
}
// ReadCommandData uses the SPI device TX to send/receive data. Implements gobot.SpiOperations
// On write command, the first byte normally contains the address and mode.
// On read data, the return value is most likely one byte behind the command.
// The length of command and data needs to be the same (except data is nil).
func (c *spiConnection) ReadCommandData(command []byte, data []byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.txRxAndCheckReadLength(command, data)
}
// Close connection to underlying SPI device.
func (c *spiConnection) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.spiSystem.Close()
}
// ReadByteData reads a byte from the given register of SPI device. Implements gobot.BusOperations.
func (c *spiConnection) ReadByteData(reg uint8) (uint8, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
buf := []byte{0x0}
if err := c.readAlignedBlockData(reg, buf); err != nil {
return 0, err
}
if spiDebugByte {
fmt.Printf("ReadByteData: register 0x%02X/0x%02X : 0x%02X %dd\n", reg, reg&0x7F>>1, buf[0], buf[0])
}
return buf[0], nil
}
// ReadBlockData fills the given buffer with reads starting from the given register of SPI device.
// Implements gobot.BusOperations.
func (c *spiConnection) ReadBlockData(reg uint8, data []byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if err := c.readAlignedBlockData(reg, data); err != nil {
return err
}
if spiDebugBlock {
fmt.Printf("ReadBlockData: register 0x%02X/0x%02X : %v\n", reg, reg&0x7F>>1, data)
}
return nil
}
// WriteByte writes the given byte value to the current register of SPI device. Implements gobot.BusOperations.
func (c *spiConnection) WriteByte(val byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.writeBytes([]byte{val})
}
// WriteByteData writes the given byte value to the given register of SPI device. Implements gobot.BusOperations.
func (c *spiConnection) WriteByteData(reg byte, data byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.writeBytes([]byte{reg, data})
}
// WriteBlockData writes the given data starting from the given register of SPI device. Implements gobot.BusOperations.
func (c *spiConnection) WriteBlockData(reg byte, data []byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()
buf := make([]byte, len(data)+1)
copy(buf[1:], data)
buf[0] = reg
return c.writeBytes(buf)
}
// WriteBytes writes the given data starting from the current register of bus device. Implements gobot.BusOperations.
func (c *spiConnection) WriteBytes(data []byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()
return c.writeBytes(data)
}
func (c *spiConnection) readAlignedBlockData(reg uint8, data []byte) error {
// length of TX needs to equal length of RX
// the read value is one cycle behind the write, so for n bytes to read, we need n+1 bytes (to read and write)
buflen := len(data) + 1
writeBuf := make([]byte, buflen)
readBuf := make([]byte, buflen)
writeBuf[0] = reg
if err := c.txRxAndCheckReadLength(writeBuf, readBuf); err != nil {
return err
}
copy(data, readBuf[1:])
return nil
}
func (c *spiConnection) writeBytes(data []byte) error {
return c.txRxAndCheckReadLength(data, nil)
}
func (c *spiConnection) txRxAndCheckReadLength(tx []byte, rx []byte) error {
dataLen := len(rx)
if err := c.spiSystem.TxRx(tx, rx); err != nil {
return err
}
if len(rx) != dataLen {
return fmt.Errorf("Read length (%d) differ to expected (%d)", len(rx), dataLen)
}
return nil
}

View File

@ -0,0 +1,125 @@
package spi
import (
"testing"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
"gobot.io/x/gobot/system"
)
var _ gobot.SpiOperations = (*spiConnection)(nil)
func initTestConnectionWithMockedSystem() (Connection, *system.MockSpiAccess) {
a := system.NewAccesser()
sysdev := a.UseMockSpi()
const (
busNum = 15
chipNum = 14
mode = 13
bits = 12
maxSpeed = int64(11)
)
d, err := a.NewSpiDevice(busNum, chipNum, mode, bits, maxSpeed)
if err != nil {
panic(err)
}
c := NewConnection(d)
return c, sysdev
}
func TestReadCommandData(t *testing.T) {
// arrange
command := []byte{0x11, 0x12}
want := []byte{0x31, 0x32}
c, sysdev := initTestConnectionWithMockedSystem()
sysdev.SetSimRead(want)
// act
got := []byte{0x01, 0x02}
err := c.ReadCommandData(command, got)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, sysdev.Written(), command)
gobottest.Assert(t, got, want)
}
func TestReadByteData(t *testing.T) {
// arrange
const (
reg = 0x15
want = uint8(0x41)
)
c, sysdev := initTestConnectionWithMockedSystem()
sysdev.SetSimRead([]byte{0x00, want}) // the answer is one cycle behind
// act
got, err := c.ReadByteData(reg)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, sysdev.Written(), []byte{reg, 0x00}) // for read register we need n+1 bytes
gobottest.Assert(t, got, want)
}
func TestReadBlockData(t *testing.T) {
// arrange
const (
reg = 0x16
)
want := []byte{42, 24, 56, 65}
c, sysdev := initTestConnectionWithMockedSystem()
sysdev.SetSimRead(append([]byte{0x00}, want...)) // the answer is one cycle behind
// act
got := make([]byte, 4)
err := c.ReadBlockData(reg, got)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, sysdev.Written(), []byte{reg, 0x00, 0x00, 0x00, 0x00}) // for read registers we need n+1 bytes
gobottest.Assert(t, got, want)
}
func TestWriteByte(t *testing.T) {
// arrange
const want = 0x02
c, sysdev := initTestConnectionWithMockedSystem()
// act
err := c.WriteByte(want)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, sysdev.Written(), []byte{want})
}
func TestWriteByteData(t *testing.T) {
// arrange
const (
reg = 0x22
val = 0x33
)
c, sysdev := initTestConnectionWithMockedSystem()
// act
err := c.WriteByteData(reg, val)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, sysdev.Written(), []byte{reg, val})
}
func TestWriteBlockData(t *testing.T) {
// arrange
const reg = 0x33
data := []byte{0x22, 0x11}
c, sysdev := initTestConnectionWithMockedSystem()
// act
err := c.WriteBlockData(reg, data)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, sysdev.Written(), append([]byte{reg}, data...))
}
func TestWriteBytes(t *testing.T) {
// arrange
want := []byte{0x03}
c, sysdev := initTestConnectionWithMockedSystem()
// act
err := c.WriteBytes(want)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, sysdev.Written(), want)
}

View File

@ -0,0 +1,76 @@
// +build example
//
// Do not build by default.
package main
import (
"fmt"
"time"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/spi"
"gobot.io/x/gobot/platforms/adaptors"
"gobot.io/x/gobot/platforms/tinkerboard"
)
// Wiring
// PWR Tinkerboard: 1 (+3.3V, VCC), 2(+5V), 6, 9, 14, 20 (GND)
// GPIO-SPI Tinkerboard (same as SPI2): 23 (CLK), 19 (TXD), 21 (RXD), 24 (CSN0)
// MFRC522 plate: VCC, GND, SCK (CLK), MOSI (->TXD), MISO (->RXD), NSS/SDA (CSN0/CSN1?)
const (
sclk = "23"
nss = "24"
mosi = "19"
miso = "21"
speedHz = 5000 // more than 15kHz is not possible with GPIO's, so we choose 5kHz
)
func main() {
a := tinkerboard.NewAdaptor(adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso))
d := spi.NewMFRC522Driver(a, spi.WithSpeed(speedHz))
wasCardDetected := false
const textToCard = "Hello RFID user!\nHey, we use GPIO's for SPI."
work := func() {
if err := d.PrintReaderVersion(); err != nil {
fmt.Println("get version err:", err)
}
gobot.Every(2*time.Second, func() {
if !wasCardDetected {
fmt.Println("\n+++ poll for card +++")
if err := d.IsCardPresent(); err != nil {
fmt.Println("no card found")
} else {
fmt.Println("\n+++ write card +++")
err := d.WriteText(textToCard)
if err != nil {
fmt.Println("write err:", err)
}
wasCardDetected = true
}
} else {
fmt.Println("\n+++ read card +++")
text, err := d.ReadText()
if err != nil {
fmt.Println("read err:", err)
wasCardDetected = false
} else {
fmt.Printf("-- start text --\n%s\n-- end text --\n", text)
}
}
})
}
robot := gobot.NewRobot("gpioSpiBot",
[]gobot.Connection{a},
[]gobot.Device{d},
work,
)
robot.Start()
}

View File

@ -0,0 +1,66 @@
// +build example
//
// Do not build by default.
package main
import (
"fmt"
"time"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/spi"
"gobot.io/x/gobot/platforms/tinkerboard"
)
// Wiring
// PWR Tinkerboard: 1 (+3.3V, VCC), 2(+5V), 6, 9, 14, 20 (GND)
// SPI0 Tinkerboard (not working with armbian): 11 (CLK), 13 (TXD), 15 (RXD), 29 (CSN0), 31 (CSN1, n.c.)
// SPI2 Tinkerboard: 23 (CLK), 19 (TXD), 21 (RXD), 24 (CSN0), 26 (CSN1, n.c.)
// MFRC522 plate: VCC, GND, SCK (CLK), MOSI (->TXD), MISO (->RXD), NSS/SDA (CSN0/CSN1?)
func main() {
a := tinkerboard.NewAdaptor()
d := spi.NewMFRC522Driver(a, spi.WithBusNumber(2))
wasCardDetected := false
const textToCard = "Hello RFID user!\nThis text was written to card."
work := func() {
if err := d.PrintReaderVersion(); err != nil {
fmt.Println("get version err:", err)
}
gobot.Every(2*time.Second, func() {
if !wasCardDetected {
fmt.Println("\n+++ poll for card +++")
if err := d.IsCardPresent(); err != nil {
fmt.Println("no card found")
} else {
fmt.Println("\n+++ write card +++")
err := d.WriteText(textToCard)
if err != nil {
fmt.Println("write err:", err)
}
wasCardDetected = true
}
} else {
fmt.Println("\n+++ read card +++")
text, err := d.ReadText()
if err != nil {
fmt.Println("read err:", err)
wasCardDetected = false
} else {
fmt.Printf("-- start text --\n%s\n-- end text --\n", text)
}
}
})
}
robot := gobot.NewRobot("spiBot",
[]gobot.Connection{a},
[]gobot.Device{d},
work,
)
robot.Start()
}

View File

@ -12,8 +12,10 @@ import (
type digitalPinTranslator func(pin string) (chip string, line int, err error)
type digitalPinInitializer func(gobot.DigitalPinner) error
type digitalPinsOption interface {
type digitalPinsOptioner interface {
setDigitalPinInitializer(digitalPinInitializer)
setDigitalPinsForSystemGpiod()
setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin string)
}
// DigitalPinsAdaptor is a adaptor for digital pins, normally used for composition in platforms.
@ -30,7 +32,7 @@ type DigitalPinsAdaptor struct {
// to the internal file name or chip/line nomenclature. This varies by each platform. If for some reasons the default
// initializer is not suitable, it can be given by the option "WithDigitalPinInitializer()". This is especially needed,
// if some values needs to be adjusted after the pin was created but before the pin is exported.
func NewDigitalPinsAdaptor(sys *system.Accesser, t digitalPinTranslator, options ...func(digitalPinsOption)) *DigitalPinsAdaptor {
func NewDigitalPinsAdaptor(sys *system.Accesser, t digitalPinTranslator, options ...func(Optioner)) *DigitalPinsAdaptor {
a := &DigitalPinsAdaptor{
sys: sys,
translate: t,
@ -43,9 +45,33 @@ func NewDigitalPinsAdaptor(sys *system.Accesser, t digitalPinTranslator, options
}
// WithDigitalPinInitializer can be used to substitute the default initializer.
func WithDigitalPinInitializer(pc digitalPinInitializer) func(digitalPinsOption) {
return func(a digitalPinsOption) {
a.setDigitalPinInitializer(pc)
func WithDigitalPinInitializer(pc digitalPinInitializer) func(Optioner) {
return func(o Optioner) {
a, ok := o.(digitalPinsOptioner)
if ok {
a.setDigitalPinInitializer(pc)
}
}
}
// WithGpiodAccess can be used to change the default sysfs implementation to the character device Kernel ABI.
// The access is provided by the gpiod package.
func WithGpiodAccess() func(Optioner) {
return func(o Optioner) {
a, ok := o.(digitalPinsOptioner)
if ok {
a.setDigitalPinsForSystemGpiod()
}
}
}
// WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage.
func WithSpiGpioAccess(sclkPin, nssPin, mosiPin, misoPin string) func(Optioner) {
return func(o Optioner) {
a, ok := o.(digitalPinsOptioner)
if ok {
a.setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin)
}
}
}
@ -111,6 +137,14 @@ func (a *DigitalPinsAdaptor) setDigitalPinInitializer(pinInit digitalPinInitiali
a.initialize = pinInit
}
func (a *DigitalPinsAdaptor) setDigitalPinsForSystemGpiod() {
system.WithDigitalPinGpiodAccess()(a.sys)
}
func (a *DigitalPinsAdaptor) setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin string) {
system.WithSpiGpioAccess(a, sclkPin, nssPin, mosiPin, misoPin)(a.sys)
}
func (a *DigitalPinsAdaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) {
if a.pins == nil {
return nil, fmt.Errorf("not connected")

View File

@ -17,7 +17,7 @@ type I2cBusAdaptor struct {
validateNumber i2cBusNumberValidator
defaultBusNumber int
mutex sync.Mutex
buses map[int]i2c.I2cDevice
connections map[string]i2c.Connection
}
// NewI2cBusAdaptor provides the access to i2c buses of the board. The validator is used to check the bus number,
@ -36,7 +36,7 @@ func (a *I2cBusAdaptor) Connect() error {
a.mutex.Lock()
defer a.mutex.Unlock()
a.buses = make(map[int]i2c.I2cDevice)
a.connections = make(map[string]i2c.Connection)
return nil
}
@ -46,38 +46,41 @@ func (a *I2cBusAdaptor) Finalize() error {
defer a.mutex.Unlock()
var err error
for _, bus := range a.buses {
if bus != nil {
if e := bus.Close(); e != nil {
for _, con := range a.connections {
if con != nil {
if e := con.Close(); e != nil {
err = multierror.Append(err, e)
}
}
}
a.buses = nil
a.connections = nil
return err
}
// GetI2cConnection returns a connection to a device on a specified i2c bus
func (a *I2cBusAdaptor) GetI2cConnection(address int, busNr int) (connection i2c.Connection, err error) {
func (a *I2cBusAdaptor) GetI2cConnection(address int, busNum int) (connection i2c.Connection, err error) {
a.mutex.Lock()
defer a.mutex.Unlock()
if a.buses == nil {
if a.connections == nil {
return nil, fmt.Errorf("not connected")
}
bus := a.buses[busNr]
if bus == nil {
if err := a.validateNumber(busNr); err != nil {
id := fmt.Sprintf("%d_%d", busNum, address)
con := a.connections[id]
if con == nil {
if err := a.validateNumber(busNum); err != nil {
return nil, err
}
bus, err = a.sys.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", busNr))
bus, err := a.sys.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", busNum))
if err != nil {
return nil, err
}
a.buses[busNr] = bus
con = i2c.NewConnection(bus, address)
a.connections[id] = con
}
return i2c.NewConnection(bus, address), err
return con, err
}
// DefaultI2cBus returns the default i2c bus number for this platform.

View File

@ -34,11 +34,11 @@ func initTestI2cAdaptorWithMockedFilesystem(mockPaths []string) (*I2cBusAdaptor,
func TestI2cWorkflow(t *testing.T) {
a, _ := initTestI2cAdaptorWithMockedFilesystem([]string{i2cBus1})
gobottest.Assert(t, len(a.buses), 0)
gobottest.Assert(t, len(a.connections), 0)
con, err := a.GetI2cConnection(0xff, 1)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.buses), 1)
gobottest.Assert(t, len(a.connections), 1)
_, err = con.Write([]byte{0x00, 0x01})
gobottest.Assert(t, err, nil)
@ -49,7 +49,7 @@ func TestI2cWorkflow(t *testing.T) {
gobottest.Assert(t, data, []byte{0x00, 0x01})
gobottest.Assert(t, a.Finalize(), nil)
gobottest.Assert(t, len(a.buses), 0)
gobottest.Assert(t, len(a.connections), 0)
}
func TestI2cGetI2cConnection(t *testing.T) {
@ -59,18 +59,18 @@ func TestI2cGetI2cConnection(t *testing.T) {
c1, e1 := a.GetI2cConnection(0xff, 1)
gobottest.Assert(t, e1, nil)
gobottest.Refute(t, c1, nil)
gobottest.Assert(t, len(a.buses), 1)
gobottest.Assert(t, len(a.connections), 1)
// assert invalid bus gets error
c2, e2 := a.GetI2cConnection(0x01, 99)
gobottest.Assert(t, e2, fmt.Errorf("99 not valid"))
gobottest.Assert(t, c2, nil)
gobottest.Assert(t, len(a.buses), 1)
gobottest.Assert(t, len(a.connections), 1)
// assert unconnected gets error
gobottest.Assert(t, a.Finalize(), nil)
c3, e3 := a.GetI2cConnection(0x01, 99)
gobottest.Assert(t, e3, fmt.Errorf("not connected"))
gobottest.Assert(t, c3, nil)
gobottest.Assert(t, len(a.buses), 0)
gobottest.Assert(t, len(a.connections), 0)
}
func TestI2cFinalize(t *testing.T) {
@ -81,16 +81,16 @@ func TestI2cFinalize(t *testing.T) {
// arrange
gobottest.Assert(t, a.Connect(), nil)
a.GetI2cConnection(0xaf, 1)
gobottest.Assert(t, len(a.buses), 1)
gobottest.Assert(t, len(a.connections), 1)
// assert that Finalize after GetI2cConnection is working and clean up
gobottest.Assert(t, a.Finalize(), nil)
gobottest.Assert(t, len(a.buses), 0)
gobottest.Assert(t, len(a.connections), 0)
// assert that finalize after finalize is working
gobottest.Assert(t, a.Finalize(), nil)
// assert that close error is recognized
gobottest.Assert(t, a.Connect(), nil)
con, _ := a.GetI2cConnection(0xbf, 1)
gobottest.Assert(t, len(a.buses), 1)
gobottest.Assert(t, len(a.connections), 1)
con.Write([]byte{0xbf})
fs.WithCloseError = true
err := a.Finalize()
@ -104,8 +104,8 @@ func TestI2cReConnect(t *testing.T) {
// act
gobottest.Assert(t, a.Connect(), nil)
// assert
gobottest.Refute(t, a.buses, nil)
gobottest.Assert(t, len(a.buses), 0)
gobottest.Refute(t, a.connections, nil)
gobottest.Assert(t, len(a.connections), 0)
}
func TestI2cGetDefaultBus(t *testing.T) {

View File

@ -0,0 +1,7 @@
package adaptors
// Optioner is the interface for adaptors options. This provides the possibility for change the platform behavior
// by the user when creating the platform, e.g. by "NewAdaptor()".
type Optioner interface {
digitalPinsOptioner
}

View File

@ -84,9 +84,11 @@ func (a *SpiBusAdaptor) GetSpiConnection(busNum, chipNum, mode, bits int, maxSpe
return nil, err
}
var err error
if con, err = a.sys.NewSpiConnection(busNum, chipNum, mode, bits, maxSpeed); err != nil {
bus, err := a.sys.NewSpiDevice(busNum, chipNum, mode, bits, maxSpeed)
if err != nil {
return nil, err
}
con = spi.NewConnection(bus)
a.connections[id] = con
}

View File

@ -48,7 +48,11 @@ type Adaptor struct {
}
// NewAdaptor returns a new Beaglebone Black/Green Adaptor
func NewAdaptor() *Adaptor {
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser()
c := &Adaptor{
name: gobot.DefaultName("BeagleboneBlack"),
@ -60,7 +64,7 @@ func NewAdaptor() *Adaptor {
analogPath: "/sys/bus/iio/devices/iio:device0",
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateAndMuxDigitalPin)
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateAndMuxDigitalPin, opts...)
c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translateAndMuxPWMPin,
adaptors.WithPWMPinDefaultPeriod(pwmPeriodDefault))
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)

View File

@ -1,6 +1,9 @@
package beaglebone
import "gobot.io/x/gobot"
import (
"gobot.io/x/gobot"
"gobot.io/x/gobot/platforms/adaptors"
)
// PocketBeagleAdaptor is the Gobot Adaptor for the PocketBeagle
// For more information check out:
@ -11,8 +14,8 @@ type PocketBeagleAdaptor struct {
}
// NewPocketBeagleAdaptor creates a new Adaptor for the PocketBeagle
func NewPocketBeagleAdaptor() *PocketBeagleAdaptor {
a := NewAdaptor()
func NewPocketBeagleAdaptor(opts ...func(adaptors.Optioner)) *PocketBeagleAdaptor {
a := NewAdaptor(opts...)
a.SetName(gobot.DefaultName("PocketBeagle"))
a.pinMap = pocketBeaglePinMap
a.pwmPinMap = pocketBeaglePwmPinMap

View File

@ -36,7 +36,11 @@ type Adaptor struct {
}
// NewAdaptor creates a C.H.I.P. Adaptor
func NewAdaptor() *Adaptor {
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser()
c := &Adaptor{
name: gobot.DefaultName("CHIP"),
@ -50,7 +54,7 @@ func NewAdaptor() *Adaptor {
c.pinmap[pin] = sysfsPin{pin: baseAddr + i, pwmPin: -1}
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin)
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin)
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)
return c

View File

@ -280,7 +280,10 @@ func (c TestSpiDevice) Close() error {
return nil
}
func (c TestSpiDevice) ReadByteData(byte) (byte, error) { return 0, nil }
func (c TestSpiDevice) ReadBlockData(byte, []byte) error { return nil }
func (c TestSpiDevice) WriteByte(byte) error { return nil }
func (c TestSpiDevice) WriteByteData(byte, byte) error { return nil }
func (c TestSpiDevice) WriteBlockData(byte, []byte) error { return nil }
func (c TestSpiDevice) WriteBytes([]byte) error { return nil }

View File

@ -44,13 +44,17 @@ var fixedPins = map[string]int{
}
// NewAdaptor creates a DragonBoard 410c Adaptor
func NewAdaptor() *Adaptor {
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser()
c := &Adaptor{
name: gobot.DefaultName("DragonBoard"),
sys: sys,
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin)
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)
c.pinMap = fixedPins
for i := 0; i < 122; i++ {

View File

@ -28,13 +28,17 @@ type Adaptor struct {
}
// NewAdaptor returns a new Joule Adaptor
func NewAdaptor() *Adaptor {
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser()
c := &Adaptor{
name: gobot.DefaultName("Joule"),
sys: sys,
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin)
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin, adaptors.WithPWMPinInitializer(pwmPinInitializer))
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)
return c

View File

@ -35,13 +35,17 @@ type Adaptor struct {
}
// NewAdaptor creates a Jetson Nano adaptor
func NewAdaptor() *Adaptor {
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser()
c := &Adaptor{
name: gobot.DefaultName("JetsonNano"),
sys: sys,
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin)
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)
c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber,
defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed)

View File

@ -39,14 +39,18 @@ type Adaptor struct {
}
// NewAdaptor creates a Raspi Adaptor
func NewAdaptor() *Adaptor {
sys := system.NewAccesser("cdev")
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser(system.WithDigitalPinGpiodAccess())
c := &Adaptor{
name: gobot.DefaultName("RaspberryPi"),
sys: sys,
PiBlasterPeriod: 10000000,
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.getPinTranslatorFunction())
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.getPinTranslatorFunction(), opts...)
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, 1)
c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber,
defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed)

View File

@ -42,13 +42,17 @@ type Adaptor struct {
}
// NewAdaptor creates a Tinkerboard Adaptor
func NewAdaptor() *Adaptor {
sys := system.NewAccesser("cdev")
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser(system.WithDigitalPinGpiodAccess())
c := &Adaptor{
name: gobot.DefaultName("Tinker Board"),
sys: sys,
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin)
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin,
adaptors.WithPolarityInvertedIdentifier(pwmInvertedIdentifier))
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)

View File

@ -50,7 +50,11 @@ type Adaptor struct {
}
// NewAdaptor creates a UP2 Adaptor
func NewAdaptor() *Adaptor {
//
// Optional parameters:
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
sys := system.NewAccesser()
c := &Adaptor{
name: gobot.DefaultName("UP2"),
@ -58,7 +62,7 @@ func NewAdaptor() *Adaptor {
ledPath: "/sys/class/leds/upboard:%s:/brightness",
pinmap: fixedPins,
}
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin)
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin)
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber)
c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber,

View File

@ -7,7 +7,7 @@ import (
"testing"
"unsafe"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)
@ -62,7 +62,7 @@ func TestNewI2cDevice(t *testing.T) {
gobottest.Assert(t, err.Error(), tc.wantErr)
gobottest.Assert(t, d, (*i2cDevice)(nil))
} else {
var _ i2c.I2cDevice = d
var _ gobot.I2cSystemDevicer = d
gobottest.Assert(t, err, nil)
}
})

View File

@ -1,9 +1,33 @@
package system
import "gobot.io/x/gobot"
import (
"gobot.io/x/gobot"
)
type periphioSpiAccess struct{}
func (*periphioSpiAccess) createConnection(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiOperations, error) {
return newSpiConnectionPeriphIo(busNum, chipNum, mode, bits, maxSpeed)
type periphioSpiAccess struct {
fs filesystem
}
type gpioSpiAccess struct {
cfg spiGpioConfig
}
func (*periphioSpiAccess) createDevice(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiSystemDevicer, error) {
return newSpiPeriphIo(busNum, chipNum, mode, bits, maxSpeed)
}
func (psa *periphioSpiAccess) isSupported() bool {
devices, err := psa.fs.find("/dev", "spidev")
if err != nil || len(devices) == 0 {
return false
}
return true
}
func (gsa *gpioSpiAccess) createDevice(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiSystemDevicer, error) {
return newSpiGpio(gsa.cfg, maxSpeed)
}
func (gsa *gpioSpiAccess) isSupported() bool {
return true
}

43
system/spi_access_test.go Normal file
View File

@ -0,0 +1,43 @@
package system
import (
"testing"
"gobot.io/x/gobot/gobottest"
)
func TestGpioSpi_isSupported(t *testing.T) {
// arrange
gsa := gpioSpiAccess{}
// act
got := gsa.isSupported()
// assert
gobottest.Assert(t, got, true)
}
func TestPeriphioSpi_isSupported(t *testing.T) {
var tests = map[string]struct {
mockPaths []string
want bool
}{
"supported": {
mockPaths: []string{"/dev/spidev0.0", "/dev/spidev1.0"},
want: true,
},
"not_supported": {
mockPaths: []string{"/sys/class/gpio/"},
want: false,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
fs := newMockFilesystem(tc.mockPaths)
psa := periphioSpiAccess{fs: fs}
// act
got := psa.isSupported()
// assert
gobottest.Assert(t, got, tc.want)
})
}
}

165
system/spi_gpio.go Normal file
View File

@ -0,0 +1,165 @@
package system
import (
"fmt"
"time"
"gobot.io/x/gobot"
)
type spiGpioConfig struct {
pinProvider gobot.DigitalPinnerProvider
sclkPinID string
nssPinID string
mosiPinID string
misoPinID string
}
// spiGpio is the implementation of the SPI interface using GPIO's.
type spiGpio struct {
cfg spiGpioConfig
// time between clock edges (i.e. half the cycle time)
tclk time.Duration
sclkPin gobot.DigitalPinner
nssPin gobot.DigitalPinner
mosiPin gobot.DigitalPinner
misoPin gobot.DigitalPinner
}
// newSpiGpio creates and returns a new SPI connection based on given GPIO's.
func newSpiGpio(cfg spiGpioConfig, maxSpeed int64) (*spiGpio, error) {
spi := &spiGpio{cfg: cfg}
spi.initializeTime(maxSpeed)
return spi, spi.initializeGpios()
}
func (s *spiGpio) initializeTime(maxSpeed int64) {
// maxSpeed is given in Hz, tclk is half the cycle time, tclk=1/(2*f), tclk[ns]=1 000 000 000/(2*maxSpeed)
// but with gpio's a speed of more than ~15kHz is most likely not possible, so we limit to 10kHz
if maxSpeed > 10000 {
if systemDebug {
fmt.Printf("reduce SPI speed for GPIO usage to 10Khz")
}
maxSpeed = 10000
}
tclk := time.Duration(1000000000/2/maxSpeed) * time.Nanosecond
if systemDebug {
fmt.Println("clk", tclk)
}
}
// TxRx uses the SPI device to send/receive data. Implements gobot.SpiSystemDevicer.
func (s *spiGpio) TxRx(tx []byte, rx []byte) error {
var doRx bool
if rx != nil {
doRx = true
if len(tx) != len(rx) {
return fmt.Errorf("length of tx (%d) must be the same as length of rx (%d)", len(tx), len(rx))
}
}
if err := s.nssPin.Write(0); err != nil {
return err
}
for idx, b := range tx {
val, err := s.transferByte(b)
if err != nil {
return err
}
if doRx {
rx[idx] = val
}
}
return s.nssPin.Write(1)
}
// Close the SPI connection. Implements gobot.SpiSystemDevicer.
func (s *spiGpio) Close() error {
if s.sclkPin != nil {
s.sclkPin.Unexport()
}
if s.mosiPin != nil {
s.mosiPin.Unexport()
}
if s.misoPin != nil {
s.misoPin.Unexport()
}
if s.nssPin != nil {
s.nssPin.Unexport()
}
return nil
}
func (cfg *spiGpioConfig) String() string {
return fmt.Sprintf("sclk: %s, nss: %s, mosi: %s, miso: %s", cfg.sclkPinID, cfg.nssPinID, cfg.mosiPinID, cfg.misoPinID)
}
// transferByte simultaneously transmit and receive a byte
// polarity and phase are assumed to be both 0 (CPOL=0, CPHA=0), so:
// * input data is captured on rising edge of SCLK
// * output data is propagated on falling edge of SCLK
func (s *spiGpio) transferByte(txByte uint8) (uint8, error) {
rxByte := uint8(0)
bitMask := uint8(0x80) // start at MSBit
for i := 0; i < 8; i++ {
if err := s.mosiPin.Write(int(txByte & bitMask)); err != nil {
return 0, err
}
time.Sleep(s.tclk)
if err := s.sclkPin.Write(1); err != nil {
return 0, err
}
v, err := s.misoPin.Read()
if err != nil {
return 0, err
}
if v != 0 {
rxByte |= bitMask
}
time.Sleep(s.tclk)
if err := s.sclkPin.Write(0); err != nil {
return 0, err
}
bitMask = bitMask >> 1 // next lower bit
}
return rxByte, nil
}
func (s *spiGpio) initializeGpios() error {
var err error
// nss is an output, negotiated (currently not implemented at pin level)
s.nssPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.nssPinID)
if err != nil {
return err
}
if err := s.nssPin.ApplyOptions(WithDirectionOutput(1)); err != nil {
return err
}
// sclk is an output, CPOL = 0
s.sclkPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.sclkPinID)
if err != nil {
return err
}
if err := s.sclkPin.ApplyOptions(WithDirectionOutput(0)); err != nil {
return err
}
// miso is an input
s.misoPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.misoPinID)
if err != nil {
return err
}
// mosi is an output
s.mosiPin, err = s.cfg.pinProvider.DigitalPin(s.cfg.mosiPinID)
if err != nil {
return err
}
return s.mosiPin.ApplyOptions(WithDirectionOutput(0))
}

View File

@ -14,50 +14,60 @@ type MockSpiAccess struct {
mode int
bits int
maxSpeed int64
connection *spiConnectionMock
sysdev *spiMock
}
func (spi *MockSpiAccess) createConnection(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiOperations, error) {
func (spi *MockSpiAccess) createDevice(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiSystemDevicer, error) {
spi.busNum = busNum
spi.chipNum = chipNum
spi.mode = mode
spi.bits = bits
spi.maxSpeed = maxSpeed
spi.connection = newSpiConnectionMock(busNum, chipNum, mode, bits, maxSpeed)
spi.sysdev = newSpiMock(busNum, chipNum, mode, bits, maxSpeed)
var err error
if spi.CreateError {
err = fmt.Errorf("error while create SPI connection in mock")
}
return spi.connection, err
return spi.sysdev, err
}
func (*MockSpiAccess) isSupported() bool {
return true
}
// SetReadError can be used to simulate a read error.
func (spi *MockSpiAccess) SetReadError(val bool) {
spi.connection.simReadErr = val
spi.sysdev.simReadErr = val
}
// SetWriteError can be used to simulate a write error.
func (spi *MockSpiAccess) SetWriteError(val bool) {
spi.connection.simWriteErr = val
spi.sysdev.simWriteErr = val
}
// SetCloseError can be used to simulate a error on Close().
func (spi *MockSpiAccess) SetCloseError(val bool) {
spi.connection.simCloseErr = val
spi.sysdev.simCloseErr = val
}
// SetSimRead is used to set the byte stream for next read.
func (spi *MockSpiAccess) SetSimRead(val []byte) {
spi.connection.simRead = val
func (spi *MockSpiAccess) SetSimRead(data []byte) {
spi.sysdev.simRead = make([]byte, len(data))
copy(spi.sysdev.simRead, data)
}
// Written returns the byte stream which was last written.
func (spi *MockSpiAccess) Written() []byte {
return spi.connection.written
return spi.sysdev.written
}
// spiConnectionMock is the a mock implementation, used in tests
type spiConnectionMock struct {
// Reset resets the last written values.
func (spi *MockSpiAccess) Reset() {
spi.sysdev.written = []byte{}
}
// spiMock is the a mock implementation, used in tests
type spiMock struct {
id string
simReadErr bool
simWriteErr bool
@ -66,48 +76,27 @@ type spiConnectionMock struct {
simRead []byte
}
// newspiConnectionMock creates and returns a new connection to a specific
// newSpiMock creates and returns a new connection to a specific
// spi device on a bus/chip using the periph.io interface.
func newSpiConnectionMock(busNum, chipNum, mode, bits int, maxSpeed int64) *spiConnectionMock {
return &spiConnectionMock{id: fmt.Sprintf("bu:%d, c:%d, m:%d, bi:%d, s:%d", busNum, chipNum, mode, bits, maxSpeed)}
func newSpiMock(busNum, chipNum, mode, bits int, maxSpeed int64) *spiMock {
return &spiMock{id: fmt.Sprintf("bu:%d, c:%d, m:%d, bi:%d, s:%d", busNum, chipNum, mode, bits, maxSpeed)}
}
// Close the SPI connection. Implements gobot.BusOperations.
func (c *spiConnectionMock) Close() error {
// Close the SPI connection to the device. Implements gobot.SpiSystemDevicer.
func (c *spiMock) Close() error {
if c.simCloseErr {
return fmt.Errorf("error while SPI close in mock")
}
return nil
}
// ReadCommandData uses the SPI device TX to send/receive data.
func (c *spiConnectionMock) ReadCommandData(command []byte, data []byte) error {
// TxRx uses the SPI device TX to send/receive data. gobot.SpiSystemDevicer.
func (c *spiMock) TxRx(tx []byte, rx []byte) error {
if c.simReadErr {
return fmt.Errorf("error while SPI read in mock")
}
c.written = append(c.written, command...)
copy(data, c.simRead)
return nil
}
// WriteByte uses the SPI device TX to send a byte value. Implements gobot.BusOperations.
func (c *spiConnectionMock) WriteByte(val byte) error {
return c.WriteBytes([]byte{val})
}
// WriteBlockData uses the SPI device TX to send data. Implements gobot.BusOperations.
func (c *spiConnectionMock) WriteBlockData(reg byte, data []byte) error {
buf := make([]byte, len(data)+1)
copy(buf[1:], data)
buf[0] = reg
return c.WriteBytes(data)
}
// WriteBytes uses the SPI device TX to send data. Implements gobot.BusOperations.
func (c *spiConnectionMock) WriteBytes(data []byte) error {
if c.simWriteErr {
return fmt.Errorf("error while SPI write in mock")
}
c.written = append(c.written, data...)
c.written = append(c.written, tx...)
// the answer can be one cycle behind, this must be considered in test setup
copy(rx, c.simRead)
return nil
}

View File

@ -8,15 +8,15 @@ import (
xsysfs "periph.io/x/host/v3/sysfs"
)
// spiConnectionPeriphIo is the implementation of the SPI interface using the periph.io sysfs implementation for Linux.
type spiConnectionPeriphIo struct {
// spiPeriphIo is the implementation of the SPI interface using the periph.io sysfs implementation for Linux.
type spiPeriphIo struct {
port xspi.PortCloser
dev xspi.Conn
}
// newSpiConnectionPeriphIo creates and returns a new connection to a specific SPI device on a bus/chip
// newSpiPeriphIo creates and returns a new connection to a specific SPI device on a bus/chip
// using the periph.io interface.
func newSpiConnectionPeriphIo(busNum, chipNum, mode, bits int, maxSpeed int64) (*spiConnectionPeriphIo, error) {
func newSpiPeriphIo(busNum, chipNum, mode, bits int, maxSpeed int64) (*spiPeriphIo, error) {
p, err := xsysfs.NewSPI(busNum, chipNum)
if err != nil {
return nil, err
@ -25,40 +25,22 @@ func newSpiConnectionPeriphIo(busNum, chipNum, mode, bits int, maxSpeed int64) (
if err != nil {
return nil, err
}
return &spiConnectionPeriphIo{port: p, dev: c}, nil
return &spiPeriphIo{port: p, dev: c}, nil
}
// Close the SPI connection. Implements gobot.BusOperations.
func (c *spiConnectionPeriphIo) Close() error {
return c.port.Close()
}
// ReadCommandData uses the SPI device TX to send/receive data.
func (c *spiConnectionPeriphIo) ReadCommandData(command []byte, data []byte) error {
dataLen := len(data)
if err := c.dev.Tx(command, data); err != nil {
// TxRx uses the SPI device TX to send/receive data. Implements gobot.SpiSystemDevicer.
func (c *spiPeriphIo) TxRx(tx []byte, rx []byte) error {
dataLen := len(rx)
if err := c.dev.Tx(tx, rx); err != nil {
return err
}
if len(data) != dataLen {
return fmt.Errorf("Read length (%d) differ to expected (%d)", len(data), dataLen)
if len(rx) != dataLen {
return fmt.Errorf("Read length (%d) differ to expected (%d)", len(rx), dataLen)
}
return nil
}
// WriteByte uses the SPI device TX to send a byte value. Implements gobot.BusOperations.
func (c *spiConnectionPeriphIo) WriteByte(val byte) error {
return c.WriteBytes([]byte{val})
}
// WriteBlockData uses the SPI device TX to send data. Implements gobot.BusOperations.
func (c *spiConnectionPeriphIo) WriteBlockData(reg byte, data []byte) error {
buf := make([]byte, len(data)+1)
copy(buf[1:], data)
buf[0] = reg
return c.WriteBytes(data)
}
// WriteBytes uses the SPI device TX to send the given data.
func (c *spiConnectionPeriphIo) WriteBytes(data []byte) error {
return c.dev.Tx(data, nil)
// Close the SPI connection. Implements gobot.SpiSystemDevicer.
func (c *spiPeriphIo) Close() error {
return c.port.Close()
}

View File

@ -1,7 +1,6 @@
package system
import (
"fmt"
"os"
"syscall"
"unsafe"
@ -47,7 +46,8 @@ type digitalPinAccesser interface {
// spiAccesser represents unexposed interface to allow the switch between different implementations and a mocked one
type spiAccesser interface {
createConnection(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiOperations, error)
isSupported() bool
createDevice(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiSystemDevicer, error)
}
// Accesser provides access to system calls, filesystem, implementation for digital pin and SPI
@ -60,31 +60,17 @@ type Accesser struct {
// NewAccesser returns a accesser to native system call, native file system and the chosen digital pin access.
// Digital pin accesser can be empty or "sysfs", otherwise it will be automatically chosen.
func NewAccesser(digitalPinAccess ...string) *Accesser {
s := Accesser{
sys: &nativeSyscall{},
fs: &nativeFilesystem{},
spiAccess: &periphioSpiAccess{},
}
a := "sysfs"
if len(digitalPinAccess) > 0 && digitalPinAccess[0] != "" {
a = digitalPinAccess[0]
}
if a != "sysfs" {
dpa := &gpiodDigitalPinAccess{fs: s.fs}
if dpa.isSupported() {
s.digitalPinAccess = dpa
if systemDebug {
fmt.Printf("use gpiod driver for digital pins with this chips: %v\n", dpa.chips)
}
return &s
}
if systemDebug {
fmt.Println("gpiod driver not supported, fallback to sysfs")
}
func NewAccesser(options ...func(Optioner)) *Accesser {
s := &Accesser{
sys: &nativeSyscall{},
fs: &nativeFilesystem{},
}
s.spiAccess = &periphioSpiAccess{fs: s.fs}
s.digitalPinAccess = &sysfsDigitalPinAccess{fs: s.fs}
return &s
for _, option := range options {
option(s)
}
return s
}
// UseDigitalPinAccessWithMockFs sets the digital pin handler accesser to the chosen one. Used only for tests.
@ -145,9 +131,9 @@ func (a *Accesser) NewPWMPin(path string, pin int, polNormIdent string, polInvId
return newPWMPinSysfs(a.fs, path, pin, polNormIdent, polInvIdent)
}
// NewSpiConnection returns a new connection to SPI with the given parameters.
func (a *Accesser) NewSpiConnection(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiOperations, error) {
return a.spiAccess.createConnection(busNum, chipNum, mode, bits, maxSpeed)
// NewSpiDevice returns a new connection to SPI with the given parameters.
func (a *Accesser) NewSpiDevice(busNum, chipNum, mode, bits int, maxSpeed int64) (gobot.SpiSystemDevicer, error) {
return a.spiAccess.createDevice(busNum, chipNum, mode, bits, maxSpeed)
}
// OpenFile opens file of given name from native or the mocked file system

65
system/system_options.go Normal file
View File

@ -0,0 +1,65 @@
package system
import (
"fmt"
"gobot.io/x/gobot"
)
// Optioner is the interface for system options. This provides the possibility for change the systems behavior by the
// caller/user when creating the system access, e.g. by "NewAccesser()".
type Optioner interface {
setDigitalPinToGpiodAccess()
setSpiToGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, nssPin, mosiPin, misoPin string)
}
// WithDigitalPinGpiodAccess can be used to change the default sysfs implementation for digital pins to the character
// device Kernel ABI. The access is provided by the gpiod package.
func WithDigitalPinGpiodAccess() func(Optioner) {
return func(s Optioner) {
s.setDigitalPinToGpiodAccess()
}
}
// WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage.
func WithSpiGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, nssPin, mosiPin, misoPin string) func(Optioner) {
return func(s Optioner) {
s.setSpiToGpioAccess(p, sclkPin, nssPin, mosiPin, misoPin)
}
}
func (a *Accesser) setDigitalPinToGpiodAccess() {
dpa := &gpiodDigitalPinAccess{fs: a.fs}
if dpa.isSupported() {
a.digitalPinAccess = dpa
if systemDebug {
fmt.Printf("use gpiod driver for digital pins with this chips: %v\n", dpa.chips)
}
return
}
if systemDebug {
fmt.Println("gpiod driver not supported, fallback to sysfs")
}
}
func (a *Accesser) setSpiToGpioAccess(p gobot.DigitalPinnerProvider, sclkPin, nssPin, mosiPin, misoPin string) {
cfg := spiGpioConfig{
pinProvider: p,
sclkPinID: sclkPin,
nssPinID: nssPin,
mosiPinID: mosiPin,
misoPinID: misoPin,
}
gsa := &gpioSpiAccess{cfg: cfg}
if gsa.isSupported() {
a.spiAccess = gsa
if systemDebug {
fmt.Printf("use gpio driver for SPI with this config: %s\n", gsa.cfg.String())
}
return
}
if systemDebug {
fmt.Println("gpio driver not supported for SPI, fallback to periphio")
}
}

View File

@ -19,7 +19,7 @@ func TestNewAccesser(t *testing.T) {
gobottest.Refute(t, perphioSpi, nil)
}
func TestNewAccesser_NewSpiConnection(t *testing.T) {
func TestNewAccesser_NewSpiDevice(t *testing.T) {
// arrange
const (
@ -32,7 +32,7 @@ func TestNewAccesser_NewSpiConnection(t *testing.T) {
a := NewAccesser()
spi := a.UseMockSpi()
// act
con, err := a.NewSpiConnection(busNum, chipNum, mode, bits, maxSpeed)
con, err := a.NewSpiDevice(busNum, chipNum, mode, bits, maxSpeed)
// assert
gobottest.Assert(t, err, nil)
gobottest.Refute(t, con, nil)
@ -44,36 +44,35 @@ func TestNewAccesser_NewSpiConnection(t *testing.T) {
}
func TestNewAccesser_IsSysfsDigitalPinAccess(t *testing.T) {
const gpiodTestCase = "accesser_gpiod"
var tests = map[string]struct {
accesser string
wantSys bool
gpiodAccesser bool
wantSys bool
}{
"default_accesser_sysfs": {
wantSys: true,
},
"accesser_sysfs": {
accesser: "sysfs",
wantSys: true,
wantSys: true,
},
gpiodTestCase: {
accesser: "cdev",
wantSys: false,
"accesser_gpiod": {
gpiodAccesser: true,
wantSys: false,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
if name == gpiodTestCase {
a := NewAccesser()
if tc.gpiodAccesser {
// there is no mock at this level, so if the system do not support
// character device gpio, we skip the test
dpa := &gpiodDigitalPinAccess{fs: &nativeFilesystem{}}
if !dpa.isSupported() {
t.Skip()
}
WithDigitalPinGpiodAccess()(a)
}
// act
a := NewAccesser(tc.accesser)
got := a.IsSysfsDigitalPinAccess()
// assert
gobottest.Refute(t, a, nil)