Skip to content
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

sht1x: add a new sht1x sensor #35

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
60 changes: 60 additions & 0 deletions samples/sht1x.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// +build ignore

package main

import (
"flag"
"fmt"
"os"
"os/signal"
"time"

"github.com/kidoman/embd"
"github.com/kidoman/embd/sensor/sht1x"

_ "github.com/kidoman/embd/host/all"
)

func main() {
flag.Parse()

if err := embd.InitGPIO(); err != nil {
panic(err)
}
defer embd.CloseGPIO()

data, err := embd.NewDigitalPin(4)
if err != nil {
panic(err)
}
defer data.Close()

clock, err := embd.NewDigitalPin(3)
if err != nil {
panic(err)
}
defer clock.Close()

sensor := sht1x.New(data, clock)

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, os.Kill)
defer signal.Stop(quit)

ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

for {
select {
case <-ticker.C:
m, err := sensor.Measure()
if err != nil {
fmt.Printf("err: %v\n", err)
}
fmt.Printf("Temperature: %.1fC, Relative Humidity: %.1f%%, Dew Point: %.1fC\n", m.Temperature, m.RelativeHumidity, m.DewPoint)

case <-quit:
return
}
}
}
298 changes: 298 additions & 0 deletions sensor/sht1x/sht1x.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
// Package sht1x allows interfacing with Sensirion SHT1x family of humidity
// and temperature sensors.
package sht1x

import (
"errors"
"math"
"sync"
"time"

"github.com/kidoman/embd"
)

// Constants and implementation derived from
// http://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/Humidity/Sensirion_Humidity_SHT1x_Datasheet_V5.pdf
const (
d1 = -40.1
d2 = 0.01
c1 = -2.0468
c2 = 0.0367
c3 = -0.0000015955
t1 = 0.01
t2 = 0.00008
tnA0 = 243.12
mA0 = 17.62
tnB0 = 272.62
mB0 = 22.46
measureTemperatureCommand = 3
measureHumidityCommand = 5
)

// Measurement represents a measurement of temperature in degrees Celsius,
// relative humidity in percent, and dew point in degrees Celsius.
type Measurement struct {
Temperature float64
RelativeHumidity float64
DewPoint float64
}

// SHT1x represents a SHT1x humidity and temperature sensor.
type SHT1x struct {
DataPin, ClockPin embd.DigitalPin
m sync.RWMutex
}

// New returns a handle to a SHT1x sensor.
func New(dataPin, clockPin embd.DigitalPin) *SHT1x {
return &SHT1x{
DataPin: dataPin,
ClockPin: clockPin,
}
}

// Measure returns a Measurement with temperature in degrees Celsius,
// relative humidity in percent, and dew point in degrees Celsius.
func (d *SHT1x) Measure() (*Measurement, error) {
rawTemperature, err := d.rawTemperature()
if err != nil {
return nil, err
}

rawHumidity, err := d.rawHumidity()
if err != nil {
return nil, err
}

temperature := temperatureFromRaw(rawTemperature)
relativeHumidity := humidityFromRaw(rawHumidity, temperature)
dewPoint := calculateDewPoint(relativeHumidity, temperature)

measurement := &Measurement{
Temperature: temperature,
RelativeHumidity: relativeHumidity,
DewPoint: dewPoint,
}

return measurement, nil
}

// Temperature returns a temperature measurement in degrees Celsius.
func (d *SHT1x) Temperature() (float64, error) {
raw, err := d.rawTemperature()
if err != nil {
return 0, err
}

temperature := temperatureFromRaw(raw)

return temperature, nil
}

// RelativeHumidity returns a relative humidity measurement in percent.
func (d *SHT1x) RelativeHumidity() (float64, error) {
rawTemperature, err := d.rawTemperature()
if err != nil {
return 0, err
}

rawHumidity, err := d.rawHumidity()
if err != nil {
return 0, err
}

temperature := temperatureFromRaw(rawTemperature)
relativeHumidity := humidityFromRaw(rawHumidity, temperature)

return relativeHumidity, nil
}

// DewPoint returns a dew point measurement in degrees Celsius.
func (d *SHT1x) DewPoint() (float64, error) {
rawTemperature, err := d.rawTemperature()
if err != nil {
return 0, err
}

rawHumidity, err := d.rawHumidity()
if err != nil {
return 0, err
}

temperature := temperatureFromRaw(rawTemperature)
relativeHumidity := humidityFromRaw(rawHumidity, temperature)

dewPoint := calculateDewPoint(relativeHumidity, temperature)
return dewPoint, nil
}

