ESPHome  2024.12.2
emc2101.cpp
Go to the documentation of this file.
1 // Implementation based on:
2 // - Adafruit_EMC2101: https://github.com/adafruit/Adafruit_EMC2101
3 // - Official Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/2101.pdf
4 
5 #include "esphome/core/log.h"
6 #include "emc2101.h"
7 
8 namespace esphome {
9 namespace emc2101 {
10 
11 static const char *const TAG = "EMC2101";
12 
13 static const uint8_t EMC2101_CHIP_ID = 0x16; // EMC2101 default device id from part id
14 static const uint8_t EMC2101_ALT_CHIP_ID = 0x28; // EMC2101 alternate device id from part id
15 
16 // EMC2101 registers from the datasheet. We only define what we use.
17 static const uint8_t EMC2101_REGISTER_INTERNAL_TEMP = 0x00; // The internal temperature register
18 static const uint8_t EMC2101_REGISTER_EXTERNAL_TEMP_MSB = 0x01; // high byte for the external temperature reading
19 static const uint8_t EMC2101_REGISTER_DAC_CONV_RATE = 0x04; // DAC convesion rate config
20 static const uint8_t EMC2101_REGISTER_EXTERNAL_TEMP_LSB = 0x10; // low byte for the external temperature reading
21 static const uint8_t EMC2101_REGISTER_CONFIG = 0x03; // configuration register
22 static const uint8_t EMC2101_REGISTER_TACH_LSB = 0x46; // Tach RPM data low byte
23 static const uint8_t EMC2101_REGISTER_TACH_MSB = 0x47; // Tach RPM data high byte
24 static const uint8_t EMC2101_REGISTER_FAN_CONFIG = 0x4A; // General fan config register
25 static const uint8_t EMC2101_REGISTER_FAN_SETTING = 0x4C; // Fan speed for non-LUT settings
26 static const uint8_t EMC2101_REGISTER_PWM_FREQ = 0x4D; // PWM frequency setting
27 static const uint8_t EMC2101_REGISTER_PWM_DIV = 0x4E; // PWM frequency divisor
28 static const uint8_t EMC2101_REGISTER_WHOAMI = 0xFD; // Chip ID register
29 
30 // EMC2101 configuration bits from the datasheet. We only define what we use.
31 
32 // Determines the funcionallity of the ALERT/TACH pin.
33 // 0 (default): The ALERT/TECH pin will function as an open drain, active low interrupt.
34 // 1: The ALERT/TECH pin will function as a high impedance TACH input. This may require an
35 // external pull-up resistor to set the proper signaling levels.
36 static const uint8_t EMC2101_ALT_TCH_BIT = 1 << 2;
37 
38 // Determines the FAN output mode.
39 // 0 (default): PWM output enabled at FAN pin.
40 // 1: DAC output enabled at FAN ping.
41 static const uint8_t EMC2101_DAC_BIT = 1 << 4;
42 
43 // Overrides the CLK_SEL bit and uses the Frequency Divide Register to determine
44 // the base PWM frequency. It is recommended that this bit be set for maximum PWM resolution.
45 // 0 (default): The base clock frequency for the PWM is determined by the CLK_SEL bit.
46 // 1 (recommended): The base clock that is used to determine the PWM frequency is set by the
47 // Frequency Divide Register
48 static const uint8_t EMC2101_CLK_OVR_BIT = 1 << 2;
49 
50 // Sets the polarity of the Fan output driver.
51 // 0 (default): The polarity of the Fan output driver is non-inverted. A '00h' setting will
52 // correspond to a 0% duty cycle or a minimum DAC output voltage.
53 // 1: The polarity of the Fan output driver is inverted. A '00h' setting will correspond to a
54 // 100% duty cycle or a maximum DAC output voltage.
55 static const uint8_t EMC2101_POLARITY_BIT = 1 << 4;
56 
58 
60  ESP_LOGCONFIG(TAG, "Setting up Emc2101 sensor...");
61 
62  // make sure we're talking to the right chip
63  uint8_t chip_id = reg(EMC2101_REGISTER_WHOAMI).get();
64  if ((chip_id != EMC2101_CHIP_ID) && (chip_id != EMC2101_ALT_CHIP_ID)) {
65  ESP_LOGE(TAG, "Wrong chip ID %02X", chip_id);
66  this->mark_failed();
67  return;
68  }
69 
70  // Configure EMC2101
71  i2c::I2CRegister config = reg(EMC2101_REGISTER_CONFIG);
72  config |= EMC2101_ALT_TCH_BIT;
73  if (this->dac_mode_) {
74  config |= EMC2101_DAC_BIT;
75  }
76  if (this->inverted_) {
77  config |= EMC2101_POLARITY_BIT;
78  }
79 
80  if (this->dac_mode_) { // DAC mode configurations
81  // set DAC conversion rate
82  reg(EMC2101_REGISTER_DAC_CONV_RATE) = this->dac_conversion_rate_;
83  } else { // PWM mode configurations
84  // set PWM divider
85  reg(EMC2101_REGISTER_FAN_CONFIG) |= EMC2101_CLK_OVR_BIT;
86  reg(EMC2101_REGISTER_PWM_DIV) = this->pwm_divider_;
87 
88  // set PWM resolution
89  reg(EMC2101_REGISTER_PWM_FREQ) = this->pwm_resolution_;
90  }
91 }
92 
94  ESP_LOGCONFIG(TAG, "Emc2101 component:");
95  LOG_I2C_DEVICE(this);
96  if (this->is_failed()) {
97  ESP_LOGE(TAG, "Communication with EMC2101 failed!");
98  }
99  ESP_LOGCONFIG(TAG, " Mode: %s", this->dac_mode_ ? "DAC" : "PWM");
100  if (this->dac_mode_) {
101  ESP_LOGCONFIG(TAG, " DAC Conversion Rate: %X", this->dac_conversion_rate_);
102  } else {
103  ESP_LOGCONFIG(TAG, " PWM Resolution: %02X", this->pwm_resolution_);
104  ESP_LOGCONFIG(TAG, " PWM Divider: %02X", this->pwm_divider_);
105  }
106  ESP_LOGCONFIG(TAG, " Inverted: %s", YESNO(this->inverted_));
107 }
108 
110  uint8_t duty_cycle = remap(value, 0.0f, 1.0f, (uint8_t) 0, this->max_output_value_);
111  ESP_LOGD(TAG, "Setting duty fan setting to %02X", duty_cycle);
112  if (!this->write_byte(EMC2101_REGISTER_FAN_SETTING, duty_cycle)) {
113  ESP_LOGE(TAG, "Communication with EMC2101 failed!");
114  this->status_set_warning();
115  return;
116  }
117 }
118 
120  uint8_t duty_cycle;
121  if (!this->read_byte(EMC2101_REGISTER_FAN_SETTING, &duty_cycle)) {
122  ESP_LOGE(TAG, "Communication with EMC2101 failed!");
123  this->status_set_warning();
124  return NAN;
125  }
126  return remap(duty_cycle, (uint8_t) 0, this->max_output_value_, 0.0f, 1.0f);
127 }
128 
130  uint8_t temperature;
131  if (!this->read_byte(EMC2101_REGISTER_INTERNAL_TEMP, &temperature)) {
132  ESP_LOGE(TAG, "Communication with EMC2101 failed!");
133  this->status_set_warning();
134  return NAN;
135  }
136  return temperature;
137 }
138 
140  // Read **MSB** first to match 'Data Read Interlock' behavior from 6.1 of datasheet
141  uint8_t lsb, msb;
142  if (!this->read_byte(EMC2101_REGISTER_EXTERNAL_TEMP_MSB, &msb) ||
143  !this->read_byte(EMC2101_REGISTER_EXTERNAL_TEMP_LSB, &lsb)) {
144  ESP_LOGE(TAG, "Communication with EMC2101 failed!");
145  this->status_set_warning();
146  return NAN;
147  }
148 
149  // join msb and lsb (5 least significant bits are not used)
150  uint16_t raw = (msb << 8 | lsb) >> 5;
151  return raw * 0.125;
152 }
153 
155  // Read **LSB** first to match 'Data Read Interlock' behavior from 6.1 of datasheet
156  uint8_t lsb, msb;
157  if (!this->read_byte(EMC2101_REGISTER_TACH_LSB, &lsb) || !this->read_byte(EMC2101_REGISTER_TACH_MSB, &msb)) {
158  ESP_LOGE(TAG, "Communication with EMC2101 failed!");
159  this->status_set_warning();
160  return NAN;
161  }
162 
163  // calculate RPMs
164  uint16_t tach = msb << 8 | lsb;
165  return tach == 0xFFFF ? 0.0f : 5400000.0f / tach;
166 }
167 
168 } // namespace emc2101
169 } // namespace esphome
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition: i2c.h:235
uint8_t raw[35]
Definition: bl0939.h:19
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition: i2c.h:149
uint8_t get() const
returns the register value
Definition: i2c.cpp:75
bool is_failed() const
Definition: component.cpp:143
void dump_config() override
Used by ESPHome framework.
Definition: emc2101.cpp:93
Emc2101DACConversionRate dac_conversion_rate_
Definition: emc2101.h:111
float get_internal_temperature()
Gets the internal temperature sensor reading.
Definition: emc2101.cpp:129
void setup() override
Used by ESPHome framework.
Definition: emc2101.cpp:59
float get_duty_cycle()
Gets the Fan output duty cycle.
Definition: emc2101.cpp:119
uint16_t temperature
Definition: sun_gtil2.cpp:26
float get_speed()
Gets the tachometer speed sensor reading.
Definition: emc2101.cpp:154
T remap(U value, U min, U max, T min_out, T max_out)
Remap value from the range (min, max) to (min_out, max_out).
Definition: helpers.h:154
const float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition: component.cpp:18
This class is used to create I2CRegister objects that act as proxies to read/write internal registers...
Definition: i2c.h:33
float get_external_temperature()
Gets the external temperature sensor reading.
Definition: emc2101.cpp:139
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition: i2c.h:262
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
void set_duty_cycle(float value)
Sets the Fan output duty cycle.
Definition: emc2101.cpp:109
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
float get_setup_priority() const override
Used by ESPHome framework.
Definition: emc2101.cpp:57