Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
49 changes: 49 additions & 0 deletions examples/honeyhsc/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"machine"
"time"

"tinygo.org/x/drivers"
"tinygo.org/x/drivers/honeyhsc"
)

// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
// these defaults are valid for the HSCMRNN030PA2A3 chip
const (
i2cAddress = 0x28
// 10%
outputMinimum = 0x666
// 90% of 2^14 - 1
outputMax = 0x399A
// min is 0 for sensors that give absolute values
pressureMin = 0
// 30psi (and we want results in millipascals)
// pressureMax = 206842.7
pressureMax = 206843 * 1000
)

func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{
Frequency: 10_000,
Copy link
Member

Choose a reason for hiding this comment

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

10kHz is an unusually low frequency, is this intentional? (Many chips don't even support such low frequencies).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very good catch. 100kHz is minimum for this IC.

SDA: machine.I2C0_SDA_PIN,
SCL: machine.I2C0_SCL_PIN,
})
if err != nil {
panic(err.Error())
}
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
for {
time.Sleep(time.Second)
const measuremask = drivers.Pressure | drivers.Temperature
err := sensor.Update(measuremask)
if err != nil {
println("error updating measurements:", err.Error())
continue
}
P := sensor.Pressure()
T := sensor.Temperature()
println("pressure:", P, "temperature:", T)
}
}
135 changes: 135 additions & 0 deletions honeyhsc/hsc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package honeyhsc

import (
"errors"
"math"

"tinygo.org/x/drivers"
)

var (
errSensorMissing = errors.New("hsc: not connected")
errDiagnostic = errors.New("hsc: diagnostic error")
)

const (
measuremask = drivers.Pressure | drivers.Temperature
statusMask = 0b1100_0000
statusOffset = 6
)

// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevI2C struct {
bus drivers.I2C
dev
addr uint8
buf [4]byte
}

func NewDevI2C(bus drivers.I2C, addr uint8, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
h := &DevI2C{
bus: bus,
addr: addr,
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h
}

func (d *DevI2C) Update(which drivers.Measurement) error {
Copy link
Member

Choose a reason for hiding this comment

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

Same here, please add documentation to this method (especially since it is exported).

if which&measuremask == 0 {
return nil
}
buf := &d.buf
const reg = 0
value := (d.addr << 1) | 1
err := d.bus.Tx(uint16(d.addr), []byte{reg, value}, buf[:])
Copy link
Member

Choose a reason for hiding this comment

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

Note that the []byte{reg, value} will likely cause a heap allocation depending on the I2C implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks for the catch!

if err != nil {
return err
}
status := (buf[0] & statusMask) >> statusOffset
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])
tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
return d.dev.update(status, bridgeData, tempData)
}

type pinout func(level bool)

// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
type DevSPI struct {
spi drivers.SPI
cs pinout
dev
buf [4]byte
}

func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Same here, this should get some explanation. At least clarifying with parameters like outMin mean.

h := &DevSPI{
spi: conn,
cs: cs,
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h, nil
}

// Update implements the sensor interface.
Copy link
Member

Choose a reason for hiding this comment

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

That's technically correct, but not super helpful to users.
What about something like

Update reads the sensor values from the device. The values are stored in the driver object, and can be read with the Pressure and Temperature methods.

func (h *DevSPI) Update(which drivers.Measurement) error {
if which&measuremask == 0 {
return nil
}
buf := &h.buf
h.cs(false)
err := h.spi.Tx(nil, buf[:4])
h.cs(true)
if err != nil {
return err
}
// First two bits are status bits.
status := (buf[0] & statusMask) >> statusOffset
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])

tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
return h.dev.update(status, bridgeData, tempData)
}

type dev struct {
pressure int32
temp int32
cmin, cmax uint16
pmin, pmax int32
}

// Pressure returns pressure in millipascals [mPa].
func (d *dev) Pressure() int32 {
return d.pressure
}

// Temperature returns temperature in milliKelvin [mK].
func (d *dev) Temperature() int32 {
Copy link
Member

Choose a reason for hiding this comment

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

By far most drivers use milli-degrees Celsius. See for example:

drivers/bma42x/bma42x.go

Lines 277 to 279 in 228e57c

// Temperature returns the last read temperature in celsius milli degrees (1°C
// is 1000).
func (d *Device) Temperature() int32 {

And:

drivers/bme280/bme280.go

Lines 180 to 181 in 228e57c

// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (int32, error) {

I would like to keep that as a convention.

Also see #332 which I still think is a good idea. It's somewhat out of scope for this PR though.

return d.temp
}

func (d *dev) update(status uint8, bridgeData, tempData uint16) error {
if tempData == math.MaxUint16 {
return errSensorMissing
} else if status == 3 {
return errDiagnostic
}

// Take care not to overflow here.
p := (int32(bridgeData)-int32(d.cmin))*(d.pmax-d.pmin)/int32(d.cmax-d.cmin) + d.pmin
d.temp = int32(tempData)
d.pressure = p
return nil
}