func (d *SHT1x) sendCommand(command int) error {
d.DataPin.SetDirection(embd.Out)
d.ClockPin.SetDirection(embd.Out)

d.DataPin.Write(embd.High)
d.ClockPin.Write(embd.High)
d.DataPin.Write(embd.Low)
d.ClockPin.Write(embd.Low)
d.ClockPin.Write(embd.High)
d.DataPin.Write(embd.High)
d.ClockPin.Write(embd.Low)

for i := 0; i < 8; i++ {
bitSet := (command&(1<<uint(7-i)) != 0)
bit := 0
if bitSet {
bit = 1
}
d.DataPin.Write(bit)
d.ClockPin.Write(embd.High)
d.ClockPin.Write(embd.Low)
}

d.DataPin.SetDirection(embd.In)

d.ClockPin.Write(embd.High)
ack, err := d.DataPin.Read()
if err != nil {
return err
}
if ack != embd.Low {
return errors.New("sht1x: command not acknowledged")
}

d.ClockPin.Write(embd.Low)
ack, err = d.DataPin.Read()
if err != nil {
return err
}
if ack != embd.High {
return errors.New("sht1x: command not acknowledged")
}

return nil
}

func (d *SHT1x) waitForResult() error {
d.DataPin.SetDirection(embd.In)

for i := 0; i < 100; i++ {
time.Sleep(10 * time.Millisecond)
ack, err := d.DataPin.Read()
if err != nil {
return err
}
if ack == embd.Low {
return nil
}
}

return errors.New("sht1x: timeout waiting for result")
}

func (d *SHT1x) getData() int {
highByte := d.readByte()

d.sendReadAck()

lowByte := d.readByte()

d.skipCrc()

temp := highByte * 256
temp = temp | lowByte

return temp
}

func (d *SHT1x) readByte() int {
d.ClockPin.SetDirection(embd.Out)
d.DataPin.SetDirection(embd.In)

readByte := 0
for i := 0; i < 8; i++ {
d.ClockPin.Write(embd.High)
val, _ := d.DataPin.Read()
readByte = readByte*2 + val
d.ClockPin.Write(embd.Low)
}
return readByte
}

func (d *SHT1x) sendReadAck() {
d.ClockPin.SetDirection(embd.Out)
d.DataPin.SetDirection(embd.Out)

d.DataPin.Write(embd.High)
d.DataPin.Write(embd.Low)
d.ClockPin.Write(embd.High)
d.ClockPin.Write(embd.Low)
}

func (d *SHT1x) skipCrc() {
d.DataPin.SetDirection(embd.Out)
d.ClockPin.SetDirection(embd.Out)

d.DataPin.Write(embd.High)
d.ClockPin.Write(embd.High)
d.ClockPin.Write(embd.Low)
}

func (d *SHT1x) rawTemperature() (float64, error) {
d.m.Lock()
defer d.m.Unlock()

err := d.sendCommand(measureTemperatureCommand)
if err != nil {
return 0, err
}

err = d.waitForResult()
if err != nil {
return 0, err
}

data := d.getData()

return float64(data), nil
}

func (d *SHT1x) rawHumidity() (float64, error) {
d.m.Lock()
defer d.m.Unlock()

err := d.sendCommand(measureHumidityCommand)
if err != nil {
return 0, err
}

err = d.waitForResult()
if err != nil {
return 0, err
}

data := d.getData()

return float64(data), nil
}

func temperatureFromRaw(rawTemperature float64) float64 {
return d1 + d2*rawTemperature
}

func humidityFromRaw(rawHumidity, temperature float64) float64 {
linearHumidity := c1 + c2*rawHumidity + c3*rawHumidity*rawHumidity
relativeHumidity := (temperature-25)*(t1+t2*rawHumidity) + linearHumidity
return relativeHumidity
}

func calculateDewPoint(humidity, temperature float64) float64 {
tn := tnB0
m := mB0

if temperature > 0 {
tn = tnA0
m = mA0
}

return tn * (math.Log(humidity/100.0) + (m*temperature)/(tn+temperature)) / (m - math.Log(humidity/100.0) - m*temperature/(tn+temperature))
}