diff --git a/config/example-extras.cfg b/config/example-extras.cfg index 830381ce..7463051c 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -966,6 +966,40 @@ # The I2C speed (in Hz) to use when communicating with the sensor. Default # is 100000. On some MCUs changing this value has no effect. +# HTU21D family two wire interface (I2C) environmental sensor. +# Note that this sensor is not intended for use with extruders and heater beds, +# but rather for montitoring ambient temperature (C) and relative humidity. +# See sample-macros.cfg for a gcode_macro that may be used to report humidity +# in addition to temperature. +#[temperature_sensor my_sensor] +# See the "temperature_sensor" section below for a description of its +# parameters. The parameters below describe HTU21D family sensor parameters. +#sensor_type: +# Must be "HTU21D" , "SI7013", "SI7020", "SI7021" or "SHT21" +#i2c_address: +# Default is 64 (0x40). +#i2c_mcu: +# MCU the sensor is connected to. Default is the primary mcu. +#i2c_bus: +# The I2C bus the sensor is connected to. On some MCU platforms the default +# is bus 0. On platforms without bus 0 this parameter is required. +#i2c_speed: +# The I2C speed (in Hz) to use when communicating with the sensor. Default +# is 100000. On some MCUs changing this value has no effect. +#htu21d_hold_master: +# If the sensor can hold the I2C buf while reading. If True no other bus +# comunication can be performed while reading is in progress.Default is False +#htu21d_resolution: +# The resolution of temperature and humidity reading. +# Valid values are: +# 'TEMP14_HUM12' -> 14bit for Temp and 12bit for humidity +# 'TEMP13_HUM10' -> 13bit for Temp and 10bit for humidity +# 'TEMP12_HUM08' -> 12bit for Temp and 08bit for humidity +# 'TEMP11_HUM11' -> 11bit for Temp and 11bit for humidity +# Default is: "TEMP11_HUM11" +#htu21d_report_time: +# interval in seconds between readings. Default is 30 + # Generic heaters (one may define any number of sections with a # "heater_generic" prefix). These heaters behave similarly to standard # heaters (extruders, heated beds). Use the SET_HEATER_TEMPERATURE diff --git a/config/sample-macros.cfg b/config/sample-macros.cfg index 3b92e34b..60ec51ec 100644 --- a/config/sample-macros.cfg +++ b/config/sample-macros.cfg @@ -147,3 +147,32 @@ gcode: printer[SENSOR].temperature, printer[SENSOR].pressure, printer[SENSOR].humidity))} + +###################################################################### +# HTU21D family Environmental Sensor +###################################################################### + +# The macro below assumes you have a HTU21D sensor_type defined in one +# of the applicable sections in printer.cfg, such as: +# +#[temperature_sensor my_sensor] +#sensor_type: HTU21D +# +# Note the format of the parameter SENSOR in the macro below. The HTU21D +# sensor status can be accessed using the format "htu21d ". +# The example section above is named "my_sensor", thus the htu21d can be +# queried as follows: +# +# QUERY_HTU21D SENSOR='htu21d my_sensor' +# +# Since a default parameter is defined one could simply enter QUERY_HTU21D +# as well. + +[gcode_macro QUERY_HTU21D] +default_parameter_SENSOR: htu21d my_sensor +gcode: + {printer.gcode.action_respond_info( + "Temperature: %.2f C\n" + "Humidity: %.2f%%" % ( + printer[SENSOR].temperature, + printer[SENSOR].humidity))} diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 29fb5d6e..cdb5c186 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -265,6 +265,7 @@ class PrinterHeaters: self.printer.try_load_module(config, "adc_temperature") self.printer.try_load_module(config, "spi_temperature") self.printer.try_load_module(config, "bme280") + self.printer.try_load_module(config, "htu21d") sensor_type = config.get('sensor_type') if sensor_type not in self.sensor_factories: raise self.printer.config_error( diff --git a/klippy/extras/htu21d.py b/klippy/extras/htu21d.py new file mode 100644 index 00000000..3c9e0590 --- /dev/null +++ b/klippy/extras/htu21d.py @@ -0,0 +1,238 @@ +# HTU21D(F)/Si7013/Si7020/Si7021/SHT21 i2c based temperature sensors support +# +# Copyright (C) 2020 Lucio Tarantino +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +###################################################################### +# NOTE: The implementation requires write support of length 0 +# before reading on the i2c bus of the mcu. +# +# Compatible Sensors: +# HTU21D - Tested on Linux MCU. +# Si7013 - Untested +# Si7020 - Untested +# Si7021 - Untested +# SHT21 - Untested +# +###################################################################### + +import bus +import logging + +HTU21D_I2C_ADDR= 0x40 + +HTU21D_COMMANDS = { + 'HTU21D_TEMP' :0xE3, + 'HTU21D_HUMID' :0xE5, + 'HTU21D_TEMP_NH' :0xF3, + 'HTU21D_HUMID_NH' :0xF5, + 'WRITE' :0xE6, + 'READ' :0xE7, + 'RESET' :0xFE, + 'SERIAL' :[0xFA,0x0F,0xFC,0xC9], + 'FIRMWARE_READ' :[0x84,0xB8] + +} + +HTU21D_RESOLUTION_MASK = 0x7E; +HTU21D_RESOLUTIONS = { + 'TEMP14_HUM12':int('00000000',2), + 'TEMP13_HUM10':int('10000000',2), + 'TEMP12_HUM08':int('00000001',2), + 'TEMP11_HUM11':int('10000001',2) +} + +# Device with conversion time for tmp/resolution bit +# The format is: +# :{id:, ..:[,].. } +HTU21D_DEVICES = { + 'SI7013':{'id':0x0D, + 'TEMP14_HUM12':[.11,.12], + 'TEMP13_HUM10':[ .7, .5], + 'TEMP12_HUM08':[ .4, .4], + 'TEMP11_HUM11':[ .3, .7]}, + 'SI7020':{'id':0x14, + 'TEMP14_HUM12':[.11,.12], + 'TEMP13_HUM10':[ .7, .5], + 'TEMP12_HUM08':[ .4, .4], + 'TEMP11_HUM11':[ .3, .7]}, + 'SI7021':{'id':0x14, + 'TEMP14_HUM12':[.11,.12], + 'TEMP13_HUM10':[ .7, .5], + 'TEMP12_HUM08':[ .4, .4], + 'TEMP11_HUM11':[ .3, .7]}, + 'SHT21': {'id':0x31, + 'TEMP14_HUM12':[.85,.29], + 'TEMP13_HUM10':[.43, .9], + 'TEMP12_HUM08':[.22, .4], + 'TEMP11_HUM11':[.11,.15]}, + 'HTU21D':{'id':0x32, + 'TEMP14_HUM12':[.50,.16], + 'TEMP13_HUM10':[.25, .5], + 'TEMP12_HUM08':[.13, .3], + 'TEMP11_HUM11':[.12, .8]} +} +#temperature coefficient for RH compensation at range 0C..80C, +# for HTU21D & SHT21 only +HTU21D_TEMP_COEFFICIENT= -0.15 +#crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1 +HTU21D_CRC8_POLYNOMINAL= 0x13100 + +class HTU21D: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name().split()[-1] + self.reactor = self.printer.get_reactor() + self.i2c = bus.MCU_I2C_from_config( + config, default_addr=HTU21D_I2C_ADDR, default_speed=100000) + self.hold_master_mode = config.getboolean('htu21d_hold_master',False) + self.resolution = config.get('htu21d_resolution','TEMP12_HUM08') + self.report_time = config.getint('htu21d_report_time',30,minval=5) + if self.resolution not in HTU21D_RESOLUTIONS: + raise config.error("Invalid HTU21D Resolution. Valid are %s" + % '|'.join(HTU21D_RESOLUTIONS.keys())) + self.deviceId = config.get('sensor_type') + self.temp = self.humidity = 0. + self.sample_timer = self.reactor.register_timer(self._sample_htu21d) + self.printer.add_object("htu21d " + self.name, self) + self.printer.register_event_handler("klippy:ready", self.handle_ready) + + def handle_ready(self): + self._init_htu21d() + self.reactor.update_timer(self.sample_timer, self.reactor.NOW) + + def setup_minmax(self, min_temp, max_temp): + pass + + def setup_callback(self, cb): + self._callback = cb + + def _init_htu21d(self): + # Device Soft Reset + self.i2c.i2c_write([HTU21D_COMMANDS['RESET']]) + # Wait 15ms after reset + self.reactor.pause(self.reactor.monotonic() + .15) + + # Read ChipId + params = self.i2c.i2c_read([HTU21D_COMMANDS['SERIAL'][2], + HTU21D_COMMANDS['SERIAL'][3]], 3) + response = bytearray(params['response']) + rdevId = response[0] << 8 + rdevId |= response[1] + checksum = response[2] + if self._chekCRC8(rdevId) != checksum: + logging.warn("htu21d: Reading deviceId !Checksum error!") + rdevId = rdevId >> 8 + deviceId_list = list( + filter( + lambda elem: HTU21D_DEVICES[elem]['id'] == rdevId,HTU21D_DEVICES) + ) + if len(deviceId_list) != 0: + logging.info("htu21d: Found Device Type %s" % deviceId_list[0]) + else: + logging.warn("htu21d: Unknown Device ID %#x " % rdevId) + + if(self.deviceId != deviceId_list[0]): + logging.warn( + "htu21d: Found device %s. Forcing to type %s as config.", + deviceId_list[0],self.deviceId) + + # Set Resolution + params = self.i2c.i2c_read([HTU21D_COMMANDS['READ']], 1) + response = bytearray(params['response']) + registerData = response[0] & HTU21D_RESOLUTION_MASK + registerData |= HTU21D_RESOLUTIONS[self.resolution] + self.i2c.i2c_write([HTU21D_COMMANDS['WRITE']],registerData) + logging.info("htu21d: Setting resolution to %s " % self.resolution) + + def _sample_htu21d(self, eventtime): + try: + # Read Temeprature + if self.hold_master_mode: + params = self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_TEMP']]) + else: + params = self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_TEMP_NH']]) + + # Wait + self.reactor.pause(self.reactor.monotonic() + + HTU21D_DEVICES[self.deviceId][self.resolution][0]) + + + params = self.i2c.i2c_read([],3) + + response = bytearray(params['response']) + rtemp = response[0] << 8 + rtemp |= response[1] + if self._chekCRC8(rtemp) != response[2]: + logging.warn("htu21d: Checksum error on Temperature reading!") + else: + self.temp = (0.002681 * float(rtemp) - 46.85) + logging.debug("htu21d: Temperature %.2f " % self.temp) + + # Read Humidity + if self.hold_master_mode: + self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_HUMID']]) + else: + self.i2c.i2c_write([HTU21D_COMMANDS['HTU21D_HUMID_NH']]) + + # Wait + self.reactor.pause(self.reactor.monotonic() + + HTU21D_DEVICES[self.deviceId][self.resolution][1]) + + params = self.i2c.i2c_read([],3) + + response = bytearray(params['response']) + rhumid = response[0] << 8 + rhumid|= response[1] + if self._chekCRC8(rhumid) != response[2]: + logging.warn("htu21d: Checksum error on Humidity reading!") + else: + #clear status bits, + # humidity always returns xxxxxx10 in the LSB field + rhumid ^= 0x02; + self.humidity = (0.001907 * float(rhumid) - 6) + if (self.humidity < 0): + #due to RH accuracy, measured value might be + # slightly less than 0 or more 100 + self.temp = 0 + elif (self.humidity > 100): + self.humidity = 100 + # Only for HTU21D & SHT21. + # Calculates temperature compensated Humidity, %RH + if( self.deviceId in ['SHT21','HTU21D'] + and self.temp > 0 and self.temp < 80): + logging.debug("htu21d: Do temp compensation..") + self.humidity = self.humidity + + (25.0 - self.temp) * HTU21D_TEMP_COEFFICIENT; + logging.debug("htu21d: Humidity %.2f " % self.humidity) + except Exception: + logging.exception("htu21d: Error reading data") + self.temp = self.humidity = .0 + return self.reactor.NEVER + + measured_time = self.reactor.monotonic() + self._callback(measured_time, self.temp) + return measured_time + self.report_time + + def _chekCRC8(self,data): + for bit in range(0,16): + if (data & 0x8000): + data = (data << 1) ^ HTU21D_CRC8_POLYNOMINAL; + else: + data <<= 1 + data = data >> 8 + return data + + def get_status(self, eventtime): + return { + 'temperature': self.temp, + 'humidity': self.humidity, + } + + +def load_config(config): + # Register sensor + pheater = config.get_printer().lookup_object("heaters") + for stype in HTU21D_DEVICES: + pheater.add_sensor_factory(stype, HTU21D) diff --git a/src/linux/i2c.c b/src/linux/i2c.c index b8e8f253..ac3feb32 100644 --- a/src/linux/i2c.c +++ b/src/linux/i2c.c @@ -91,7 +91,8 @@ void i2c_read(struct i2c_config config, uint8_t reg_len, uint8_t *reg , uint8_t read_len, uint8_t *data) { - i2c_write(config, reg_len, reg); + if(reg_len != 0) + i2c_write(config, reg_len, reg); int ret = read(config.fd, data, read_len); if (ret != read_len) { if (ret < 0)