From bd7de1d36d4a57786ca82c9d2340b39937b0729a Mon Sep 17 00:00:00 2001 From: Benoit Pereira da Silva Date: Tue, 2 Jan 2024 19:34:52 +0100 Subject: [PATCH] =?UTF-8?q?adxl345:=20=20I=C2=B2C=20support=20(#67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed useless ReadRaw - Changed the scope of ReadAndCombine to private readAndCombine - Renamed ExampleNewI2C & ExampleNewSpi to follow the recommandations. Exposed an I2CAddr for those who may need it. --- adxl345/adxl345.go | 127 ++++++++++++++++++++++++------------- adxl345/example/example.go | 56 ---------------- adxl345/example_test.go | 97 ++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 101 deletions(-) delete mode 100644 adxl345/example/example.go create mode 100644 adxl345/example_test.go diff --git a/adxl345/adxl345.go b/adxl345/adxl345.go index 54bf23b..59dac36 100644 --- a/adxl345/adxl345.go +++ b/adxl345/adxl345.go @@ -7,10 +7,13 @@ package adxl345 import ( "encoding/binary" "fmt" + "periph.io/x/conn/v3" + "periph.io/x/conn/v3/i2c" "periph.io/x/conn/v3/physic" "periph.io/x/conn/v3/spi" ) +// Sensitivity represents the sensitivity of the Accelerometer. type Sensitivity byte // The following constants are register used by the ADXL345 @@ -70,6 +73,13 @@ const ( S16G Sensitivity = 0x03 // Sensitivity at 16g ) +// Currently tested devices + +const ( + AdxlXXX = 0x01 // No specific expectation. For non-detected devices the response is 0x00 + Adxl345 = 0xE5 // Expecting an Adxl345 +) + var ( SpiFrequency = physic.MegaHertz * 2 SpiMode = spi.Mode3 // Defines the base clock signal, along with the polarity and phase of the data signal. @@ -77,7 +87,7 @@ var ( ) var DefaultOpts = Opts{ - ExpectedDeviceID: 0xE5, + ExpectedDeviceID: AdxlXXX, // No specific expectation by default Sensitivity: S2G, } @@ -89,22 +99,44 @@ type Opts struct { // Dev is a driver for the ADXL345 accelerometer // It uses the SPI interface to communicate with the device. type Dev struct { - name string - s spi.Conn + c conn.Conn + name string + isSPI bool // The sensitivity of the device (2G, 4G, 8G, 16G) // Set to 2G by default, can be changed in the Opts at initialization. sensitivity Sensitivity } +func (d *Dev) Mode() string { + if d.isSPI { + return "SPI" + } else { + return "I²C" + } +} + func (d *Dev) String() string { - return fmt.Sprintf("ADXL345{Sensitivity:%s}", d.sensitivity) + return fmt.Sprintf("%s{Sensitivity:%s, Mode:%s}", d.name, d.sensitivity, d.Mode()) +} + +// NewI2C returns an object that communicates over I²C to ADXL345 +// accelerometer. +// +// The device is automatically turned on and the sensitivity is set to the Opts.Sensitivity. +func NewI2C(b i2c.Bus, addr uint16, opts *Opts) (*Dev, error) { + d := &Dev{ + c: &i2c.Dev{Bus: b, Addr: addr}, + isSPI: false} + if err := d.makeDev(opts); err != nil { + return nil, err + } + return d, nil } -// NewSpi creates a new ADXL345 Dev with a spi connection or returns an error. -// The bus and chip parameters define the SPI bus and chip select to use. -// The SPI s is configured. -// The device is turned on. -// The device is verified to be an ADXL345. +// NewSpi returns an object that communicates over spi to ADXL345 +// accelerometer. +// +// The device is automatically turned on and the sensitivity is set to the Opts.Sensitivity. func NewSpi(p spi.Port, o *Opts) (*Dev, error) { // Convert the spi.Port into a spi.Conn so it can be used for communication. c, err := p.Connect(SpiFrequency, SpiMode, SpiBits) @@ -112,25 +144,45 @@ func NewSpi(p spi.Port, o *Opts) (*Dev, error) { return nil, err } d := &Dev{ - name: "ADXL345", - s: c, + c: c, + isSPI: true, } - err = d.TurnOn() + err = d.makeDev(o) if err != nil { return nil, err } + return d, nil +} + +// makeDev turns on with the expected sensitivity and verifies if it is a supported device. +func (d *Dev) makeDev(o *Opts) error { + err := d.TurnOn() + if err != nil { + return err + } if o.Sensitivity != S2G { // default err = d.setSensitivity(o.Sensitivity) if err != nil { - return nil, err + return err } } - // Verify that the device is an ADXL345. - rx, _ := d.ReadRaw(DeviceID) - if rx[1] != o.ExpectedDeviceID { - return nil, fmt.Errorf("wrong device connected should be an adxl345 should be\"%#x\" rx0=\"%#x\" rx1=\"%#x\"", o.ExpectedDeviceID, rx[0], rx[1]) + // Verify that the device Id + tx := []byte{DeviceID | 0x80, 0x00} + rx := make([]byte, len(tx)) + err = d.c.Tx(tx, rx) + if err != nil { + return fmt.Errorf("unable to read the deviceID \"%s\"", err.Error()) + } + switch rx[1] { + case Adxl345: + d.name = "adxl345" + return nil + case o.ExpectedDeviceID: + d.name = fmt.Sprintf("expected%#x", o.ExpectedDeviceID) + return nil + default: + return fmt.Errorf("unrecognized device expected=\"%#02x\" or \"%#02x\"found=\"%#02x\" ", o.ExpectedDeviceID, Adxl345, rx) } - return d, nil } // SetSensitivity sets the sensitivity of the ADXL345. @@ -162,57 +214,42 @@ func (d *Dev) TurnOff() error { // This is a simple synchronous implementation. func (d *Dev) Update() Acceleration { return Acceleration{ - X: d.ReadAndCombine(DataX0, DataX1), - Y: d.ReadAndCombine(DataY0, DataY1), - Z: d.ReadAndCombine(DataZ0, DataZ1), + X: d.readAndCombine(DataX0, DataX1), + Y: d.readAndCombine(DataY0, DataY1), + Z: d.readAndCombine(DataZ0, DataZ1), } } -// ReadAndCombine combines two registers to form a 16-bit value. +// readAndCombine combines two registers to form a 16-bit value. // The ADXL345 uses two 8-bit registers to store the output data for each axis. -// X := d.ReadAndCombine(DataX0, DataX1) where: +// X := d.readAndCombine(DataX0, DataX1) where: // `DataX0` is the address of the lower byte (LSB, least significant byte) // `DataX1` is the address of the upper byte (MSB, most significant byte) // The ADXL345 combines both registers to deliver 16-bit output for each acceleration axis. // A similar approach is used for the Y and Z axes. This technique provides higher precision in the measurements. -func (d *Dev) ReadAndCombine(reg1, reg2 byte) int16 { +func (d *Dev) readAndCombine(reg1, reg2 byte) int16 { low, _ := d.Read(reg1) high, _ := d.Read(reg2) - return int16(uint16(high)<<8) | int16(low) } // Read reads a 16-bit value from the specified register address. func (d *Dev) Read(regAddress byte) (int16, error) { + // Send a two-byte sequence: + // - The first byte contains the address with bit 7 set high to indicate read op + // - The second byte is a "don't care" value, usually zero tx := []byte{regAddress | 0x80, 0x00} rx := make([]byte, len(tx)) - err := d.s.Tx(tx, rx) + err := d.c.Tx(tx, rx) if err != nil { return 0, err } return int16(binary.LittleEndian.Uint16(rx)), nil } -// ReadRaw reads a []byte value from the specified register address. -func (d *Dev) ReadRaw(regAddress byte) ([]byte, error) { - // Send a two-byte sequence: - // - The first byte contains the address with bit 7 set high to indicate read op - // - The second byte is a "don't care" value, usually zero - tx := []byte{regAddress | 0x80, 0x00} - rx := make([]byte, len(tx)) - err := d.s.Tx(tx, rx) - return rx, err -} - // Write writes a 1 byte value to the specified register address. func (d *Dev) Write(regAddress byte, value byte) error { - // Prepare a 2-byte buffer with the register address and the desired value. - tx := []byte{regAddress, value} - // Prepare a receiving buffer of the same size as the transmit buffer. - rx := make([]byte, len(tx)) - // Perform the transfer. We expect the SPI device to write back an acknowledgement. - err := d.s.Tx(tx, rx) - return err + return d.c.Tx([]byte{regAddress, value}, nil) } // Acceleration represents the acceleration on the three axes X,Y,Z. @@ -245,6 +282,6 @@ func (s Sensitivity) String() string { case S16G: return "+/-16g" default: - return "unsupported" + return fmt.Sprintf("unknown sensitivity: %#x", s) } } diff --git a/adxl345/example/example.go b/adxl345/example/example.go deleted file mode 100644 index 595a676..0000000 --- a/adxl345/example/example.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 The Periph Authors. All rights reserved. -// Use of this source code is governed under the Apache License, Version 2.0 -// that can be found in the LICENSE file. - -package example - -import ( - "fmt" - "log" - "periph.io/x/conn/v3/spi/spireg" - "periph.io/x/devices/v3/adxl345" - "periph.io/x/host/v3" - "time" -) - -// Example reads the acceleration values every 30ms for 3 seconds. -func Example() { - - // Initialize the host - // Make sure periph is initialized. - if _, err := host.Init(); err != nil { - log.Fatal(err) - } - - // Use spireg SPI port registry to find the first available SPI bus. - p, err := spireg.Open("") - if err != nil { - log.Fatal(err) - } - - defer p.Close() - - d, err := adxl345.NewSpi(p, &adxl345.DefaultOpts) - if err != nil { - panic(err) - } - - fmt.Println(d.String()) - - // use a ticker to read the acceleration values every 200ms - ticker := time.NewTicker(30 * time.Millisecond) - defer ticker.Stop() - - // stop after 3 seconds - stop := time.After(3 * time.Second) - - for { - select { - case <-stop: - return - case <-ticker.C: - a := d.Update() - fmt.Println(a) - } - } -} diff --git a/adxl345/example_test.go b/adxl345/example_test.go new file mode 100644 index 0000000..d6f3152 --- /dev/null +++ b/adxl345/example_test.go @@ -0,0 +1,97 @@ +// Copyright 2023 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package adxl345 + +import ( + "fmt" + "log" + "periph.io/x/conn/v3/i2c/i2creg" + "periph.io/x/conn/v3/spi/spireg" + "periph.io/x/host/v3" + "time" +) + +var I2CAddr uint16 = 0x53 + +// ExampleNewI2C uses an adxl345 device connected by I²C. +// You can set the I²C address by setting I2CAddr (default is 0x53). +// it reads the acceleration values every 30ms for 30 seconds. +// You can i use `i2dctools` to find the I²C bus number +// e.g : sudo apt-get install i2c-tools +// +// sudo i2cdetect -y 1 +func ExampleNewI2C() { + mustInitHost() + + // Use i2creg to find the first available I²C bus. + // Generally I2C1 on raspberry pi. + p, err := i2creg.Open("") + if err != nil { + log.Fatal(err) + } + fmt.Print(p.String()) + + defer p.Close() + + d, err := NewI2C(p, I2CAddr, &DefaultOpts) + if err != nil { + panic(err) + } + measure(d, 30*time.Second) +} + +// ExampleNewSpi uses an adxl345 device connected by SPI. +// it reads the acceleration values every 30ms for 30 seconds. +func ExampleNewSpi() { + + mustInitHost() + + // Use spireg SPI port registry to find the first available SPI bus. + p, err := spireg.Open("") + if err != nil { + log.Fatal(err) + } + + defer p.Close() + + d, err := NewSpi(p, &DefaultOpts) + if err != nil { + panic(err) + } + + measure(d, 30*time.Second) +} + +// mustInitHost Make sure host is initialized. +func mustInitHost() { + + if _, err := host.Init(); err != nil { + log.Fatal(err) + } +} + +// measure reads the acceleration values every 30ms for seconds +func measure(d *Dev, duration time.Duration) { + + fmt.Println(d.String()) + + mode := d.Mode() + // use a ticker to read the acceleration values every 200ms + ticker := time.NewTicker(30 * time.Millisecond) + defer ticker.Stop() + + // stop after 3 seconds + stop := time.After(duration) + + for { + select { + case <-stop: + return + case <-ticker.C: + a := d.Update() + fmt.Println(mode, a) + } + } +}