1
0
mirror of https://github.com/kidoman/embd synced 2024-12-22 12:50:19 +01:00

Merge pull request #68 from tve/master

Add support for CHIP; merge piled-up PRs
This commit is contained in:
Thorsten von Eicken 2016-09-08 21:01:39 -07:00 committed by GitHub
commit eac7d5af1b
19 changed files with 545 additions and 20 deletions

View File

@ -2,9 +2,18 @@
**embd** is a hardware abstraction layer (HAL) for embedded systems. **embd** is a hardware abstraction layer (HAL) for embedded systems.
It allows you to start your hardware hack on easily available hobby boards (like the Raspberry Pi, BeagleBone Black, etc.) by giving you staight forward access to the board's capabilities as well as a plethora of **sensors** (like accelerometers, gyroscopes, thermometers, etc.) and **controllers** (PWM generators, digital-to-analog convertors) for which we have written drivers. And when things get serious, you dont have to throw away the code. You carry forward the effort onto more custom designed boards where the HAL abstraction of EMBD will save you precious time. It allows you to start your hardware hack on easily available hobby boards
(like the Raspberry Pi, BeagleBone Black, C.H.I.P., etc.) by giving you
straight-forward access to the board's capabilities as well as a plethora of
**sensors** (like accelerometers, gyroscopes, thermometers, etc.) and
**controllers** (PWM generators, digital-to-analog convertors) for
which we have written drivers. And when things get serious, you dont
have to throw away the code. You carry forward the effort onto more
custom designed boards where the HAL abstraction of EMBD will save you
precious time.
Development supported and sponsored by [**SoStronk**](https://www.sostronk.com) and [**ThoughtWorks**](http://www.thoughtworks.com/) Development supported and sponsored by [**SoStronk**](https://www.sostronk.com) and
[**ThoughtWorks**](http://www.thoughtworks.com/).
Also, you might be interested in: [Why Golang?](https://github.com/kidoman/embd/wiki/Why-Go) Also, you might be interested in: [Why Golang?](https://github.com/kidoman/embd/wiki/Why-Go)
@ -12,7 +21,8 @@ Also, you might be interested in: [Why Golang?](https://github.com/kidoman/embd/
## Getting Started ## Getting Started
After installing Go* and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first .go file. We'll call it ```simpleblinker.go```. After installing Go* and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH),
create your first .go file. We'll call it ```simpleblinker.go```.
```go ```go
package main package main
@ -32,7 +42,7 @@ func main() {
} }
``` ```
Then install the EMBD package (go1.2 and greater is required): Then install the EMBD package (go1.6 or greater is required):
$ go get github.com/kidoman/embd $ go get github.com/kidoman/embd
@ -54,11 +64,11 @@ Then run the program with ```sudo```*:
**<nowiki>*</nowiki> Notes** **<nowiki>*</nowiki> Notes**
* Please install the cross compilers. Mac users: ```brew install go --cross-compile-common```. [goxc](https://github.com/laher/goxc) can be a big help as well
* We are instructing the ```go``` compiler to create a binary which will run on the RaspberryPi processor * We are instructing the ```go``` compiler to create a binary which will run on the RaspberryPi processor
* Assuming your RaspberryPi has an IP address of ```192.168.2.2```. Substitute as necessary * Assuming your RaspberryPi has an IP address of ```192.168.2.2```. Substitute as necessary
* ```sudo``` (root) permission is required as we are controlling the hardware by writing to special files * ```sudo``` (root) permission is required as we are controlling the hardware by writing to special files
* This sample program is optimized for brevity and does not clean up after itself. Click here to see the [full version](https://github.com/kidoman/embd/blob/master/samples/fullblinker.go) * This sample program is optimized for brevity and does not clean up after itself. Click here to
see the [full version](https://github.com/kidoman/embd/blob/master/samples/fullblinker.go)
## Getting Help ## Getting Help
@ -68,6 +78,7 @@ Join the [mailing list](https://groups.google.com/forum/#!forum/go-embd)
* [RaspberryPi](http://www.raspberrypi.org/) (including [A+](http://www.raspberrypi.org/products/model-a-plus/) and [B+](http://www.raspberrypi.org/products/model-b-plus/)) * [RaspberryPi](http://www.raspberrypi.org/) (including [A+](http://www.raspberrypi.org/products/model-a-plus/) and [B+](http://www.raspberrypi.org/products/model-b-plus/))
* [RaspberryPi 2](http://www.raspberrypi.org/) * [RaspberryPi 2](http://www.raspberrypi.org/)
* [NextThing C.H.I.P](https://www.nextthing.co/pages/chip)
* [BeagleBone Black](http://beagleboard.org/Products/BeagleBone%20Black) * [BeagleBone Black](http://beagleboard.org/Products/BeagleBone%20Black)
* [Intel Edison](http://www.intel.com/content/www/us/en/do-it-yourself/galileo-maker-quark-board.html) **coming soon** * [Intel Edison](http://www.intel.com/content/www/us/en/do-it-yourself/galileo-maker-quark-board.html) **coming soon**
* [Radxa](http://radxa.com/) **coming soon** * [Radxa](http://radxa.com/) **coming soon**

2
controller/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package controller is a container for the various device controllers supported by EMBD.
package controller

View File

@ -131,6 +131,7 @@ type mockI2CBus struct {
closed bool closed bool
} }
func (bus *mockI2CBus) ReadBytes(addr byte, num int) ([]byte, error) { return []byte{0x00}, nil }
func (bus *mockI2CBus) ReadByte(addr byte) (byte, error) { return 0x00, nil } func (bus *mockI2CBus) ReadByte(addr byte) (byte, error) { return 0x00, nil }
func (bus *mockI2CBus) WriteBytes(addr byte, value []byte) error { return nil } func (bus *mockI2CBus) WriteBytes(addr byte, value []byte) error { return nil }
func (bus *mockI2CBus) ReadFromReg(addr, reg byte, value []byte) error { return nil } func (bus *mockI2CBus) ReadFromReg(addr, reg byte, value []byte) error { return nil }

View File

@ -31,6 +31,9 @@ const (
// HostRadxa represents the Radxa board. // HostRadxa represents the Radxa board.
HostRadxa = "Radxa" HostRadxa = "Radxa"
// HostCHIP represents the NextThing C.H.I.P.
HostCHIP = "CHIP"
) )
func execOutput(name string, arg ...string) (output string, err error) { func execOutput(name string, arg ...string) (output string, err error) {
@ -92,7 +95,7 @@ func cpuInfo() (model, hardware string, revision int, err error) {
} }
revision = int(rev) revision = int(rev)
case strings.HasPrefix(fields[0], "Hardware"): case strings.HasPrefix(fields[0], "Hardware"):
hardware = fields[1] hardware = strings.TrimSpace(fields[1])
case strings.HasPrefix(fields[0], "model name"): case strings.HasPrefix(fields[0], "model name"):
model = fields[1] model = fields[1]
} }
@ -108,7 +111,9 @@ func DetectHost() (host Host, rev int, err error) {
} }
if major < 3 || (major == 3 && minor < 8) { if major < 3 || (major == 3 && minor < 8) {
return HostNull, 0, fmt.Errorf("embd: linux kernel versions lower than 3.8 are not supported. you have %v.%v.%v", major, minor, patch) return HostNull, 0, fmt.Errorf(
"embd: linux kernel versions lower than 3.8 are not supported, "+
"you have %v.%v.%v", major, minor, patch)
} }
model, hardware, rev, err := cpuInfo() model, hardware, rev, err := cpuInfo()
@ -121,6 +126,13 @@ func DetectHost() (host Host, rev int, err error) {
return HostBBB, rev, nil return HostBBB, rev, nil
case strings.Contains(hardware, "BCM2708") || strings.Contains(hardware, "BCM2709"): case strings.Contains(hardware, "BCM2708") || strings.Contains(hardware, "BCM2709"):
return HostRPi, rev, nil return HostRPi, rev, nil
case hardware == "Allwinner sun4i/sun5i Families":
if major < 4 || (major == 4 && minor < 4) {
return HostNull, 0, fmt.Errorf(
"embd: linux kernel version 4.4+ required, you have %v.%v",
major, minor)
}
return HostCHIP, rev, nil
default: default:
return HostNull, 0, fmt.Errorf(`embd: your host "%v:%v" is not supported at this moment. request support at https://github.com/kidoman/embd/issues`, host, model) return HostNull, 0, fmt.Errorf(`embd: your host "%v:%v" is not supported at this moment. request support at https://github.com/kidoman/embd/issues`, host, model)
} }

View File

@ -95,7 +95,7 @@ var ledMap = embd.LEDMap{
"beaglebone:green:usr3": []string{"3", "USR3", "usr3"}, "beaglebone:green:usr3": []string{"3", "USR3", "usr3"},
} }
var spiDeviceMinor byte = 1 var spiDeviceMinor int = 1
func ensureFeatureEnabled(id string) error { func ensureFeatureEnabled(id string) error {
glog.V(3).Infof("bbb: enabling feature %v", id) glog.V(3).Infof("bbb: enabling feature %v", id)

34
host/chip/README.md Normal file
View File

@ -0,0 +1,34 @@
# Using embd on CHIP
The CHIP drivers support gpio, I2C, SPI, and pin interrupts. Not supported are PWM or LED.
The names of the pins on chip have multiple aliases. The official CHIP pin names are supported,
for example XIO-P1 or LCD-D2 and the pin number are also supported, such as U14-14 (same as XIO-P1)
or U13-17. Some of the alternate function names are also supported, like "SPI2_MOSI", and the
linux 4.4 kernel gpio pin numbers as well, e.g., 1017 for XIO-P1. Finally, the official GPIO pins
(XIO-P0 thru XIO-P7) can be addressed as gpio0-gpio7.
A simple demo to blink an LED connected with a small resistor between XIO-P6 and 3.3V is
```
package main
import (
"time"
"github.com/kidoman/embd"
_ "github.com/kidoman/embd/host/chip"
)
func main() {
embd.InitGPIO()
defer embd.CloseGPIO()
embd.SetDirection("gpio6", embd.Out)
on := 0
for {
embd.DigitalWrite("gpio6", on)
on = 1 - on
time.Sleep(250 * time.Millisecond)
}
}
```
Run it as root: `sudo ./blinky`

99
host/chip/chip.go Normal file
View File

@ -0,0 +1,99 @@
// Copyright 2016 by Thorsten von Eicken, see LICENSE file
// Package chip provides NextThing C.H.I.P. support.
// References:
// http://docs.getchip.com/chip.html#chip-hardware
// http://www.chip-community.org/index.php/Hardware_Information
//
// The following features are supported on Linux kernel 4.4+
// GPIO (digital (rw))
// I²C
// SPI
// Could add LED support by following https://bbs.nextthing.co/t/pwr-and-stat-leds/748/5
package chip
import (
"github.com/kidoman/embd"
"github.com/kidoman/embd/host/generic"
)
var spiDeviceMinor = 32766
var chipPins = embd.PinMap{
// official GPIO pins (U14 connector) using the pcf8574a
&embd.PinDesc{"XIO-P0", []string{"1016", "0", "U14-13", "gpio0"}, embd.CapDigital, 1016, 0},
&embd.PinDesc{"XIO-P1", []string{"1017", "1", "U14-14", "gpio1"}, embd.CapDigital, 1017, 0},
&embd.PinDesc{"XIO-P2", []string{"1018", "2", "U14-15", "gpio2"}, embd.CapDigital, 1018, 0},
&embd.PinDesc{"XIO-P3", []string{"1019", "3", "U14-16", "gpio3"}, embd.CapDigital, 1019, 0},
&embd.PinDesc{"XIO-P4", []string{"1020", "4", "U14-17", "gpio4"}, embd.CapDigital, 1020, 0},
&embd.PinDesc{"XIO-P5", []string{"1021", "5", "U14-18", "gpio5"}, embd.CapDigital, 1021, 0},
&embd.PinDesc{"XIO-P6", []string{"1022", "6", "U14-19", "gpio6"}, embd.CapDigital, 1022, 0},
&embd.PinDesc{"XIO-P7", []string{"1023", "7", "U14-20", "gpio7"}, embd.CapDigital, 1023, 0},
// pins usable on the U13 connector
&embd.PinDesc{"TWI1-SDA", []string{"48", "U13-9", "I2C0_SDA"}, embd.CapDigital | embd.CapI2C, 48, 0},
&embd.PinDesc{"TWI1-SCK", []string{"47", "U13-11", "I2C0_SCK"}, embd.CapDigital | embd.CapI2C, 47, 0},
&embd.PinDesc{"PWM0", []string{"34", "U13-18"}, embd.CapDigital | embd.CapPWM, 34, 0},
&embd.PinDesc{"LCD-D2", []string{"98", "U13-17"}, embd.CapDigital, 98, 0},
&embd.PinDesc{"LCD-D3", []string{"99", "U13-20"}, embd.CapDigital, 99, 0},
&embd.PinDesc{"LCD-D4", []string{"100", "U13-19"}, embd.CapDigital, 100, 0},
&embd.PinDesc{"LCD-D5", []string{"101", "U13-22"}, embd.CapDigital, 101, 0},
&embd.PinDesc{"LCD-D6", []string{"102", "U13-21"}, embd.CapDigital, 102, 0},
&embd.PinDesc{"LCD-D7", []string{"103", "U13-24"}, embd.CapDigital, 103, 0},
&embd.PinDesc{"LCD-D10", []string{"106", "U13-23"}, embd.CapDigital, 106, 0},
&embd.PinDesc{"LCD-D11", []string{"107", "U13-26"}, embd.CapDigital, 107, 0},
&embd.PinDesc{"LCD-D12", []string{"108", "U13-25"}, embd.CapDigital, 108, 0},
&embd.PinDesc{"LCD-D13", []string{"109", "U13-28"}, embd.CapDigital, 109, 0},
&embd.PinDesc{"LCD-D14", []string{"110", "U13-27"}, embd.CapDigital, 110, 0},
&embd.PinDesc{"LCD-D15", []string{"111", "U13-30"}, embd.CapDigital, 111, 0},
&embd.PinDesc{"LCD-D18", []string{"114", "U13-29"}, embd.CapDigital, 114, 0},
&embd.PinDesc{"LCD-D19", []string{"115", "U13-32"}, embd.CapDigital, 115, 0},
&embd.PinDesc{"LCD-D20", []string{"116", "U13-31"}, embd.CapDigital, 116, 0},
&embd.PinDesc{"LCD-D21", []string{"117", "U13-34"}, embd.CapDigital, 117, 0},
&embd.PinDesc{"LCD-D22", []string{"118", "U13-33"}, embd.CapDigital, 118, 0},
&embd.PinDesc{"LCD-D23", []string{"119", "U13-36"}, embd.CapDigital, 119, 0},
&embd.PinDesc{"LCD-CLK", []string{"120", "U13-35"}, embd.CapDigital, 120, 0},
&embd.PinDesc{"LCD-VSYNC", []string{"123", "U13-37"}, embd.CapDigital, 123, 0},
&embd.PinDesc{"LCD-HSYNC", []string{"122", "U13-38"}, embd.CapDigital, 122, 0},
&embd.PinDesc{"LCD-DE", []string{"121", "U13-40"}, embd.CapDigital, 121, 0},
// pins usable on the U14 connector
&embd.PinDesc{"UART1-TX", []string{"195", "U14-3", "EINT3"}, embd.CapDigital | embd.CapUART, 195, 0},
&embd.PinDesc{"UART1-RX", []string{"196", "U14-5", "EINT4"}, embd.CapDigital | embd.CapUART, 196, 0},
&embd.PinDesc{"AP-EINT1", []string{"193", "U14-23", "EINT1"}, embd.CapDigital, 193, 0},
&embd.PinDesc{"AP-EINT3", []string{"35", "U14-24", "EINT3"}, embd.CapDigital, 35, 0},
&embd.PinDesc{"TWI2-SDA", []string{"50", "U14-25", "I2C2_SDA"}, embd.CapDigital | embd.CapI2C, 50, 0},
&embd.PinDesc{"TWI2-SCK", []string{"49", "U14-26", "I2C2_SCK"}, embd.CapDigital | embd.CapI2C, 49, 0},
&embd.PinDesc{"CSIPCK", []string{"128", "U14-27", "SPI2_SCO", "SPI2_CS0"}, embd.CapDigital | embd.CapSPI, 128, 0},
&embd.PinDesc{"CSICK", []string{"129", "U14-28", "SPI2_CLK"}, embd.CapDigital | embd.CapSPI, 129, 0},
&embd.PinDesc{"CSIHSYNC", []string{"130", "U14-29", "SPI2_MOSI"}, embd.CapDigital | embd.CapSPI, 130, 0},
&embd.PinDesc{"CSIVSYNC", []string{"131", "U14-30", "SPI2_MISO"}, embd.CapDigital | embd.CapSPI, 131, 0},
&embd.PinDesc{"CSID0", []string{"132", "U14-31"}, embd.CapDigital, 132, 0},
&embd.PinDesc{"CSID1", []string{"133", "U14-32"}, embd.CapDigital, 133, 0},
&embd.PinDesc{"CSID2", []string{"134", "U14-33"}, embd.CapDigital, 134, 0},
&embd.PinDesc{"CSID3", []string{"135", "U14-34"}, embd.CapDigital, 135, 0},
&embd.PinDesc{"CSID4", []string{"136", "U14-35"}, embd.CapDigital, 136, 0},
&embd.PinDesc{"CSID5", []string{"137", "U14-36"}, embd.CapDigital, 137, 0},
&embd.PinDesc{"CSID6", []string{"138", "U14-37", "UART1_TX"}, embd.CapDigital | embd.CapUART, 138, 0},
&embd.PinDesc{"CSID7", []string{"139", "U14-38", "UART1_RX"}, embd.CapDigital | embd.CapUART, 139, 0},
}
func init() {
embd.Register(embd.HostCHIP, func(rev int) *embd.Descriptor {
return &embd.Descriptor{
GPIODriver: func() embd.GPIODriver {
return embd.NewGPIODriver(chipPins, generic.NewDigitalPin, nil, nil)
},
I2CDriver: func() embd.I2CDriver {
return embd.NewI2CDriver(generic.NewI2CBus)
},
//LEDDriver: func() embd.LEDDriver {
// return embd.NewLEDDriver(ledMap, generic.NewLED)
//},
SPIDriver: func() embd.SPIDriver {
return embd.NewSPIDriver(spiDeviceMinor, generic.NewSPIBus, nil)
},
}
})
}

View File

@ -10,6 +10,7 @@ import (
"os" "os"
"path" "path"
"strconv" "strconv"
"syscall"
"time" "time"
"github.com/kidoman/embd" "github.com/kidoman/embd"
@ -69,6 +70,9 @@ func (p *digitalPin) export() error {
} }
defer exporter.Close() defer exporter.Close()
_, err = exporter.WriteString(strconv.Itoa(p.n)) _, err = exporter.WriteString(strconv.Itoa(p.n))
if e, ok := err.(*os.PathError); ok && e.Err == syscall.EBUSY {
return nil // EBUSY -> the pin has already been exported
}
return err return err
} }

View File

@ -101,6 +101,28 @@ func (b *i2cBus) ReadByte(addr byte) (byte, error) {
return bytes[0], nil return bytes[0], nil
} }
func (b *i2cBus) ReadBytes(addr byte, num int) ([]byte, error) {
b.mu.Lock()
defer b.mu.Unlock()
if err := b.init(); err != nil {
return []byte{0}, err
}
if err := b.setAddress(addr); err != nil {
return []byte{0}, err
}
bytes := make([]byte, num)
n, _ := b.file.Read(bytes)
if n != num {
return []byte{0}, fmt.Errorf("i2c: Unexpected number (%v) of bytes read", n)
}
return bytes, nil
}
func (b *i2cBus) WriteByte(addr, value byte) error { func (b *i2cBus) WriteByte(addr, value byte) error {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
@ -223,7 +245,7 @@ func (b *i2cBus) WriteToReg(addr, reg byte, value []byte) error {
message.addr = uint16(addr) message.addr = uint16(addr)
message.flags = 0 message.flags = 0
message.len = uint16(len(outbuf)) message.len = uint16(len(outbuf))
message.buf = uintptr(unsafe.Pointer(&hdrp.Data)) message.buf = uintptr(unsafe.Pointer(hdrp.Data))
var packets i2c_rdwr_ioctl_data var packets i2c_rdwr_ioctl_data

View File

@ -61,11 +61,13 @@ func initEpollListener() *epollListener {
if err != nil { if err != nil {
panic(fmt.Sprintf("EpollWait error: %v", err)) panic(fmt.Sprintf("EpollWait error: %v", err))
} }
listener.mu.Lock()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if irq, ok := listener.interruptablePins[int(epollEvents[i].Fd)]; ok { if irq, ok := listener.interruptablePins[int(epollEvents[i].Fd)]; ok {
irq.Signal() irq.Signal()
} }
} }
listener.mu.Unlock()
} }
}() }()
return listener return listener

View File

@ -36,12 +36,14 @@ type spiIOCTransfer struct {
speedHz uint32 speedHz uint32
delayus uint16 delayus uint16
bitsPerWord uint8 bitsPerWord uint8
csChange uint8
pad uint32
} }
type spiBus struct { type spiBus struct {
file *os.File file *os.File
spiDevMinor byte spiDevMinor int
channel byte channel byte
mode byte mode byte
@ -61,7 +63,7 @@ func spiIOCMessageN(n uint32) uint32 {
return (spiIOCMessage0 + (n * spiIOCIncrementor)) return (spiIOCMessage0 + (n * spiIOCIncrementor))
} }
func NewSPIBus(spiDevMinor, mode, channel byte, speed, bpw, delay int, i func() error) embd.SPIBus { func NewSPIBus(spiDevMinor int, mode, channel byte, speed, bpw, delay int, i func() error) embd.SPIBus {
return &spiBus{ return &spiBus{
spiDevMinor: spiDevMinor, spiDevMinor: spiDevMinor,
mode: mode, mode: mode,

View File

@ -13,7 +13,7 @@ import (
"github.com/kidoman/embd/host/generic" "github.com/kidoman/embd/host/generic"
) )
var spiDeviceMinor = byte(0) var spiDeviceMinor = 0
var rev1Pins = embd.PinMap{ var rev1Pins = embd.PinMap{
&embd.PinDesc{ID: "P1_3", Aliases: []string{"0", "GPIO_0", "SDA", "I2C0_SDA"}, Caps: embd.CapDigital | embd.CapI2C, DigitalLogical: 0}, &embd.PinDesc{ID: "P1_3", Aliases: []string{"0", "GPIO_0", "SDA", "I2C0_SDA"}, Caps: embd.CapDigital | embd.CapI2C, DigitalLogical: 0},

2
i2c.go
View File

@ -6,6 +6,8 @@ package embd
type I2CBus interface { type I2CBus interface {
// ReadByte reads a byte from the given address. // ReadByte reads a byte from the given address.
ReadByte(addr byte) (value byte, err error) ReadByte(addr byte) (value byte, err error)
// ReadBytes reads a slice of bytes from the given address.
ReadBytes(addr byte, num int) (value []byte, err error)
// WriteByte writes a byte to the given address. // WriteByte writes a byte to the given address.
WriteByte(addr, value byte) error WriteByte(addr, value byte) error
// WriteBytes writes a slice bytes to the given address. // WriteBytes writes a slice bytes to the given address.

1
radio/doc.go Normal file
View File

@ -0,0 +1 @@
package radio

8
radio/rfm69/README.md Normal file
View File

@ -0,0 +1,8 @@
RFM69 Driver Info
=================
Interesting links
-----------------
- Info about listen mode with low duty cycle: https://lowpowerlab.com/forum/low-power-techniques/ultra-low-power-listening-mode-for-battery-nodes/

68
radio/rfm69/registers.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2016 by Thorsten von Eicken, see LICENSE file
package rfm69
const (
REG_FIFO = 0x00
REG_OPMODE = 0x01
REG_FRFMSB = 0x07
REG_PALEVEL = 0x11
REG_LNAVALUE = 0x18
REG_AFCMSB = 0x1F
REG_AFCLSB = 0x20
REG_FEIMSB = 0x21
REG_FEILSB = 0x22
REG_RSSIVALUE = 0x24
REG_IRQFLAGS1 = 0x27
REG_IRQFLAGS2 = 0x28
REG_SYNCVALUE1 = 0x2F
REG_SYNCVALUE2 = 0x30
REG_NODEADDR = 0x39
REG_BCASTADDR = 0x3A
REG_FIFOTHRESH = 0x3C
REG_PKTCONFIG2 = 0x3D
REG_AESKEYMSB = 0x3E
MODE_SLEEP = 0 << 2
MODE_STANDBY = 1 << 2
MODE_TRANSMIT = 3 << 2
MODE_RECEIVE = 4 << 2
START_TX = 0xC2
STOP_TX = 0x42
RCCALSTART = 0x80
IRQ1_MODEREADY = 1 << 7
IRQ1_RXREADY = 1 << 6
IRQ1_SYNADDRMATCH = 1 << 0
IRQ2_FIFONOTEMPTY = 1 << 6
IRQ2_PACKETSENT = 1 << 3
IRQ2_PAYLOADREADY = 1 << 2
)
// register values to initialize the chip, this array has pairs of <address, data>
var configRegs = []byte{
// POR value is better for first rf_sleep 0x01, 0x00, // OpMode = sleep
0x02, 0x00, // DataModul = packet mode, fsk
0x03, 0x02, // BitRateMsb, data rate = 49,261 khz
0x04, 0x8A, // BitRateLsb, divider = 32 MHz / 650
0x05, 0x02, // FdevMsb = 45 KHz
0x06, 0xE1, // FdevLsb = 45 KHz
0x0B, 0x20, // Low M
0x19, 0x4A, // RxBw 100 KHz
0x1A, 0x42, // AfcBw 125 KHz
0x1E, 0x0C, // AfcAutoclearOn, AfcAutoOn
//0x25, 0x40, //0x80, // DioMapping1 = SyncAddress (Rx)
0x26, 0x07, // disable clkout
0x29, 0xA0, // RssiThresh -80 dB
0x2D, 0x05, // PreambleSize = 5
0x2E, 0x88, // SyncConfig = sync on, sync size = 2
0x2F, 0x2D, // SyncValue1 = 0x2D
0x37, 0xD0, // PacketConfig1 = fixed, white, no filtering
0x38, 0x42, // PayloadLength = 0, unlimited
0x3C, 0x8F, // FifoTresh, not empty, level 15
0x3D, 0x12, // 0x10, // PacketConfig2, interpkt = 1, autorxrestart off
0x6F, 0x20, // TestDagc ...
0x71, 0x02, // RegTestAfc
}

257
radio/rfm69/rfm69.go Normal file
View File

@ -0,0 +1,257 @@
// Copyright 2016 by Thorsten von Eicken, see LICENSE file
// The RFM69 package interfaces with a HopeRF RFM69 radio connected to an SPI bus. In addition,
// an interrupt capable GPIO pin may be used to avoid having to poll the radio.
package rfm69
import (
"fmt"
"log"
"github.com/kidoman/embd"
)
// rfm69 represents a HopeRF RFM69 radio
type rfm69 struct {
// configuration
spi embd.SPIBus // bus where the radio is connected
intrPin embd.InterruptPin // interrupt pin for RX and TX interrupts
id byte // my RF ID/address
group byte // RF address of group
freq int // center frequency
parity byte // ???
// state
mode byte // current operation mode
// info about current RX packet
rxInfo *RxInfo
}
type Packet struct {
Length uint8 // number of message bytes plus 1 for the address byte
Address uint8 // destination address
Message []byte
}
type RxInfo struct {
rssi int // rssi value for current packet
lna int // low noise amp gain for current packet
fei int // frequency error for current packet
afc int // frequency correction applied for current packet
}
// New creates a connection to an rfm69 radio connected to the provided SPI bus and interrupt pin.
// the bufCount determines how many transmit buffers are allocated to allow for the queueing of
// transmit packets.
// For the RFM69 the SPI bus must be set to 10Mhz and mode 0.
func New(bus embd.SPIBus, intr embd.InterruptPin, id, group byte, freq int) *rfm69 {
// bit 7 = b7^b5^b3^b1; bit 6 = b6^b4^b2^b0
parity := group ^ (group << 4)
parity = (parity ^ (parity << 2)) & 0xc0
return &rfm69{spi: bus, intrPin: intr, id: id, group: group, freq: freq, parity: parity,
mode: 255}
}
func (rf *rfm69) writeReg(addr, data byte) error {
buf := []byte{addr | 0x80, data}
return rf.spi.TransferAndReceiveData(buf)
}
func (rf *rfm69) readReg(addr byte) (byte, error) {
buf := []byte{addr & 0x7f, 0}
err := rf.spi.TransferAndReceiveData(buf)
return buf[1], err
}
func (rf *rfm69) Init() error {
// try to establish communication with the rfm69
sync := func(pattern byte) error {
n := 10
for {
rf.writeReg(REG_SYNCVALUE1, pattern)
v, err := rf.readReg(REG_SYNCVALUE1)
if err != nil {
return err
}
if v == pattern {
return nil
}
if n == 0 {
return fmt.Errorf("Cannot sync with rfm69 chip")
}
n--
}
}
if err := sync(0xaa); err != nil {
return err
}
if err := sync(0x55); err != nil {
return err
}
// write the configuration into the registers
for i := 0; i < len(configRegs)-1; i += 2 {
if err := rf.writeReg(configRegs[i], configRegs[i+1]); err != nil {
return err
}
}
rf.setFrequency(rf.freq)
rf.writeReg(REG_SYNCVALUE2, rf.group)
if gpio, ok := rf.intrPin.(embd.DigitalPin); ok {
log.Printf("Set intr direction")
gpio.SetDirection(embd.In)
}
if err := rf.intrPin.Watch(embd.EdgeRising, rf.intrHandler); err != nil {
return err
}
return nil
}
func (rf *rfm69) setFrequency(freq int) {
// accept any frequency scale as input, including KHz and MHz
// multiply by 10 until freq >= 100 MHz
for freq > 0 && freq < 100000000 {
freq = freq * 10
}
// Frequency steps are in units of (32,000,000 >> 19) = 61.03515625 Hz
// use multiples of 64 to avoid multi-precision arithmetic, i.e. 3906.25 Hz
// due to this, the lower 6 bits of the calculated factor will always be 0
// this is still 4 ppm, i.e. well below the radio's 32 MHz crystal accuracy
// 868.0 MHz = 0xD90000, 868.3 MHz = 0xD91300, 915.0 MHz = 0xE4C000
frf := (freq << 2) / (32000000 >> 11)
rf.writeReg(REG_FRFMSB, byte(frf>>10))
rf.writeReg(REG_FRFMSB+1, byte(frf>>2))
rf.writeReg(REG_FRFMSB+2, byte(frf<<6))
}
func (rf *rfm69) setMode(mode byte) error {
reg, err := rf.readReg(REG_OPMODE)
if err != nil {
return err
}
reg = (reg & 0xE3) | mode
err = rf.writeReg(REG_OPMODE, reg)
if err != nil {
return err
}
for {
val, err := rf.readReg(REG_IRQFLAGS1)
if err != nil {
rf.mode = 255
return err
}
if val&IRQ1_MODEREADY != 0 {
rf.mode = mode
return nil
}
}
}
func (rf *rfm69) Send(header byte, message []byte) error {
if len(message) > 62 {
return fmt.Errorf("message too long")
}
rf.setMode(MODE_SLEEP)
buf := make([]byte, len(message)+4)
buf[0] = REG_FIFO | 0x80
buf[1] = byte(len(message) + 2)
buf[2] = (header & 0x3f) | rf.parity
buf[3] = (header & 0xC0) | rf.id
copy(buf[4:], message)
err := rf.spi.TransferAndReceiveData(buf)
if err != nil {
return err
}
rf.setMode(MODE_TRANSMIT)
for {
val, err := rf.readReg(REG_IRQFLAGS2)
if err != nil {
return err
}
if val&IRQ2_PACKETSENT != 0 {
break
}
}
rf.setMode(MODE_STANDBY)
return nil
}
func (rf *rfm69) readInfo() *RxInfo {
// collect rxinfo, start with rssi
rxInfo := &RxInfo{}
rssi, _ := rf.readReg(REG_RSSIVALUE)
rxInfo.rssi = 0 - int(rssi)/2
// low noise amp gain
lna, _ := rf.readReg(REG_LNAVALUE)
rxInfo.lna = int((lna >> 3) & 0x7)
// auto freq correction applied, caution: signed value
buf := []byte{REG_AFCMSB, 0, 0}
rf.spi.TransferAndReceiveData(buf)
rxInfo.afc = int(int8(buf[1]))<<8 | int(buf[2])
// freq error detected, caution: signed value
buf = []byte{REG_FEIMSB, 0, 0}
rf.spi.TransferAndReceiveData(buf)
rxInfo.fei = int(int8(buf[1]))<<8 | int(buf[2])
return rxInfo
}
func (rf *rfm69) Receive() (header byte, message []byte, info *RxInfo, err error) {
// if we're not in receive mode, then switch, this also flushes the FIFO
if rf.mode != MODE_RECEIVE {
rf.setMode(MODE_RECEIVE)
return
}
// if we don't have rxinfo check whether we have RX_READY, which means that we've
// started receiving a packet so we can collect info
if rf.rxInfo == nil {
irq1, err := rf.readReg(REG_IRQFLAGS1)
if err != nil {
return 0, nil, nil, err
}
if irq1&IRQ1_RXREADY != 0 {
rf.rxInfo = rf.readInfo()
}
}
// see whether we have a full packet
irq2, err := rf.readReg(REG_IRQFLAGS2)
if err != nil {
return 0, nil, nil, err
}
if irq2&IRQ2_PAYLOADREADY == 0 {
return
}
i2 := rf.readInfo()
if rf.rxInfo != nil && i2 != nil &&
(rf.rxInfo.rssi != i2.rssi || rf.rxInfo.lna != i2.lna ||
rf.rxInfo.afc != i2.afc || rf.rxInfo.fei != i2.fei) {
fmt.Printf("\nrxInfo mismatch: %+v vs %+v\n", *rf.rxInfo, *i2)
}
// got packet, read it by fetching the entire FIFO, should be faster than first
// looking at the length
buf := make([]byte, 67)
buf[0] = REG_FIFO
err = rf.spi.TransferAndReceiveData(buf)
if err != nil {
return 0, nil, nil, err
}
// return the packet
info = rf.rxInfo
rf.rxInfo = nil
l := buf[1]
if l > 66 {
l = 66 // or error?
}
header = buf[2]
message = buf[3 : 2+l]
return
}
func (rf *rfm69) intrHandler(pin embd.DigitalPin) {
log.Printf("Interrupt called!")
}

View File

@ -24,25 +24,25 @@ func main() {
panic(err) panic(err)
} }
fmt.Println("received data is: %v", dataBuf) fmt.Println("received data is:", dataBuf)
dataReceived, err := spiBus.ReceiveData(3) dataReceived, err := spiBus.ReceiveData(3)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println("received data is: %v", dataReceived) fmt.Println("received data is:", dataReceived)
dataByte := byte(1) dataByte := byte(1)
receivedByte, err := spiBus.TransferAndReceiveByte(dataByte) receivedByte, err := spiBus.TransferAndReceiveByte(dataByte)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println("received byte is: %v", receivedByte) fmt.Println("received byte is:", receivedByte)
receivedByte, err = spiBus.ReceiveByte() receivedByte, err = spiBus.ReceiveByte()
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println("received byte is: %v", receivedByte) fmt.Println("received byte is:", receivedByte)
} }

View File

@ -2,10 +2,10 @@ package embd
import "sync" import "sync"
type spiBusFactory func(byte, byte, byte, int, int, int, func() error) SPIBus type spiBusFactory func(int, byte, byte, int, int, int, func() error) SPIBus
type spiDriver struct { type spiDriver struct {
spiDevMinor byte spiDevMinor int
initializer func() error initializer func() error
busMap map[byte]SPIBus busMap map[byte]SPIBus
@ -16,7 +16,7 @@ type spiDriver struct {
// NewSPIDriver returns a SPIDriver interface which allows control // NewSPIDriver returns a SPIDriver interface which allows control
// over the SPI bus. // over the SPI bus.
func NewSPIDriver(spiDevMinor byte, sbf spiBusFactory, i func() error) SPIDriver { func NewSPIDriver(spiDevMinor int, sbf spiBusFactory, i func() error) SPIDriver {
return &spiDriver{ return &spiDriver{
spiDevMinor: spiDevMinor, spiDevMinor: spiDevMinor,
sbf: sbf, sbf: sbf,