Skip to content

Commit 01409c1

Browse files
authored
Introduces driver for Sensirion SHT-4X Temperature/Humidity Sensors. (#109)
Move CRC8 logic into common package for resuability.
1 parent adae653 commit 01409c1

File tree

10 files changed

+735
-73
lines changed

10 files changed

+735
-73
lines changed

common/crc.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
// Package common contains functions used across multiple packages. For
6+
// example, a CRC8 calculation
7+
package common
8+
9+
// CRC8 calculates the 8-bit CRC of the byte slice parameter and returns the
10+
// calculated value. CRC bytes are used in sensors from TI and Sensirion.
11+
func CRC8(bytes []byte) byte {
12+
var crc byte = 0xff
13+
for _, val := range bytes {
14+
crc ^= val
15+
for range 8 {
16+
if (crc & 0x80) == 0 {
17+
crc <<= 1
18+
} else {
19+
crc = (byte)((crc << 1) ^ 0x31)
20+
}
21+
}
22+
}
23+
return crc
24+
}

common/crc_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
package common
6+
7+
import "testing"
8+
9+
func TestCRC8(t *testing.T) {
10+
var tests = []struct {
11+
bytes []byte
12+
result byte
13+
}{
14+
{bytes: []byte{0xbe, 0xef}, result: 0x92},
15+
{bytes: []byte{0x01, 0xa4}, result: 0x4d},
16+
{bytes: []byte{0xab, 0xcd}, result: 0x6f},
17+
}
18+
for _, test := range tests {
19+
res := CRC8(test.bytes)
20+
if res != test.result {
21+
t.Errorf("CRC8(%#v)!=0x%d received 0x%d", test.bytes, test.result, res)
22+
}
23+
}
24+
}

hdc302x/hdc302x.go

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"periph.io/x/conn/v3"
2222
"periph.io/x/conn/v3/i2c"
2323
"periph.io/x/conn/v3/physic"
24+
"periph.io/x/devices/v3/common"
2425
)
2526

2627
type SampleRate uint16
@@ -203,21 +204,6 @@ func countToHumidity(bytes []byte) physic.RelativeHumidity {
203204
return physic.RelativeHumidity(f * float64(physic.PercentRH))
204205
}
205206

