diff --git a/README.md b/README.md index f37b5b5..932be11 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,18 @@ **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) @@ -12,7 +21,8 @@ Also, you might be interested in: [Why Golang?](https://github.com/kidoman/embd/ ## 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 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 @@ -54,11 +64,11 @@ Then run the program with ```sudo```*: *** 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 * 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 -* 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 @@ -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 2](http://www.raspberrypi.org/) +* [NextThing C.H.I.P](https://www.nextthing.co/pages/chip) * [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** * [Radxa](http://radxa.com/) **coming soon** diff --git a/controller/doc.go b/controller/doc.go new file mode 100644 index 0000000..01256a5 --- /dev/null +++ b/controller/doc.go @@ -0,0 +1,2 @@ +// Package controller is a container for the various device controllers supported by EMBD. +package controller diff --git a/controller/hd44780/hd44780_test.go b/controller/hd44780/hd44780_test.go index b05dddc..eee375d 100644 --- a/controller/hd44780/hd44780_test.go +++ b/controller/hd44780/hd44780_test.go @@ -131,6 +131,7 @@ type mockI2CBus struct { 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) WriteBytes(addr byte, value []byte) error { return nil } func (bus *mockI2CBus) ReadFromReg(addr, reg byte, value []byte) error { return nil } diff --git a/detect.go b/detect.go index a92ff4e..93f4500 100644 --- a/detect.go +++ b/detect.go @@ -31,6 +31,9 @@ const ( // HostRadxa represents the Radxa board. HostRadxa = "Radxa" + + // HostCHIP represents the NextThing C.H.I.P. + HostCHIP = "CHIP" ) 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) case strings.HasPrefix(fields[0], "Hardware"): - hardware = fields[1] + hardware = strings.TrimSpace(fields[1]) case strings.HasPrefix(fields[0], "model name"): model = fields[1] } @@ -108,7 +111,9 @@ func DetectHost() (host Host, rev int, err error) { } 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() @@ -121,6 +126,13 @@ func DetectHost() (host Host, rev int, err error) { return HostBBB, rev, nil case strings.Contains(hardware, "BCM2708") || strings.Contains(hardware, "BCM2709"): 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: 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) } diff --git a/host/bbb/bbb.go b/host/bbb/bbb.go index f909627..1f42d7d 100644 --- a/host/bbb/bbb.go +++ b/host/bbb/bbb.go @@ -95,7 +95,7 @@ var ledMap = embd.LEDMap{ "beaglebone:green:usr3": []string{"3", "USR3", "usr3"}, } -var spiDeviceMinor byte = 1 +var spiDeviceMinor int = 1 func ensureFeatureEnabled(id string) error { glog.V(3).Infof("bbb: enabling feature %v", id) diff --git a/host/chip/README.md b/host/chip/README.md new file mode 100644 index 0000000..51fd49e --- /dev/null +++ b/host/chip/README.md @@ -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` + diff --git a/host/chip/chip.go b/host/chip/chip.go new file mode 100644 index 0000000..d849df4 --- /dev/null +++ b/host/chip/chip.go @@ -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) + }, + } + }) +} diff --git a/host/generic/digitalpin.go b/host/generic/digitalpin.go index 80a81ea..525eeb4 100644 --- a/host/generic/digitalpin.go +++ b/host/generic/digitalpin.go @@ -10,6 +10,7 @@ import ( "os" "path" "strconv" + "syscall" "time" "github.com/kidoman/embd" @@ -69,6 +70,9 @@ func (p *digitalPin) export() error { } defer exporter.Close() _, 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 } diff --git a/host/generic/i2cbus.go b/host/generic/i2cbus.go index dc8e74c..fbe6f55 100644 --- a/host/generic/i2cbus.go +++ b/host/generic/i2cbus.go @@ -101,6 +101,28 @@ func (b *i2cBus) ReadByte(addr byte) (byte, error) { 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 { b.mu.Lock() defer b.mu.Unlock() @@ -223,7 +245,7 @@ func (b *i2cBus) WriteToReg(addr, reg byte, value []byte) error { message.addr = uint16(addr) message.flags = 0 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 diff --git a/host/generic/interrupt.go b/host/generic/interrupt.go index 4052283..7f9b766 100644 --- a/host/generic/interrupt.go +++ b/host/generic/interrupt.go @@ -61,11 +61,13 @@ func initEpollListener() *epollListener { if err != nil { panic(fmt.Sprintf("EpollWait error: %v", err)) } + listener.mu.Lock() for i := 0; i < n; i++ { if irq, ok := listener.interruptablePins[int(epollEvents[i].Fd)]; ok { irq.Signal() } } + listener.mu.Unlock() } }() return listener diff --git a/host/generic/spibus.go b/host/generic/spibus.go index ad3bcec..a33576f 100644 --- a/host/generic/spibus.go +++ b/host/generic/spibus.go @@ -36,12 +36,14 @@ type spiIOCTransfer struct { speedHz uint32 delayus uint16 bitsPerWord uint8 + csChange uint8 + pad uint32 } type spiBus struct { file *os.File - spiDevMinor byte + spiDevMinor int channel byte mode byte @@ -61,7 +63,7 @@ func spiIOCMessageN(n uint32) uint32 { 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{ spiDevMinor: spiDevMinor, mode: mode, diff --git a/host/rpi/rpi.go b/host/rpi/rpi.go index 0a82a4b..9279ca8 100644 --- a/host/rpi/rpi.go +++ b/host/rpi/rpi.go @@ -13,7 +13,7 @@ import ( "github.com/kidoman/embd/host/generic" ) -var spiDeviceMinor = byte(0) +var spiDeviceMinor = 0 var rev1Pins = embd.PinMap{ &embd.PinDesc{ID: "P1_3", Aliases: []string{"0", "GPIO_0", "SDA", "I2C0_SDA"}, Caps: embd.CapDigital | embd.CapI2C, DigitalLogical: 0}, diff --git a/i2c.go b/i2c.go index 8bdb876..6a5d1ec 100644 --- a/i2c.go +++ b/i2c.go @@ -6,6 +6,8 @@ package embd type I2CBus interface { // ReadByte reads a byte from the given address. 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(addr, value byte) error // WriteBytes writes a slice bytes to the given address. diff --git a/radio/doc.go b/radio/doc.go new file mode 100644 index 0000000..3e2bdf6 --- /dev/null +++ b/radio/doc.go @@ -0,0 +1 @@ +package radio diff --git a/radio/rfm69/README.md b/radio/rfm69/README.md new file mode 100644 index 0000000..9dcd402 --- /dev/null +++ b/radio/rfm69/README.md @@ -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/ diff --git a/radio/rfm69/registers.go b/radio/rfm69/registers.go new file mode 100644 index 0000000..94e2a73 --- /dev/null +++ b/radio/rfm69/registers.go @@ -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 +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 +} diff --git a/radio/rfm69/rfm69.go b/radio/rfm69/rfm69.go new file mode 100644 index 0000000..15029b6 --- /dev/null +++ b/radio/rfm69/rfm69.go @@ -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!") +} diff --git a/samples/spi.go b/samples/spi.go index 298acc4..20d606c 100644 --- a/samples/spi.go +++ b/samples/spi.go @@ -24,25 +24,25 @@ func main() { panic(err) } - fmt.Println("received data is: %v", dataBuf) + fmt.Println("received data is:", dataBuf) dataReceived, err := spiBus.ReceiveData(3) if err != nil { panic(err) } - fmt.Println("received data is: %v", dataReceived) + fmt.Println("received data is:", dataReceived) dataByte := byte(1) receivedByte, err := spiBus.TransferAndReceiveByte(dataByte) if err != nil { panic(err) } - fmt.Println("received byte is: %v", receivedByte) + fmt.Println("received byte is:", receivedByte) receivedByte, err = spiBus.ReceiveByte() if err != nil { panic(err) } - fmt.Println("received byte is: %v", receivedByte) + fmt.Println("received byte is:", receivedByte) } diff --git a/spidriver.go b/spidriver.go index cecc19d..a145155 100644 --- a/spidriver.go +++ b/spidriver.go @@ -2,10 +2,10 @@ package embd 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 { - spiDevMinor byte + spiDevMinor int initializer func() error busMap map[byte]SPIBus @@ -16,7 +16,7 @@ type spiDriver struct { // NewSPIDriver returns a SPIDriver interface which allows control // 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{ spiDevMinor: spiDevMinor, sbf: sbf,