Skip to content

Commit 5cb360a

Browse files
authored
Add Honeywell HSC TruStability SPI+I2C pressure sensor driver (#799)
* add honeywell pressure sensor * apply @aykevl suggestions
1 parent 51b604c commit 5cb360a

File tree

2 files changed

+240
-0
lines changed

2 files changed

+240
-0
lines changed

examples/honeyhsc/example.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"machine"
5+
"time"
6+
7+
"tinygo.org/x/drivers"
8+
"tinygo.org/x/drivers/honeyhsc"
9+
)
10+
11+
// Data taken from https://github.com/rodan/honeywell_hsc_ssc_i2c/blob/master/hsc_ssc_i2c.cpp
12+
// these defaults are valid for the HSCMRNN030PA2A3 chip
13+
const (
14+
i2cAddress = 0x28
15+
// 10%
16+
outputMinimum = 0x666
17+
// 90% of 2^14 - 1
18+
outputMax = 0x399A
19+
// min is 0 for sensors that give absolute values
20+
pressureMin = 0
21+
// 30psi (and we want results in millipascals)
22+
// pressureMax = 206842.7
23+
pressureMax = 206843 * 1000
24+
)
25+
26+
func main() {
27+
bus := machine.I2C0
28+
err := bus.Configure(machine.I2CConfig{
29+
Frequency: 400_000, // 100kHz minimum and 400kHz I2C maximum clock. 50 to 800 for SPI.
30+
SDA: machine.I2C0_SDA_PIN,
31+
SCL: machine.I2C0_SCL_PIN,
32+
})
33+
if err != nil {
34+
panic(err.Error())
35+
}
36+
sensor := honeyhsc.NewDevI2C(bus, i2cAddress, outputMinimum, outputMax, pressureMin, pressureMax)
37+
for {
38+
time.Sleep(time.Second)
39+
const measuremask = drivers.Pressure | drivers.Temperature
40+
err := sensor.Update(measuremask)
41+
if err != nil {
42+
println("error updating measurements:", err.Error())
43+
continue
44+
}
45+
P := sensor.Pressure()
46+
T := sensor.Temperature()
47+
println("pressure:", P, "temperature:", T)
48+
}
49+
}

