Add Honeywell HSC TruStability SPI+I2C pressure sensor driver (#799)

* add honeywell pressure sensor
* apply @aykevl suggestions
This commit is contained in:
Pat Whittingslow
2025-11-08 06:45:18 -03:00
committed by GitHub
parent 51b604ce97
commit 5cb360a4bf
2 changed files with 240 additions and 0 deletions
+49
View File
@@ -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: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
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)
}
}
+191
View File
@@ -0,0 +1,191 @@
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 [6]byte
}
// NewDevI2C creates and returns a new DevI2C that communicates with an HSC device over the provided I2C bus.
// Parameters:
// - bus: the I2C bus to use.
// - addr: the 7-bit I2C address of the sensor.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The returned DevI2C will use these calibration parameters to convert raw bridge counts to pressure.
func NewDevI2C(bus drivers.I2C, addr, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
h := &DevI2C{
bus: bus,
addr: uint8(addr),
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the I2C-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevI2C) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads both temperature and pressure data from the I2C-attached HSC device when
// the requested measurement mask includes pressure or temperature.
// If neither pressure nor temperature is requested, Update is a no-op.
func (d *DevI2C) Update(which drivers.Measurement) error {
// Update performs an I2C transaction to read 4 bytes, parses the status bits, 14-bit bridge data and
// temperature bits, and forwards them to the internal update routine. Any I2C transport error is returned,
// as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
if which&measuremask == 0 {
return nil
}
rbuf := d.buf[:4]
wbuf := d.buf[4:6]
const reg = 0
value := (d.addr << 1) | 1
wbuf[0] = reg
wbuf[1] = value
err := d.bus.Tx(uint16(d.addr), wbuf, rbuf)
if err != nil {
return err
}
status := (rbuf[0] & statusMask) >> statusOffset
bridgeData := (uint16(rbuf[0]&^statusMask) << 8) | uint16(rbuf[1])
tempData := uint16(rbuf[2])<<8 | uint16(rbuf[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
}
// NewDevSPI creates and returns a new DevSPI that communicates with an HSC device over SPI.
// Parameters:
// - conn: the SPI connection to use.
// - cs: a chip-select function that drives the device select line low/high.
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
//
// The function returns the constructed DevSPI and an error value (currently always nil).
func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
h := &DevSPI{
spi: conn,
cs: cs,
dev: dev{
cmin: outMin,
cmax: outMax,
pmin: pMin,
pmax: pMax,
},
}
return h, nil
}
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the SPI-attached HSC device.
// It performs an Update internally to get the latest temperature value.
func (h *DevSPI) ReadTemperature() (int32, error) {
err := h.Update(drivers.Temperature)
if err != nil {
return 0, err
}
return h.Temperature(), nil
}
// Update reads pressure and temperature data from the SPI-attached HSC device when the requested measurement mask includes
// pressure or temperature. If neither pressure nor temperature is requested, Update is a no-op.
func (h *DevSPI) Update(which drivers.Measurement) error {
// It toggles the provided chip-select, performs an SPI transfer to read 4 bytes, parses the status bits,
// 14-bit bridge data and temperature bits, and forwards them to the internal update routine. Any SPI
// transport error is returned, as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
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 the most recently computed pressure value in millipascals (mPa).
// The value is taken from the last successful Update.
func (d *dev) Pressure() int32 {
return d.pressure
}
// Temperature returns the most recently read temperature value in milliKelvin (mC).
// The value is taken from the last successful Update.
func (d *dev) Temperature() int32 {
return d.temp + 273_150
}
// update interprets raw sensor fields (status, bridgeData, tempData) and updates the dev's stored
// pressure and temperature. It returns errSensorMissing when the temperature raw value indicates no sensor
// (tempData == math.MaxUint16), errDiagnostic when the status indicates a device diagnostic condition
// (status == 3), or nil on success. Pressure is computed with integer arithmetic using the configured
// cmin/cmax -> pmin/pmax linear mapping in order to avoid overflows.
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
}