-
Notifications
You must be signed in to change notification settings - Fork 229
Add Honeywell HSC TruStability SPI+I2C pressure sensor driver #799
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
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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: 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) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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) { | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||||||||
| 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 { | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By far most drivers use milli-degrees Celsius. See for example: Lines 277 to 279 in 228e57c
And: Lines 180 to 181 in 228e57c
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 + 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 | ||||||||||||
| } | ||||||||||||
There was a problem hiding this comment.
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).