206-
func crc8(bytes []byte) byte {
207-
var crc byte = 0xff
208-
for _, val := range bytes {
209-
crc ^= val
210-
for range 8 {
211-
if (crc & 0x80) == 0 {
212-
crc <<= 1
213-
} else {
214-
crc = (byte)((crc << 1) ^ 0x31)
215-
}
216-
}
217-
}
218-
return crc
219-
}
220-
221207
// Halt shuts down the device. If a SenseContinuous operation is in progress,
222208
// its aborted. Implements conn.Resource
223209
func (dev *Dev) Halt() error {
@@ -251,7 +237,7 @@ func (dev *Dev) Sense(env *physic.Env) error {
251237
if err := dev.d.Tx(read, res); err != nil {
252238
return fmt.Errorf("hdc302x: %w", err)
253239
}
254-
if crc8(res[:2]) != res[2] || crc8(res[3:5]) != res[5] {
240+
if common.CRC8(res[:2]) != res[2] || common.CRC8(res[3:5]) != res[5] {
255241
return errInvalidCRC
256242
}
257243
env.Temperature = countToTemperature(res)
@@ -320,7 +306,7 @@ func (dev *Dev) readSerialNumber() int64 {
320306
// this is a 6 byte value read in 3 parts
321307
for range 3 {
322308
err := dev.d.Tx(cmd, r)
323-
if err != nil || (crc8(r[:2]) != r[2]) {
309+
if err != nil || (common.CRC8(r[:2]) != r[2]) {
324310
return result
325311
}
326312
result = result<<16 | (int64(r[0])<<8 | int64(r[1]))
@@ -358,7 +344,7 @@ func (dev *Dev) readAlertValues(cfg *Configuration) error {
358344
if err != nil {
359345
return err
360346
}
361-
if crc8(r[:2]) != r[2] {
347+
if common.CRC8(r[:2]) != r[2] {
362348
return errInvalidCRC
363349
}
364350
wValue := uint16(r[0])<<8 | uint16(r[1])
@@ -381,7 +367,7 @@ func (dev *Dev) readOffsets(cfg *Configuration) error {
381367
if err := dev.d.Tx(readSetOffsets, r); err != nil {
382368
return fmt.Errorf("hdc302x: %w", err)
383369
}
384-
if crc8(r[:2]) != r[2] {
370+
if common.CRC8(r[:2]) != r[2] {
385371
return errInvalidCRC
386372
}
387373

@@ -424,7 +410,7 @@ func (dev *Dev) ReadStatus() (StatusWord, error) {
424410
if err := dev.d.Tx(readStatus, r); err != nil {
425411
return 0, err
426412
}
427-
if crc8(r[:2]) != r[2] {
413+
if common.CRC8(r[:2]) != r[2] {
428414
return 0, errInvalidCRC
429415
}
430416
_ = dev.d.Tx(clearStatus, nil)
@@ -463,7 +449,7 @@ func (dev *Dev) setOffsets(cfg *Configuration) error {
463449
computeTemperatureOffsetByte(cfg.TemperatureOffset),
464450
0,
465451
}
466-
w[4] = crc8(w[2:4])
452+
w[4] = common.CRC8(w[2:4])
467453
return dev.d.Tx(w, nil)
468454
}
469455

@@ -553,7 +539,7 @@ func (dev *Dev) setThresholds(typeAlert bool, tp *ThresholdPair) error {
553539
wval := uint16(0)
554540
wval = (humBits & 0xfe00) | tempBits>>7
555541
w := []byte{cmds[pair][ix][0], cmds[pair][ix][1], byte(wval >> 8), byte(wval & 0xff), 0}
556-
w[4] = crc8(w[2:4])
542+
w[4] = common.CRC8(w[2:4])
557543
err := dev.d.Tx(w, nil)
558544
if err != nil {
559545
return err
@@ -609,7 +595,7 @@ func (dev *Dev) SetHeater(powerLevel HeaterPower) error {
609595
byte((powerLevel >> 8) & 0xff),
610596
byte(powerLevel & 0xff),
611597
0}
612-
setValue[4] = crc8(setValue[2:4])
598+
setValue[4] = common.CRC8(setValue[2:4])
613599
err := dev.d.Tx(setValue, nil)
614600
if err != nil {
615601
return err

hdc302x/hdc302x_test.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -200,22 +200,6 @@ func shutdown(t *testing.T) {
200200
}
201201
}
202202

203-
func TestCRC(t *testing.T) {
204-
var tests = []struct {
205-
bytes []byte
206-
result byte
207-
}{
208-
{bytes: []byte{0xbe, 0xef}, result: 0x92},
209-
{bytes: []byte{0xab, 0xcd}, result: 0x6f},
210-
}
211-
for _, test := range tests {
212-
res := crc8(test.bytes)
213-
if res != test.result {
214-
t.Errorf("crc8(%#v)!=0x%d receieved 0x%d", test.bytes, test.result, res)
215-
}
216-
}
217-
}
218-
219203
// TestConversions tests the various temperature/humidity functions
220204
// for correct operation.
221205
func TestConversions(t *testing.T) {

scd4x/scd4x.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"periph.io/x/conn/v3/i2c"
1414
"periph.io/x/conn/v3/physic"
15+
"periph.io/x/devices/v3/common"
1516
)
1617

1718
// PPM=Parts Per Million. Units of measure for CO2 concentration.
@@ -402,30 +403,14 @@ func (d *Dev) Reset(mode ResetMode) error {
402403
return err
403404
}
404405

405-
func calcCRC(bytes []byte) byte {
406-
polynomial := byte(0x31)
407-
crc := byte(0xff)
408-
for ix := range len(bytes) {
409-
crc ^= bytes[ix]
410-
for crc_bit := byte(8); crc_bit > 0; crc_bit-- {
411-
if (crc & 0x80) == 0x80 {
412-
crc = (crc << 1) ^ polynomial
413-
} else {
414-
crc = (crc << 1)
415-
}
416-
}
417-
}
418-
return crc
419-
}
420-
421406
// makeWriteData converts the slice of word values into byte values with the
422407
// CRC following.
423408
func makeWriteData(data []uint16) []byte {
424409
bytes := make([]byte, len(data)*3)
425410
for ix, val := range data {
426411
bytes[ix*3] = byte((val >> 8) & 0xff)
427412
bytes[ix*3+1] = byte(val & 0xff)
428-
bytes[ix*3+2] = calcCRC(bytes[ix*3 : ix*3+2])
413+
bytes[ix*3+2] = common.CRC8(bytes[ix*3 : ix*3+2])
429414
}
430415
return bytes
431416
}
@@ -464,7 +449,7 @@ func (d *Dev) sendCommand(cmd command, writeData []uint16) ([]uint16, error) {
464449
// verify the CRC as we go.
465450
result := make([]uint16, cmd.responseSize/3)
466451
for ix := range len(result) {
467-
crc := calcCRC(r[ix*3 : ix*3+2])
452+
crc := common.CRC8(r[ix*3 : ix*3+2])
468453
if r[ix*3+2] != crc {
469454
return nil, fmt.Errorf("scd4x cmd 0x%x: invalid crc", cmd.cmdWord)
470455
}

scd4x/scd4x_test.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -154,22 +154,6 @@ func shutdown(t *testing.T) {
154154
}
155155
}
156156

157-
func TestCRC(t *testing.T) {
158-
tests := []struct {
159-
bytes []byte
160-
crc byte
161-
}{
162-
{bytes: []byte{0xbe, 0xef}, crc: 0x92},
163-
{bytes: []byte{0x01, 0xa4}, crc: 0x4d},
164-
}
165-
for _, test := range tests {
166-
res := calcCRC(test.bytes)
167-
if res != test.crc {
168-
t.Error(fmt.Errorf("crc calculation error bytes: %#v, result: 0x%x expected: 0x%x", test.bytes, res, test.crc))
169-
}
170-
}
171-
}
172-
173157
func TestCountToTemperature(t *testing.T) {
174158
tests := []struct {
175159
count uint16

sht4x/example_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2025 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
5+
package sht4x_test
6+
7+
import (
8+
"log"
9+
"time"
10+
11+
"periph.io/x/conn/v3/i2c/i2creg"
12+
"periph.io/x/conn/v3/physic"
13+
"periph.io/x/devices/v3/sht4x"
14+
"periph.io/x/host/v3"
15+
)
16+
17+
// Example shows creating an SHT-4X sensor and reading from it.
18+
func Example() {
19+
if _, err := host.Init(); err != nil {
20+
log.Fatal("Error calling host.init()")
21+
}
22+
bus, err := i2creg.Open("")
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
defer bus.Close()
27+
28+
dev, err := sht4x.New(bus, sht4x.DefaultAddress)
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
33+
env := &physic.Env{}
34+
35+
for range 10 {
36+
err = dev.Sense(env)
37+
if err != nil {
38+
log.Println(err)
39+
} else {
40+
log.Printf("Temperature: %s Humidity: %s\n", env.Temperature, env.Humidity)
41+
}
42+
time.Sleep(time.Second)
43+
}
44+
}

0 commit comments

Comments
 (0)