Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adxl345: I²C support #67

Merged
merged 29 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a0f580f
adxl345: Driver, initial commit for review.
benoit-pereira-da-silva Dec 28, 2023
d3ad1aa
Added sensitivity option.
benoit-pereira-da-silva Dec 28, 2023
006e2f0
Keep it simple remove useless Acceleration func
benoit-pereira-da-silva Dec 28, 2023
76bf276
Expose the simpliest api possible.
benoit-pereira-da-silva Dec 28, 2023
9f4770f
Added a bunch of clarifications
benoit-pereira-da-silva Dec 28, 2023
75b1480
Sensitivity String returns a human readable value
benoit-pereira-da-silva Dec 28, 2023
7e0a946
- Mark the register constants more explicitly.
benoit-pereira-da-silva Dec 28, 2023
99ce1e2
Licence compliance, adding Copyright 2023 The Periph Authors. All rig…
benoit-pereira-da-silva Dec 28, 2023
a5a4a2c
Doc
benoit-pereira-da-silva Dec 28, 2023
b62bbe2
Prepare to support I2C in the future
benoit-pereira-da-silva Dec 28, 2023
9ddc052
Some adxlXXX device may be well-supported so i added an internal swit…
benoit-pereira-da-silva Dec 29, 2023
5eba749
Added I²C support.
benoit-pereira-da-silva Dec 29, 2023
3dfa619
Fixing inconsistent Read of device id on multiple instantiation.
benoit-pereira-da-silva Dec 29, 2023
8778b8a
adxl345: Driver, initial commit for review.
benoit-pereira-da-silva Dec 28, 2023
641bb8a
Added sensitivity option.
benoit-pereira-da-silva Dec 28, 2023
dc6dcef
Keep it simple remove useless Acceleration func
benoit-pereira-da-silva Dec 28, 2023
e9a2fa7
Expose the simpliest api possible.
benoit-pereira-da-silva Dec 28, 2023
4c68073
Added a bunch of clarifications
benoit-pereira-da-silva Dec 28, 2023
490bf3b
Sensitivity String returns a human readable value
benoit-pereira-da-silva Dec 28, 2023
d22b104
- Mark the register constants more explicitly.
benoit-pereira-da-silva Dec 28, 2023
d4248eb
Licence compliance, adding Copyright 2023 The Periph Authors. All rig…
benoit-pereira-da-silva Dec 28, 2023
8f0a10c
Doc
benoit-pereira-da-silva Dec 28, 2023
c7ff3e4
Prepare to support I2C in the future
benoit-pereira-da-silva Dec 28, 2023
c666127
Some adxlXXX device may be well-supported so i added an internal swit…
benoit-pereira-da-silva Dec 29, 2023
5fec9d4
Added I²C support.
benoit-pereira-da-silva Dec 29, 2023
d15a6e9
Fixing inconsistent Read of device id on multiple instantiation.
benoit-pereira-da-silva Dec 29, 2023
c89b202
Merge branch 'main' of github.com:benoit-pereira-da-silva/devices int…
benoit-pereira-da-silva Jan 1, 2024
60fc493
moving example to example_test.go file
benoit-pereira-da-silva Jan 2, 2024
0c715a3
Renamed ExampleNewI2C & ExampleNewSpi to follow the recommandations. …
benoit-pereira-da-silva Jan 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 82 additions & 45 deletions adxl345/adxl345.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -70,14 +73,21 @@ 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.
SpiBits = 8
)

var DefaultOpts = Opts{
ExpectedDeviceID: 0xE5,
ExpectedDeviceID: AdxlXXX, // No specific expectation by default
Sensitivity: S2G,
}

Expand All @@ -89,48 +99,90 @@ 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)
if err != nil {
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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -245,6 +282,6 @@ func (s Sensitivity) String() string {
case S16G:
return "+/-16g"
default:
return "unsupported"
return fmt.Sprintf("unknown sensitivity: %#x", s)
}
}
56 changes: 0 additions & 56 deletions adxl345/example/example.go

This file was deleted.

97 changes: 97 additions & 0 deletions adxl345/example_test.go
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: if you use package adxl345_test, the example in the documentation becomes copy-pastable as-is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll resolve to commit, this can be done in a follow up and is optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been pushing the change.


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