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:
parent
fdf4f4b194
commit
6c7ecbe584
37
README.md
37
README.md
@ -7,9 +7,11 @@
|
||||
[](https://goreportcard.com/report/hybridgroup/gobot)
|
||||
[](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.
|
||||
|
45
adaptor.go
45
adaptor.go
@ -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
|
||||
|
39
drivers/common/mfrc522/mfrc522_connectionwrapper.go
Normal file
39
drivers/common/mfrc522/mfrc522_connectionwrapper.go
Normal 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
|
||||
}
|
382
drivers/common/mfrc522/mfrc522_pcd.go
Normal file
382
drivers/common/mfrc522/mfrc522_pcd.go
Normal 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")
|
||||
}
|
422
drivers/common/mfrc522/mfrc522_pcd_register.go
Normal file
422
drivers/common/mfrc522/mfrc522_pcd_register.go
Normal 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 register’s IRq bit
|
||||
// 0: signal on pin IRQ is equal to the IRq bit; in combination with the DivIEn register’s 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 register’s LoAlert bit is set in opposition to the LoAlert bit, the LoAlertIRq bit stores this event and
|
||||
// can only be reset as indicated by the Set1 bit in this register
|
||||
comIrqRegLoAlertIRqBit = 0x04 // bit 2: if 1, see above
|
||||
// the Status1 register’s HiAlert bit is set in opposition to the HiAlert bit, the HiAlertIRq bit stores this event
|
||||
// and can only be reset as indicated by the Set1 bit in this register
|
||||
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 register’s RxNoErr bit is set to logic 1,
|
||||
// the RxIRq bit is only set to logic 1 when data bytes are available in the FIFO
|
||||
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 register’s 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 MFRC522’s 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 register’s StartSend bit
|
||||
// the minimum time for TxWait is defined by the TxWait register
|
||||
status2RegModemStateTxWait = 0x02 // wait until RF field is present if the TMode register’s 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 register’s 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 buffer’s read and write pointer and Error register’s 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 receiver’s 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 register’s RxMultiple bit is not set, the timer stops immediately after receiving the 5th bit (1 start
|
||||
// bit, 4 data bits); if the RxMultiple bit is set to logic 1 the timer never stops, in which case the timer can be
|
||||
// stopped by setting the Control register’s 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 register’s TRunning bit is logic 1 when the timer is enabled by
|
||||
// the TMode register’s 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 register’s 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 register’s TPrescalEven bit in Demot register’s 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 register’s 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
|
||||
)
|
216
drivers/common/mfrc522/mfrc522_pcd_test.go
Normal file
216
drivers/common/mfrc522/mfrc522_pcd_test.go
Normal 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)
|
||||
}
|
403
drivers/common/mfrc522/mfrc522_picc.go
Normal file
403
drivers/common/mfrc522/mfrc522_picc.go
Normal 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
|
||||
}
|
@ -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.
|
||||
|
@ -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()
|
||||
|
38
drivers/i2c/mfrc522_driver.go
Normal file
38
drivers/i2c/mfrc522_driver.go
Normal 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)
|
||||
}
|
@ -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
|
||||
|
@ -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 }
|
||||
|
56
drivers/spi/mfrc522_driver.go
Normal file
56
drivers/spi/mfrc522_driver.go
Normal 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)
|
||||
}
|
44
drivers/spi/mfrc522_driver_test.go
Normal file
44
drivers/spi/mfrc522_driver_test.go
Normal 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})
|
||||
}
|
140
drivers/spi/spi_connection.go
Normal file
140
drivers/spi/spi_connection.go
Normal 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
|
||||
}
|
125
drivers/spi/spi_connection_test.go
Normal file
125
drivers/spi/spi_connection_test.go
Normal 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)
|
||||
}
|
76
examples/tinkerboard_mfcrc522gpio.go
Normal file
76
examples/tinkerboard_mfcrc522gpio.go
Normal 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()
|
||||
}
|
66
examples/tinkerboard_mfcrc522spi.go
Normal file
66
examples/tinkerboard_mfcrc522spi.go
Normal 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()
|
||||
}
|
@ -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")
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
7
platforms/adaptors/options.go
Normal file
7
platforms/adaptors/options.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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++ {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
@ -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
43
system/spi_access_test.go
Normal 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
165
system/spi_gpio.go
Normal 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))
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
65
system/system_options.go
Normal 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")
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user