diff --git a/convertors/mcp3008/mcp3008.go b/convertors/mcp3008/mcp3008.go new file mode 100644 index 0000000..716d437 --- /dev/null +++ b/convertors/mcp3008/mcp3008.go @@ -0,0 +1,45 @@ +package mcp3008 + +import ( + "github.com/golang/glog" + "github.com/kidoman/embd" +) + +// MCP3008 represents a mcp3008 8bit DAC. +type MCP3008 struct { + Mode byte + + Bus embd.SPIBus +} + +const ( + // SingleMode represents the single-ended mode for the mcp3008. + SingleMode = 1 + + // DifferenceMode represents the diffenrential mode for the mcp3008. + DifferenceMode = 0 +) + +// New creates a representation of the mcp3008 convertor +func New(mode byte, bus embd.SPIBus) *MCP3008 { + return &MCP3008{mode, bus} +} + +const ( + startBit = 1 +) + +// AnalogValueAt returns the analog value at the given channel of the convertor. +func (m *MCP3008) AnalogValueAt(chanNum int) (int, error) { + var data [3]uint8 + data[0] = startBit + data[1] = uint8(m.Mode)<<7 | uint8(chanNum)<<4 + data[2] = 0 + + glog.V(2).Infof("mcp3008: sendingdata buffer %v", data) + if err := m.Bus.TransferAndRecieveData(data[:]); err != nil { + return 0, err + } + + return int(uint16(data[1]&0x03)<<8 | uint16(data[2])), nil +} diff --git a/descriptor.go b/descriptor.go index 78208f3..b891008 100644 --- a/descriptor.go +++ b/descriptor.go @@ -14,6 +14,7 @@ type Descriptor struct { GPIODriver func() GPIODriver I2CDriver func() I2CDriver LEDDriver func() LEDDriver + SPIDriver func() SPIDriver } // The Describer type is a Descriptor provider. diff --git a/host/bbb/bbb.go b/host/bbb/bbb.go index c52f8a8..f909627 100644 --- a/host/bbb/bbb.go +++ b/host/bbb/bbb.go @@ -15,6 +15,7 @@ import ( "os" "strings" + "github.com/golang/glog" "github.com/kidoman/embd" "github.com/kidoman/embd/host/generic" ) @@ -94,7 +95,10 @@ var ledMap = embd.LEDMap{ "beaglebone:green:usr3": []string{"3", "USR3", "usr3"}, } +var spiDeviceMinor byte = 1 + func ensureFeatureEnabled(id string) error { + glog.V(3).Infof("bbb: enabling feature %v", id) pattern := "/sys/devices/bone_capemgr.*/slots" file, err := embd.FindFirstMatchingFile(pattern) if err != nil { @@ -106,6 +110,7 @@ func ensureFeatureEnabled(id string) error { } str := string(bytes) if strings.Contains(str, id) { + glog.V(3).Infof("bbb: feature %v already enabled", id) return nil } slots, err := os.OpenFile(file, os.O_WRONLY, os.ModeExclusive) @@ -113,6 +118,7 @@ func ensureFeatureEnabled(id string) error { return err } defer slots.Close() + glog.V(3).Infof("bbb: writing %v to slots file", id) _, err = slots.WriteString(id) return err } @@ -153,6 +159,13 @@ func ensureFeatureDisabled(id string) error { return fmt.Errorf("embd: could not disable feature %q", id) } +func spiInitializer() error { + if err := ensureFeatureEnabled("BB-SPIDEV0"); err != nil { + return err + } + return nil +} + func init() { embd.Register(embd.HostBBB, func(rev int) *embd.Descriptor { return &embd.Descriptor{ @@ -165,6 +178,9 @@ func init() { LEDDriver: func() embd.LEDDriver { return embd.NewLEDDriver(ledMap, generic.NewLED) }, + SPIDriver: func() embd.SPIDriver { + return embd.NewSPIDriver(spiDeviceMinor, generic.NewSPIBus, spiInitializer) + }, } }) } diff --git a/host/generic/spibus.go b/host/generic/spibus.go new file mode 100644 index 0000000..e00150c --- /dev/null +++ b/host/generic/spibus.go @@ -0,0 +1,245 @@ +package generic + +import ( + "fmt" + "os" + "sync" + "syscall" + "unsafe" + + "github.com/golang/glog" + "github.com/kidoman/embd" +) + +const ( + spiIOCWrMode = 0x40016B01 + spiIOCWrBitsPerWord = 0x40016B03 + spiIOCWrMaxSpeedHz = 0x40046B04 + + spiIOCRdMode = 0x80016B01 + spiIOCRdBitsPerWord = 0x80016B03 + spiIOCRdMaxSpeedHz = 0x80046B04 + + spiIOCMessage0 = 1073769216 //0x40006B00 + spiIOCIncrementor = 2097152 //0x200000 + + defaultDelayms = 0 + defaultSPIBPW = 8 + defaultSPISpeed = 1000000 +) + +type spiIOCTransfer struct { + txBuf uint64 + rxBuf uint64 + + length uint32 + speedHz uint32 + delayus uint16 + bitsPerWord uint8 +} + +type spiBus struct { + file *os.File + + spiDevMinor byte + + channel byte + mode byte + speed int + bpw int + delayms int + + mu sync.Mutex + + spiTransferData spiIOCTransfer + initialized bool + + initializer func() error +} + +func spiIOCMessageN(n uint32) uint32 { + return (spiIOCMessage0 + (n * spiIOCIncrementor)) +} + +func NewSPIBus(spiDevMinor, mode, channel byte, speed, bpw, delay int, i func() error) embd.SPIBus { + return &spiBus{ + spiDevMinor: spiDevMinor, + mode: mode, + channel: channel, + speed: speed, + bpw: bpw, + delayms: delay, + initializer: i, + } +} + +func (b *spiBus) init() error { + if b.initialized { + return nil + } + + if b.initializer != nil { + if err := b.initializer(); err != nil { + return err + } + } + + var err error + if b.file, err = os.OpenFile(fmt.Sprintf("/dev/spidev%v.%v", b.spiDevMinor, b.channel), os.O_RDWR, os.ModeExclusive); err != nil { + return err + } + glog.V(3).Infof("spi: sucessfully opened file /dev/spidev%v.%v", b.spiDevMinor, b.channel) + + if err = b.setMode(); err != nil { + return err + } + + b.spiTransferData = spiIOCTransfer{} + + if err = b.setSpeed(); err != nil { + return err + } + + if err = b.setBPW(); err != nil { + return err + } + + b.setDelay() + + glog.V(2).Infof("spi: bus %v initialized", b.channel) + glog.V(3).Infof("spi: bus %v initialized with spiIOCTransfer as %v", b.channel, b.spiTransferData) + + b.initialized = true + return nil +} + +func (b *spiBus) setMode() error { + var mode = uint8(b.mode) + glog.V(3).Infof("spi: setting spi mode to %v", mode) + + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, b.file.Fd(), spiIOCWrMode, uintptr(unsafe.Pointer(&mode))) + if errno != 0 { + err := syscall.Errno(errno) + glog.V(3).Infof("spi: failed to set mode due to %v", err.Error()) + return err + } + glog.V(3).Infof("spi: mode set to %v", mode) + return nil +} + +func (b *spiBus) setSpeed() error { + var speed uint32 = defaultSPISpeed + if b.speed > 0 { + speed = uint32(b.speed) + } + + glog.V(3).Infof("spi: setting spi speedMax to %v", speed) + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, b.file.Fd(), spiIOCWrMaxSpeedHz, uintptr(unsafe.Pointer(&speed))) + if errno != 0 { + err := syscall.Errno(errno) + glog.V(3).Infof("spi: failed to set speedMax due to %v", err.Error()) + return err + } + glog.V(3).Infof("spi: speedMax set to %v", speed) + b.spiTransferData.speedHz = speed + + return nil +} + +func (b *spiBus) setBPW() error { + var bpw uint8 = defaultSPIBPW + if b.bpw > 0 { + bpw = uint8(b.bpw) + } + + glog.V(3).Infof("spi: setting spi bpw to %v", bpw) + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, b.file.Fd(), spiIOCWrBitsPerWord, uintptr(unsafe.Pointer(&bpw))) + if errno != 0 { + err := syscall.Errno(errno) + glog.V(3).Infof("spi: failed to set bpw due to %v", err.Error()) + return err + } + glog.V(3).Infof("spi: bpw set to %v", bpw) + b.spiTransferData.bitsPerWord = uint8(bpw) + return nil +} + +func (b *spiBus) setDelay() { + var delay uint16 = defaultDelayms + if b.delayms > 0 { + delay = uint16(b.delayms) + } + + glog.V(3).Infof("spi: delayms set to %v", delay) + b.spiTransferData.delayus = delay +} + +func (b *spiBus) TransferAndRecieveData(dataBuffer []uint8) error { + if err := b.init(); err != nil { + return err + } + + len := len(dataBuffer) + dataCarrier := b.spiTransferData + + dataCarrier.length = uint32(len) + dataCarrier.txBuf = uint64(uintptr(unsafe.Pointer(&dataBuffer[0]))) + dataCarrier.rxBuf = uint64(uintptr(unsafe.Pointer(&dataBuffer[0]))) + + glog.V(3).Infof("spi: sending dataBuffer %v with carrier %v", dataBuffer, dataCarrier) + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, b.file.Fd(), uintptr(spiIOCMessageN(1)), uintptr(unsafe.Pointer(&dataCarrier))) + if errno != 0 { + err := syscall.Errno(errno) + glog.V(3).Infof("spi: failed to read due to %v", err.Error()) + return err + } + glog.V(3).Infof("spi: read into dataBuffer %v", dataBuffer) + return nil +} + +func (b *spiBus) ReceiveData(len int) ([]uint8, error) { + if err := b.init(); err != nil { + return nil, err + } + + data := make([]uint8, len) + if err := b.TransferAndRecieveData(data); err != nil { + return nil, err + } + return data, nil +} + +func (b *spiBus) TransferAndReceiveByte(data byte) (byte, error) { + if err := b.init(); err != nil { + return 0, err + } + + d := [1]uint8{uint8(data)} + if err := b.TransferAndRecieveData(d[:]); err != nil { + return 0, err + } + return d[0], nil +} + +func (b *spiBus) ReceiveByte() (byte, error) { + if err := b.init(); err != nil { + return 0, err + } + + var d [1]uint8 + if err := b.TransferAndRecieveData(d[:]); err != nil { + return 0, err + } + return byte(d[0]), nil +} + +func (b *spiBus) Close() error { + b.mu.Lock() + defer b.mu.Unlock() + + if !b.initialized { + return nil + } + + return b.file.Close() +} diff --git a/host/rpi/rpi.go b/host/rpi/rpi.go index 36a9905..41db588 100644 --- a/host/rpi/rpi.go +++ b/host/rpi/rpi.go @@ -13,6 +13,8 @@ import ( "github.com/kidoman/embd/host/generic" ) +var spiDeviceMinor = byte(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}, &embd.PinDesc{ID: "P1_5", Aliases: []string{"1", "GPIO_1", "SCL", "I2C0_SCL"}, Caps: embd.CapDigital | embd.CapI2C, DigitalLogical: 1}, @@ -74,6 +76,9 @@ func init() { LEDDriver: func() embd.LEDDriver { return embd.NewLEDDriver(ledMap, generic.NewLED) }, + SPIDriver: func() embd.SPIDriver { + return embd.NewSPIDriver(spiDeviceMinor, generic.NewSPIBus, nil) + }, } }) } diff --git a/samples/.gitignore b/samples/.gitignore index 836f1c7..af9b180 100644 --- a/samples/.gitignore +++ b/samples/.gitignore @@ -23,3 +23,6 @@ tmp006 universalblinker us020 watersensor +spi +mcp3008 +spimcp3008 diff --git a/samples/mcp3008.go b/samples/mcp3008.go new file mode 100644 index 0000000..791d56a --- /dev/null +++ b/samples/mcp3008.go @@ -0,0 +1,45 @@ +// +build ignore + +// this sample uses the mcp3008 package to interface with the 8-bit ADC and works without code change on bbb and rpi +package main + +import ( + "flag" + "fmt" + "time" + + "github.com/kidoman/embd" + "github.com/kidoman/embd/convertors/mcp3008" + _ "github.com/kidoman/embd/host/all" +) + +const ( + channel = 0 + speed = 1000000 + bpw = 8 + delay = 0 +) + +func main() { + flag.Parse() + fmt.Println("this is a sample code for mcp3008 10bit 8 channel ADC") + + if err := embd.InitSPI(); err != nil { + panic(err) + } + defer embd.CloseSPI() + + spiBus := embd.NewSPIBus(embd.SPIMode0, channel, speed, bpw, delay) + defer spiBus.Close() + + adc := mcp3008.New(mcp3008.SingleMode, spiBus) + + for i := 0; i < 20; i++ { + time.Sleep(1 * time.Second) + val, err := adc.AnalogValueAt(0) + if err != nil { + panic(err) + } + fmt.Printf("analog value is: %v\n", val) + } +} diff --git a/samples/spi.go b/samples/spi.go new file mode 100644 index 0000000..1f702b2 --- /dev/null +++ b/samples/spi.go @@ -0,0 +1,48 @@ +// +build ignore + +package main + +import ( + "fmt" + + "github.com/kidoman/embd" + _ "github.com/kidoman/embd/host/all" +) + +func main() { + if err := embd.InitSPI(); err != nil { + panic(err) + } + defer embd.CloseSPI() + + spiBus := embd.NewSPIBus(embd.SPIMode0, 0, 1000000, 8, 0) + defer spiBus.Close() + + dataBuf := [3]uint8{1, 2, 3} + + if err := spiBus.TransferAndRecieveData(dataBuf[:]); err != nil { + panic(err) + } + + fmt.Println("received data is: %v", dataBuf) + + dataReceived, err := spiBus.ReceiveData(3) + if err != nil { + panic(err) + } + + fmt.Println("received data is: %v", dataReceived) + + dataByte := byte(1) + receivedByte, err := spiBus.TransferAndReceiveByte(dataByte) + if err != nil { + panic(err) + } + fmt.Println("received byte is: %v", receivedByte) + + receivedByte, err = spiBus.ReceiveByte() + if err != nil { + panic(err) + } + fmt.Println("received byte is: %v", receivedByte) +} diff --git a/samples/spimcp3008.go b/samples/spimcp3008.go new file mode 100644 index 0000000..efff661 --- /dev/null +++ b/samples/spimcp3008.go @@ -0,0 +1,50 @@ +// +build ignore + +package main + +import ( + "flag" + "fmt" + "time" + + "github.com/kidoman/embd" + _ "github.com/kidoman/embd/host/all" +) + +const ( + channel = 0 + speed = 1000000 + bpw = 8 + delay = 0 +) + +func main() { + flag.Parse() + if err := embd.InitSPI(); err != nil { + panic(err) + } + defer embd.CloseSPI() + + bus := embd.NewSPIBus(embd.SPIMode0, channel, speed, bpw, delay) + defer bus.Close() + + for i := 0; i < 30; i++ { + time.Sleep(1 * time.Second) + val, err := getSensorValue(bus) + if err != nil { + panic(err) + } + fmt.Printf("value is: %v\n", val) + } +} + +func getSensorValue(bus embd.SPIBus) (uint16, error) { + data := [3]uint8{1, 128, 0} + + var err error + err = bus.TransferAndRecieveData(data[:]) + if err != nil { + return uint16(0), err + } + return uint16(data[1]&0x03)<<8 | uint16(data[2]), nil +} diff --git a/spi.go b/spi.go new file mode 100644 index 0000000..1cc50e0 --- /dev/null +++ b/spi.go @@ -0,0 +1,86 @@ +// SPI support. + +package embd + +const ( + spiCpha = 0x01 + spiCpol = 0x02 + + // SPIMode0 represents the mode0 operation (CPOL=0 CPHA=0) of spi. + SPIMode0 = (0 | 0) + + // SPIMode1 represents the mode0 operation (CPOL=0 CPHA=1) of spi. + SPIMode1 = (0 | spiCpha) + + // SPIMode2 represents the mode0 operation (CPOL=1 CPHA=0) of spi. + SPIMode2 = (spiCpol | 0) + + // SPIMode3 represents the mode0 operation (CPOL=1 CPHA=1) of spi. + SPIMode3 = (spiCpol | spiCpha) +) + +// SPI interface allows interaction with the SPI bus. +type SPIBus interface { + // TransferAndRecieveData transmits data in a buffer(slice) and receives into it. + TransferAndRecieveData(dataBuffer []uint8) error + + // ReceiveData receives data of length len into a slice. + ReceiveData(len int) ([]uint8, error) + + // TransferAndReceiveByte transmits a byte data and receives a byte. + TransferAndReceiveByte(data byte) (byte, error) + + // ReceiveByte receives a byte data. + ReceiveByte() (byte, error) + + // Close releases the resources associated with the bus. + Close() error +} + +// SPIDriver interface interacts with the host descriptors to allow us +// control of SPI communication. +type SPIDriver interface { + // Bus returns a SPIBus interface which allows us to use spi functionalities + Bus(byte, byte, int, int, int) SPIBus + + // Close cleans up all the initialized SPIbus + Close() error +} + +var spiDriverInitialized bool +var spiDriverInstance SPIDriver + +// InitSPI initializes the SPI driver. +func InitSPI() error { + if spiDriverInitialized { + return nil + } + + desc, err := DescribeHost() + if err != nil { + return err + } + + if desc.SPIDriver == nil { + return ErrFeatureNotSupported + } + + spiDriverInstance = desc.SPIDriver() + spiDriverInitialized = true + + return nil +} + +// CloseSPI releases resources associated with the SPI driver. +func CloseSPI() error { + return spiDriverInstance.Close() +} + +// NewSPIBus returns a SPIBus. +func NewSPIBus(mode, channel byte, speed, bpw, delay int) SPIBus { + if err := InitSPI(); err != nil { + panic(err) + } + + return spiDriverInstance.Bus(mode, channel, speed, bpw, delay) +} diff --git a/spidriver.go b/spidriver.go new file mode 100644 index 0000000..cecc19d --- /dev/null +++ b/spidriver.go @@ -0,0 +1,45 @@ +package embd + +import "sync" + +type spiBusFactory func(byte, byte, byte, int, int, int, func() error) SPIBus + +type spiDriver struct { + spiDevMinor byte + initializer func() error + + busMap map[byte]SPIBus + busMapLock sync.Mutex + + sbf spiBusFactory +} + +// NewSPIDriver returns a SPIDriver interface which allows control +// over the SPI bus. +func NewSPIDriver(spiDevMinor byte, sbf spiBusFactory, i func() error) SPIDriver { + return &spiDriver{ + spiDevMinor: spiDevMinor, + sbf: sbf, + initializer: i, + } +} + +// Bus returns a SPIBus interface which allows us to use spi functionalities +func (s *spiDriver) Bus(mode, channel byte, speed, bpw, delay int) SPIBus { + s.busMapLock.Lock() + defer s.busMapLock.Unlock() + + b := s.sbf(s.spiDevMinor, mode, channel, speed, bpw, delay, s.initializer) + s.busMap = make(map[byte]SPIBus) + s.busMap[channel] = b + return b +} + +// Close cleans up all the initialized SPIbus +func (s *spiDriver) Close() error { + for _, b := range s.busMap { + b.Close() + } + + return nil +}