diff --git a/Makefile b/Makefile index 185374b..75c45a9 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,8 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/hd44780i2c/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/hts221/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic @@ -212,7 +214,7 @@ DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \ hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \ - pcf8563 mcp2515 servo sdcard rtl8720dn image cmd i2csoft + pcf8563 mcp2515 servo sdcard rtl8720dn image cmd i2csoft hts221 TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS)) unit-test: diff --git a/examples/hts221/main.go b/examples/hts221/main.go new file mode 100644 index 0000000..0b48295 --- /dev/null +++ b/examples/hts221/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "machine" + "time" + + "tinygo.org/x/drivers/hts221" +) + +func main() { + + machine.I2C1.Configure(machine.I2CConfig{ + SCL: machine.P0_15, // SCL1 on Nano 33 BLE Sense + SDA: machine.P0_14, // SDA1 on Nano 33 BLE Sense + Frequency: machine.TWI_FREQ_400KHZ, + }) + + sensor := hts221.New(machine.I2C1) + + if !sensor.Connected() { + println("HTS221 not connected!") + return + } + + sensor.Configure() // power on and calibrate + + for { + + h, _ := sensor.ReadHumidity() + t, _ := sensor.ReadTemperature() + println("h =", float32(h)/100.0, "% / t =", float32(t)/1000.0, "*C") + time.Sleep(time.Second) + + } + +} diff --git a/hts221/hts221.go b/hts221/hts221.go new file mode 100644 index 0000000..63f4385 --- /dev/null +++ b/hts221/hts221.go @@ -0,0 +1,178 @@ +// Package hts221 implements a driver for HTS221, +// a capacitive digital sensor for relative humidity and temperature. +// +// Datasheet: https://www.st.com/resource/en/datasheet/hts221.pdf +// +package hts221 + +import ( + "errors" + + "tinygo.org/x/drivers" +) + +// Device wraps an I2C connection to a HTS221 device. +type Device struct { + bus drivers.I2C + Address uint8 + humiditySlope float32 + humidityZero float32 + temperatureSlope float32 + temperatureZero float32 +} + +// Connected returns whether HTS221 has been found. +// It does a "who am I" request and checks the response. +func (d *Device) Connected() bool { + data := []byte{0} + d.bus.ReadRegister(d.Address, HTS221_WHO_AM_I_REG, data) + return data[0] == 0xBC +} + +// Configure sets up the HTS221 device for communication. +func (d *Device) Configure() { + // read calibration data + d.calibration() + // activate device and use block data update mode + d.Power(true) +} + +// Power is for turn on/off the HTS221 device +func (d *Device) Power(status bool) { + data := []byte{0} + if status { + data[0] = 0x84 + } + d.bus.WriteRegister(d.Address, HTS221_CTRL1_REG, data) +} + +// ReadHumidity returns the relative humidity in percent * 100. +// Returns an error if the device is not turned on. +func (d *Device) ReadHumidity() (humidity int32, err error) { + err = d.waitForOneShot(0x02) + if err != nil { + return + } + + // read data and calibrate + data := []byte{0, 0} + d.bus.ReadRegister(d.Address, HTS221_HUMID_OUT_REG, data[:1]) + d.bus.ReadRegister(d.Address, HTS221_HUMID_OUT_REG+1, data[1:]) + hValue := readInt(data[1], data[0]) + hValueCalib := float32(hValue)*d.humiditySlope + d.humidityZero + + return int32(hValueCalib * 100), nil +} + +// ReadTemperature returns the temperature in celsius milli degrees (°C/1000). +// Returns an error if the device is not turned on. +func (d *Device) ReadTemperature() (temperature int32, err error) { + err = d.waitForOneShot(0x01) + if err != nil { + return + } + + // read data and calibrate + data := []byte{0, 0} + d.bus.ReadRegister(d.Address, HTS221_TEMP_OUT_REG, data[:1]) + d.bus.ReadRegister(d.Address, HTS221_TEMP_OUT_REG+1, data[1:]) + tValue := readInt(data[1], data[0]) + tValueCalib := float32(tValue)*d.temperatureSlope + d.temperatureZero + + return int32(tValueCalib * 1000), nil +} + +// Resolution sets the HTS221's resolution mode. +// The higher resolutions are more accurate but comsume more power (see datasheet). +// The number of averaged samples will be (h + 2) ^ 2, (t + 1) ^ 2 +// +func (d *Device) Resolution(h uint8, t uint8) { + if h > 7 { + h = 3 // default + } + if t > 7 { + t = 3 // default + } + d.bus.WriteRegister(d.Address, HTS221_AV_CONF_REG, []byte{h<<3 | t}) +} + +// private functions + +// read factory calibration data +func (d *Device) calibration() { + h0rH, h1rH := []byte{0}, []byte{0} + t0degC, t1degC := []byte{0}, []byte{0} + t1t0msb := []byte{0} + h0t0Out, h1t0Out := []byte{0, 0}, []byte{0, 0} + t0Out, t1Out := []byte{0, 0}, []byte{0, 0} + + d.bus.ReadRegister(d.Address, HTS221_H0_rH_x2_REG, h0rH) + d.bus.ReadRegister(d.Address, HTS221_H1_rH_x2_REG, h1rH) + d.bus.ReadRegister(d.Address, HTS221_T0_degC_x8_REG, t0degC) + d.bus.ReadRegister(d.Address, HTS221_T1_degC_x8_REG, t1degC) + d.bus.ReadRegister(d.Address, HTS221_T1_T0_MSB_REG, t1t0msb) + d.bus.ReadRegister(d.Address, HTS221_H0_T0_OUT_REG, h0t0Out[:1]) + d.bus.ReadRegister(d.Address, HTS221_H0_T0_OUT_REG+1, h0t0Out[1:]) + d.bus.ReadRegister(d.Address, HTS221_H1_T0_OUT_REG, h1t0Out[:1]) + d.bus.ReadRegister(d.Address, HTS221_H1_T0_OUT_REG+1, h1t0Out[1:]) + d.bus.ReadRegister(d.Address, HTS221_T0_OUT_REG, t0Out[:1]) + d.bus.ReadRegister(d.Address, HTS221_T0_OUT_REG+1, t0Out[1:]) + d.bus.ReadRegister(d.Address, HTS221_T1_OUT_REG, t1Out[:1]) + d.bus.ReadRegister(d.Address, HTS221_T1_OUT_REG+1, t1Out[1:]) + + h0rH_v := float32(h0rH[0]) / 2.0 + h1rH_v := float32(h1rH[0]) / 2.0 + t0degC_v := float32(readUint(t1t0msb[0]&0x03, t0degC[0])) / 8.0 + t1degC_v := float32(readUint(t1t0msb[0]&0x0C>>2, t1degC[0])) / 8.0 + h0t0Out_v := float32(readInt(h0t0Out[1], h0t0Out[0])) + h1t0Out_v := float32(readInt(h1t0Out[1], h1t0Out[0])) + t0Out_v := float32(readInt(t0Out[1], t0Out[0])) + t1Out_v := float32(readInt(t1Out[1], t1Out[0])) + + d.humiditySlope = (h1rH_v - h0rH_v) / (h1t0Out_v - h0t0Out_v) + d.humidityZero = h0rH_v - d.humiditySlope*h0t0Out_v + d.temperatureSlope = (t1degC_v - t0degC_v) / (t1Out_v - t0Out_v) + d.temperatureZero = t0degC_v - d.temperatureSlope*t0Out_v +} + +// wait and trigger one shot in block update +func (d *Device) waitForOneShot(filter uint8) error { + data := []byte{0} + + // check if the device is on + d.bus.ReadRegister(d.Address, HTS221_CTRL1_REG, data) + if data[0]&0x80 == 0 { + return errors.New("device is off, unable to query") + } + + // wait until one shot (one conversion) is ready to go + data[0] = 1 + for { + d.bus.ReadRegister(d.Address, HTS221_CTRL2_REG, data) + if data[0]&0x01 == 0 { + break + } + } + + // trigger one shot + d.bus.WriteRegister(d.Address, HTS221_CTRL2_REG, []byte{0x01}) + + // wait until conversion completed + data[0] = 0 + for { + d.bus.ReadRegister(d.Address, HTS221_STATUS_REG, data) + if data[0]&filter == filter { + break + } + } + + return nil +} + +func readUint(msb byte, lsb byte) uint16 { + return uint16(msb)<<8 | uint16(lsb) +} + +func readInt(msb byte, lsb byte) int16 { + return int16(uint16(msb)<<8 | uint16(lsb)) +} diff --git a/hts221/hts221_generic.go b/hts221/hts221_generic.go new file mode 100644 index 0000000..c9a93cd --- /dev/null +++ b/hts221/hts221_generic.go @@ -0,0 +1,13 @@ +// +build !nano_33_ble + +package hts221 + +import "tinygo.org/x/drivers" + +// New creates a new HTS221 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not touch the device. +func New(bus drivers.I2C) Device { + return Device{bus: bus, Address: HTS221_ADDRESS} +} diff --git a/hts221/hts221_nano_33_ble.go b/hts221/hts221_nano_33_ble.go new file mode 100644 index 0000000..7fb2e7b --- /dev/null +++ b/hts221/hts221_nano_33_ble.go @@ -0,0 +1,28 @@ +// +build nano_33_ble + +package hts221 + +import ( + "machine" + "time" + + "tinygo.org/x/drivers" +) + +// New creates a new HTS221 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not touch the device. +func New(bus drivers.I2C) Device { + // turn on internal power pin (machine.P0_22) and I2C1 pullups power pin (machine.P1_00) + // and wait a moment. + ENV := machine.P0_22 + ENV.Configure(machine.PinConfig{Mode: machine.PinOutput}) + ENV.High() + R := machine.P1_00 + R.Configure(machine.PinConfig{Mode: machine.PinOutput}) + R.High() + time.Sleep(time.Millisecond * 10) + + return Device{bus: bus, Address: HTS221_ADDRESS} +} diff --git a/hts221/registers.go b/hts221/registers.go new file mode 100644 index 0000000..62a27a3 --- /dev/null +++ b/hts221/registers.go @@ -0,0 +1,27 @@ +package hts221 + +const ( + + // I2C address + HTS221_ADDRESS = 0x5F + + // control/status registers + HTS221_WHO_AM_I_REG = 0x0F + HTS221_AV_CONF_REG = 0x10 + HTS221_CTRL1_REG = 0x20 + HTS221_CTRL2_REG = 0x21 + HTS221_STATUS_REG = 0x27 + HTS221_HUMID_OUT_REG = 0x28 + HTS221_TEMP_OUT_REG = 0x2A + + // calibration registers + HTS221_H0_rH_x2_REG = 0x30 + HTS221_H1_rH_x2_REG = 0x31 + HTS221_T0_degC_x8_REG = 0x32 + HTS221_T1_degC_x8_REG = 0x33 + HTS221_T1_T0_MSB_REG = 0x35 + HTS221_H0_T0_OUT_REG = 0x36 + HTS221_H1_T0_OUT_REG = 0x3A + HTS221_T0_OUT_REG = 0x3C + HTS221_T1_OUT_REG = 0x3E +)