adxl345: I²C support (#67)

- 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.
pull/71/head
Benoit Pereira da Silva 2 years ago committed by GitHub
parent 931687b33a
commit bd7de1d36d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 {
c conn.Conn
name string
s spi.Conn
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)
}
}

@ -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)
}
}
}

@ -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 <duration> 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)
}
}
}
Loading…
Cancel
Save