honeyhsc/hsc.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package honeyhsc
2+
3+
import (
4+
"errors"
5+
"math"
6+
7+
"tinygo.org/x/drivers"
8+
)
9+
10+
var (
11+
errSensorMissing = errors.New("hsc: not connected")
12+
errDiagnostic = errors.New("hsc: diagnostic error")
13+
)
14+
15+
const (
16+
measuremask = drivers.Pressure | drivers.Temperature
17+
statusMask = 0b1100_0000
18+
statusOffset = 6
19+
)
20+
21+
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
22+
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
23+
type DevI2C struct {
24+
bus drivers.I2C
25+
dev
26+
addr uint8
27+
buf [6]byte
28+
}
29+
30+
// NewDevI2C creates and returns a new DevI2C that communicates with an HSC device over the provided I2C bus.
31+
// Parameters:
32+
// - bus: the I2C bus to use.
33+
// - addr: the 7-bit I2C address of the sensor.
34+
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
35+
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
36+
//
37+
// The returned DevI2C will use these calibration parameters to convert raw bridge counts to pressure.
38+
func NewDevI2C(bus drivers.I2C, addr, outMin, outMax uint16, pMin, pMax int32) *DevI2C {
39+
h := &DevI2C{
40+
bus: bus,
41+
addr: uint8(addr),
42+
dev: dev{
43+
cmin: outMin,
44+
cmax: outMax,
45+
pmin: pMin,
46+
pmax: pMax,
47+
},
48+
}
49+
return h
50+
}
51+
52+
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the I2C-attached HSC device.
53+
// It performs an Update internally to get the latest temperature value.
54+
func (h *DevI2C) ReadTemperature() (int32, error) {
55+
err := h.Update(drivers.Temperature)
56+
if err != nil {
57+
return 0, err
58+
}
59+
return h.Temperature(), nil
60+
}
61+
62+
// Update reads both temperature and pressure data from the I2C-attached HSC device when
63+
// the requested measurement mask includes pressure or temperature.
64+
// If neither pressure nor temperature is requested, Update is a no-op.
65+
func (d *DevI2C) Update(which drivers.Measurement) error {
66+
// Update performs an I2C transaction to read 4 bytes, parses the status bits, 14-bit bridge data and
67+
// temperature bits, and forwards them to the internal update routine. Any I2C transport error is returned,
68+
// as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
69+
if which&measuremask == 0 {
70+
return nil
71+
}
72+
rbuf := d.buf[:4]
73+
wbuf := d.buf[4:6]
74+
const reg = 0
75+
value := (d.addr << 1) | 1
76+
wbuf[0] = reg
77+
wbuf[1] = value
78+
err := d.bus.Tx(uint16(d.addr), wbuf, rbuf)
79+
if err != nil {
80+
return err
81+
}
82+
status := (rbuf[0] & statusMask) >> statusOffset
83+
bridgeData := (uint16(rbuf[0]&^statusMask) << 8) | uint16(rbuf[1])
84+
tempData := uint16(rbuf[2])<<8 | uint16(rbuf[3]&0xe0)>>5
85+
return d.dev.update(status, bridgeData, tempData)
86+
}
87+
88+
type pinout func(level bool)
89+
90+
// DevI2C is the TruStability® High Accuracy Silicon Ceramic (HSC) Series is a piezoresistive silicon pressure sensor offering a ratiometric
91+
// analog or digital output for reading pressure over the specified full scale pressure span and temperature range.
92+
type DevSPI struct {
93+
spi drivers.SPI
94+
cs pinout
95+
dev
96+
buf [4]byte
97+
}
98+
99+
// NewDevSPI creates and returns a new DevSPI that communicates with an HSC device over SPI.
100+
// Parameters:
101+
// - conn: the SPI connection to use.
102+
// - cs: a chip-select function that drives the device select line low/high.
103+
// - outMin, outMax: raw output code range (counts) corresponding to the pressure span. Depends on sensor model.
104+
// - pMin, pMax: pressure range endpoints in millipascals (mPa). Depends on sensor model.
105+
//
106+
// The function returns the constructed DevSPI and an error value (currently always nil).
107+
func NewDevSPI(conn drivers.SPI, cs pinout, outMin, outMax uint16, pMin, pMax int32) (*DevSPI, error) {
108+
h := &DevSPI{
109+
spi: conn,
110+
cs: cs,
111+
dev: dev{
112+
cmin: outMin,
113+
cmax: outMax,
114+
pmin: pMin,
115+
pmax: pMax,
116+
},
117+
}
118+
return h, nil
119+
}
120+
121+
// ReadTemperature reads and returns the temperature in milliKelvin (mC) from the SPI-attached HSC device.
122+
// It performs an Update internally to get the latest temperature value.
123+
func (h *DevSPI) ReadTemperature() (int32, error) {
124+
err := h.Update(drivers.Temperature)
125+
if err != nil {
126+
return 0, err
127+
}
128+
return h.Temperature(), nil
129+
}
130+
131+
// Update reads pressure and temperature data from the SPI-attached HSC device when the requested measurement mask includes
132+
// pressure or temperature. If neither pressure nor temperature is requested, Update is a no-op.
133+
func (h *DevSPI) Update(which drivers.Measurement) error {
134+
// It toggles the provided chip-select, performs an SPI transfer to read 4 bytes, parses the status bits,
135+
// 14-bit bridge data and temperature bits, and forwards them to the internal update routine. Any SPI
136+
// transport error is returned, as well as errors produced by the internal update (e.g. errSensorMissing, errDiagnostic).
137+
if which&measuremask == 0 {
138+
return nil
139+
}
140+
buf := &h.buf
141+
h.cs(false)
142+
err := h.spi.Tx(nil, buf[:4])
143+
h.cs(true)
144+
if err != nil {
145+
return err
146+
}
147+
// First two bits are status bits.
148+
status := (buf[0] & statusMask) >> statusOffset
149+
bridgeData := (uint16(buf[0]&^statusMask) << 8) | uint16(buf[1])
150+
151+
tempData := uint16(buf[2])<<8 | uint16(buf[3]&0xe0)>>5
152+
return h.dev.update(status, bridgeData, tempData)
153+
}
154+
155+
type dev struct {
156+
pressure int32
157+
temp int32
158+
cmin, cmax uint16
159+
pmin, pmax int32
160+
}
161+
162+
// Pressure returns the most recently computed pressure value in millipascals (mPa).
163+
// The value is taken from the last successful Update.
164+
func (d *dev) Pressure() int32 {
165+
return d.pressure
166+
}
167+
168+
// Temperature returns the most recently read temperature value in milliKelvin (mC).
169+
// The value is taken from the last successful Update.
170+
func (d *dev) Temperature() int32 {
171+
return d.temp + 273_150
172+
}
173+
174+
// update interprets raw sensor fields (status, bridgeData, tempData) and updates the dev's stored
175+
// pressure and temperature. It returns errSensorMissing when the temperature raw value indicates no sensor
176+
// (tempData == math.MaxUint16), errDiagnostic when the status indicates a device diagnostic condition
177+
// (status == 3), or nil on success. Pressure is computed with integer arithmetic using the configured
178+
// cmin/cmax -> pmin/pmax linear mapping in order to avoid overflows.
179+
func (d *dev) update(status uint8, bridgeData, tempData uint16) error {
180+
if tempData == math.MaxUint16 {
181+
return errSensorMissing
182+
} else if status == 3 {
183+
return errDiagnostic
184+
}
185+
186+
// Take care not to overflow here.
187+
p := (int32(bridgeData)-int32(d.cmin))*(d.pmax-d.pmin)/int32(d.cmax-d.cmin) + d.pmin
188+
d.temp = int32(tempData)
189+
d.pressure = p
190+
return nil
191+
}

0 commit comments

Comments
 (0)