ESPHome  2024.12.2
qmc5883l.cpp
Go to the documentation of this file.
1 #include "qmc5883l.h"
3 #include "esphome/core/log.h"
4 #include "esphome/core/hal.h"
5 #include <cmath>
6 
7 namespace esphome {
8 namespace qmc5883l {
9 
10 static const char *const TAG = "qmc5883l";
11 static const uint8_t QMC5883L_ADDRESS = 0x0D;
12 
13 static const uint8_t QMC5883L_REGISTER_DATA_X_LSB = 0x00;
14 static const uint8_t QMC5883L_REGISTER_DATA_X_MSB = 0x01;
15 static const uint8_t QMC5883L_REGISTER_DATA_Y_LSB = 0x02;
16 static const uint8_t QMC5883L_REGISTER_DATA_Y_MSB = 0x03;
17 static const uint8_t QMC5883L_REGISTER_DATA_Z_LSB = 0x04;
18 static const uint8_t QMC5883L_REGISTER_DATA_Z_MSB = 0x05;
19 static const uint8_t QMC5883L_REGISTER_STATUS = 0x06;
20 static const uint8_t QMC5883L_REGISTER_TEMPERATURE_LSB = 0x07;
21 static const uint8_t QMC5883L_REGISTER_TEMPERATURE_MSB = 0x08;
22 static const uint8_t QMC5883L_REGISTER_CONTROL_1 = 0x09;
23 static const uint8_t QMC5883L_REGISTER_CONTROL_2 = 0x0A;
24 static const uint8_t QMC5883L_REGISTER_PERIOD = 0x0B;
25 
27  ESP_LOGCONFIG(TAG, "Setting up QMC5883L...");
28  // Soft Reset
29  if (!this->write_byte(QMC5883L_REGISTER_CONTROL_2, 1 << 7)) {
31  this->mark_failed();
32  return;
33  }
34  delay(10);
35 
36  uint8_t control_1 = 0;
37  control_1 |= 0b01 << 0; // MODE (Mode) -> 0b00=standby, 0b01=continuous
38  control_1 |= this->datarate_ << 2;
39  control_1 |= this->range_ << 4;
40  control_1 |= this->oversampling_ << 6;
41  if (!this->write_byte(QMC5883L_REGISTER_CONTROL_1, control_1)) {
43  this->mark_failed();
44  return;
45  }
46 
47  uint8_t control_2 = 0;
48  control_2 |= 0b0 << 7; // SOFT_RST (Soft Reset) -> 0b00=disabled, 0b01=enabled
49  control_2 |= 0b0 << 6; // ROL_PNT (Pointer Roll Over) -> 0b00=disabled, 0b01=enabled
50  control_2 |= 0b0 << 0; // INT_ENB (Interrupt) -> 0b00=disabled, 0b01=enabled
51  if (!this->write_byte(QMC5883L_REGISTER_CONTROL_2, control_2)) {
53  this->mark_failed();
54  return;
55  }
56 
57  uint8_t period = 0x01; // recommended value
58  if (!this->write_byte(QMC5883L_REGISTER_PERIOD, period)) {
60  this->mark_failed();
61  return;
62  }
63 
64  if (this->get_update_interval() < App.get_loop_interval()) {
65  high_freq_.start();
66  }
67 }
69  ESP_LOGCONFIG(TAG, "QMC5883L:");
70  LOG_I2C_DEVICE(this);
71  if (this->error_code_ == COMMUNICATION_FAILED) {
72  ESP_LOGE(TAG, "Communication with QMC5883L failed!");
73  }
74  LOG_UPDATE_INTERVAL(this);
75 
76  LOG_SENSOR(" ", "X Axis", this->x_sensor_);
77  LOG_SENSOR(" ", "Y Axis", this->y_sensor_);
78  LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
79  LOG_SENSOR(" ", "Heading", this->heading_sensor_);
80  LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
81 }
84  i2c::ErrorCode err;
85  uint8_t status = false;
86  // Status byte gets cleared when data is read, so we have to read this first.
87  // If status and two axes are desired, it's possible to save one byte of traffic by enabling
88  // ROL_PNT in setup and reading 7 bytes starting at the status register.
89  // If status and all three axes are desired, using ROL_PNT saves you 3 bytes.
90  // But simply not reading status saves you 4 bytes always and is much simpler.
91  if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) {
92  err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1);
93  if (err != i2c::ERROR_OK) {
94  this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str());
95  return;
96  }
97  }
98 
99  uint16_t raw[3] = {0};
100  // Z must always be requested, otherwise the data registers will remain locked against updates.
101  // Skipping the Y axis if X and Z are needed actually requires an additional byte of comms.
102  // Starting partway through the axes does save you traffic.
103  uint8_t start, dest;
104  if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) {
105  start = QMC5883L_REGISTER_DATA_X_LSB;
106  dest = 0;
107  } else if (this->y_sensor_ != nullptr) {
108  start = QMC5883L_REGISTER_DATA_Y_LSB;
109  dest = 1;
110  } else {
111  start = QMC5883L_REGISTER_DATA_Z_LSB;
112  dest = 2;
113  }
114  err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest);
115  if (err != i2c::ERROR_OK) {
116  this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str());
117  return;
118  }
119 
120  float mg_per_bit;
121  switch (this->range_) {
123  mg_per_bit = 0.0833f;
124  break;
126  mg_per_bit = 0.333f;
127  break;
128  default:
129  mg_per_bit = NAN;
130  }
131 
132  // in µT
133  const float x = int16_t(raw[0]) * mg_per_bit * 0.1f;
134  const float y = int16_t(raw[1]) * mg_per_bit * 0.1f;
135  const float z = int16_t(raw[2]) * mg_per_bit * 0.1f;
136 
137  float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
138 
139  float temp = NAN;
140  if (this->temperature_sensor_ != nullptr) {
141  uint16_t raw_temp;
142  err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp);
143  if (err != i2c::ERROR_OK) {
144  this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str());
145  return;
146  }
147  temp = int16_t(raw_temp) * 0.01f;
148  }
149 
150  ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° temperature=%0.01f°C status=%u", x, y, z, heading,
151  temp, status);
152 
153  if (this->x_sensor_ != nullptr)
154  this->x_sensor_->publish_state(x);
155  if (this->y_sensor_ != nullptr)
156  this->y_sensor_->publish_state(y);
157  if (this->z_sensor_ != nullptr)
158  this->z_sensor_->publish_state(z);
159  if (this->heading_sensor_ != nullptr)
160  this->heading_sensor_->publish_state(heading);
161  if (this->temperature_sensor_ != nullptr)
162  this->temperature_sensor_->publish_state(temp);
163 }
164 
165 i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) {
166  i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2);
167  if (err != i2c::ERROR_OK)
168  return err;
169  for (size_t i = 0; i < len; i++)
170  data[i] = convert_little_endian(data[i]);
171  return err;
172 }
173 
174 } // namespace qmc5883l
175 } // namespace esphome
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
uint8_t raw[35]
Definition: bl0939.h:19
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop=true)
reads an array of bytes from a specific register in the I²C device
Definition: i2c.cpp:10
sensor::Sensor * heading_sensor_
Definition: qmc5883l.h:52
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
uint16_t x
Definition: tt21100.cpp:17
uint16_t y
Definition: tt21100.cpp:18
HighFrequencyLoopRequester high_freq_
Definition: qmc5883l.h:59
enum esphome::qmc5883l::QMC5883LComponent::ErrorCode error_code_
float get_setup_priority() const override
Definition: qmc5883l.cpp:82
void start()
Start running the loop continuously.
Definition: helpers.cpp:670
No error found during execution of method.
Definition: i2c_bus.h:13
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:320
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
Application App
Global storage of Application pointer - only one Application can exist.
i2c::ErrorCode read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len=1)
Definition: qmc5883l.cpp:165
uint32_t get_loop_interval() const
Definition: application.h:232
sensor::Sensor * temperature_sensor_
Definition: qmc5883l.h:53
virtual uint32_t get_update_interval() const
Get the update interval in ms of this sensor.
Definition: component.cpp:228
u_int8_t raw_temp
uint8_t status
Definition: bl0942.h:74
std::string size_t len
Definition: helpers.h:293
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
constexpr14 T convert_little_endian(T val)
Convert a value between host byte order and little endian (least significant byte first) order...
Definition: helpers.h:249
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition: i2c_bus.h:11
QMC5883LOversampling oversampling_
Definition: qmc5883l.h:48
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26