ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
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#include <numbers>
7
9
10ESPHOME_LOG_TAG(TAG, "qmc5883l");
11
12static const uint8_t QMC5883L_ADDRESS = 0x0D;
13
14static const uint8_t QMC5883L_REGISTER_DATA_X_LSB = 0x00;
15static const uint8_t QMC5883L_REGISTER_DATA_X_MSB = 0x01;
16static const uint8_t QMC5883L_REGISTER_DATA_Y_LSB = 0x02;
17static const uint8_t QMC5883L_REGISTER_DATA_Y_MSB = 0x03;
18static const uint8_t QMC5883L_REGISTER_DATA_Z_LSB = 0x04;
19static const uint8_t QMC5883L_REGISTER_DATA_Z_MSB = 0x05;
20static const uint8_t QMC5883L_REGISTER_STATUS = 0x06;
21static const uint8_t QMC5883L_REGISTER_TEMPERATURE_LSB = 0x07;
22static const uint8_t QMC5883L_REGISTER_TEMPERATURE_MSB = 0x08;
23static const uint8_t QMC5883L_REGISTER_CONTROL_1 = 0x09;
24static const uint8_t QMC5883L_REGISTER_CONTROL_2 = 0x0A;
25static const uint8_t QMC5883L_REGISTER_PERIOD = 0x0B;
26
28
30 // Soft Reset
31 if (!this->write_byte(QMC5883L_REGISTER_CONTROL_2, 1 << 7)) {
32 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
33 return;
34 }
35 delay(10);
36
37 if (this->drdy_pin_) {
38 this->drdy_pin_->setup();
39 if (this->drdy_pin_->is_internal()) {
40 static_cast<InternalGPIOPin *>(this->drdy_pin_)
42 this->drdy_use_isr_ = true;
43 this->stop_poller();
44 }
45 }
46
47 uint8_t control_1 = 0;
48 control_1 |= 0b01 << 0; // MODE (Mode) -> 0b00=standby, 0b01=continuous
49 control_1 |= this->datarate_ << 2;
50 control_1 |= this->range_ << 4;
51 control_1 |= this->oversampling_ << 6;
52 if (!this->write_byte(QMC5883L_REGISTER_CONTROL_1, control_1)) {
53 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
54 return;
55 }
56
57 uint8_t control_2 = 0;
58 control_2 |= 0b0 << 7; // SOFT_RST (Soft Reset) -> 0b00=disabled, 0b01=enabled
59 control_2 |= 0b0 << 6; // ROL_PNT (Pointer Roll Over) -> 0b00=disabled, 0b01=enabled
60 control_2 |= 0b0 << 0; // INT_ENB (Interrupt) -> 0b00=disabled, 0b01=enabled
61 if (!this->write_byte(QMC5883L_REGISTER_CONTROL_2, control_2)) {
62 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
63 return;
64 }
65
66 uint8_t period = 0x01; // recommended value
67 if (!this->write_byte(QMC5883L_REGISTER_PERIOD, period)) {
68 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
69 return;
70 }
71
72 if (!this->drdy_use_isr_ && this->get_update_interval() < App.get_loop_interval()) {
73 this->high_freq_.start();
74 }
75}
76
78 ESP_LOGCONFIG(TAG, "QMC5883L:");
79 LOG_I2C_DEVICE(this);
80 LOG_UPDATE_INTERVAL(this);
81
82 LOG_SENSOR(" ", "X Axis", this->x_sensor_);
83 LOG_SENSOR(" ", "Y Axis", this->y_sensor_);
84 LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
85 LOG_SENSOR(" ", "Heading", this->heading_sensor_);
86 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
87 LOG_PIN(" DRDY Pin: ", this->drdy_pin_);
88 if (this->drdy_pin_ != nullptr) {
89 ESP_LOGCONFIG(TAG, " DRDY mode: %s",
90 this->drdy_use_isr_ ? LOG_STR_LITERAL("interrupt") : LOG_STR_LITERAL("polling"));
91 }
92}
93
95 // If DRDY is on an external expander we keep the polling path and early-return
96 // if data is not ready yet. Internal DRDY pins take the ISR path via loop().
97 if (this->drdy_pin_ && !this->drdy_pin_->digital_read()) {
98 return;
99 }
100 this->read_sensor_();
101}
102
104 this->disable_loop();
105 if (!this->drdy_use_isr_ || !this->drdy_pin_->digital_read()) {
106 return;
107 }
108 this->read_sensor_();
109}
110
112 i2c::ErrorCode err;
113 uint8_t status = false;
114
115 // Status byte gets cleared when data is read, so we have to read this first.
116 // If status and two axes are desired, it's possible to save one byte of traffic by enabling
117 // ROL_PNT in setup and reading 7 bytes starting at the status register.
118 // If status and all three axes are desired, using ROL_PNT saves you 3 bytes.
119 // But simply not reading status saves you 4 bytes always and is much simpler.
120 if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
121 err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1);
122 if (err != i2c::ERROR_OK) {
123 char buf[32];
124 snprintf(buf, sizeof(buf), "status read failed (%d)", err);
125 this->status_set_warning(buf);
126 return;
127 }
128 }
129
130 uint16_t raw[3] = {0};
131 // Z must always be requested, otherwise the data registers will remain locked against updates.
132 // Skipping the Y axis if X and Z are needed actually requires an additional byte of comms.
133 // Starting partway through the axes does save you traffic.
134 uint8_t start, dest;
135 if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) {
136 start = QMC5883L_REGISTER_DATA_X_LSB;
137 dest = 0;
138 } else if (this->y_sensor_ != nullptr) {
139 start = QMC5883L_REGISTER_DATA_Y_LSB;
140 dest = 1;
141 } else {
142 start = QMC5883L_REGISTER_DATA_Z_LSB;
143 dest = 2;
144 }
145 err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest);
146 if (err != i2c::ERROR_OK) {
147 char buf[32];
148 snprintf(buf, sizeof(buf), "mag read failed (%d)", err);
149 this->status_set_warning(buf);
150 return;
151 }
152
153 float mg_per_bit;
154 switch (this->range_) {
156 mg_per_bit = 0.0833f;
157 break;
159 mg_per_bit = 0.333f;
160 break;
161 default:
162 mg_per_bit = NAN;
163 }
164
165 // in µT
166 const float x = int16_t(raw[0]) * mg_per_bit * 0.1f;
167 const float y = int16_t(raw[1]) * mg_per_bit * 0.1f;
168 const float z = int16_t(raw[2]) * mg_per_bit * 0.1f;
169
170 float heading = atan2f(0.0f - x, y) * 180.0f / std::numbers::pi_v<float>;
171
172 float temp = NAN;
173 if (this->temperature_sensor_ != nullptr) {
174 uint16_t raw_temp;
175 err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp);
176 if (err != i2c::ERROR_OK) {
177 char buf[32];
178 snprintf(buf, sizeof(buf), "temp read failed (%d)", err);
179 this->status_set_warning(buf);
180 return;
181 }
182 temp = int16_t(raw_temp) * 0.01f;
183 }
184
185 ESP_LOGV(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,
186 temp, status);
187
188 if (this->x_sensor_ != nullptr)
189 this->x_sensor_->publish_state(x);
190 if (this->y_sensor_ != nullptr)
191 this->y_sensor_->publish_state(y);
192 if (this->z_sensor_ != nullptr)
193 this->z_sensor_->publish_state(z);
194 if (this->heading_sensor_ != nullptr)
195 this->heading_sensor_->publish_state(heading);
196 if (this->temperature_sensor_ != nullptr)
198}
199
200i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) {
201 i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2);
202 if (err != i2c::ERROR_OK)
203 return err;
204 for (size_t i = 0; i < len; i++)
205 data[i] = convert_little_endian(data[i]);
206 return err;
207}
208
209} // namespace esphome::qmc5883l
uint8_t raw[35]
Definition bl0939.h:0
uint8_t status
Definition bl0942.h:8
uint32_t get_loop_interval() const
void mark_failed()
Mark this component as failed.
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void disable_loop()
Disable this component's loop.
virtual void setup()=0
virtual bool is_internal()
Only the platform pin class behind the InternalGPIOPin alias may return true: callers static_cast to ...
Definition gpio_pin.h:87
virtual bool digital_read()=0
void start()
Start running the loop continuously.
Definition helpers.cpp:835
virtual uint32_t get_update_interval() const
Get the update interval in ms of this sensor.
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:312
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len)
reads an array of bytes from a specific register in the I²C device
Definition i2c.cpp:28
sensor::Sensor * temperature_sensor_
Definition qmc5883l.h:57
i2c::ErrorCode read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len=1)
Definition qmc5883l.cpp:200
QMC5883LOversampling oversampling_
Definition qmc5883l.h:52
static void gpio_intr(QMC5883LComponent *arg)
Definition qmc5883l.cpp:27
HighFrequencyLoopRequester high_freq_
Definition qmc5883l.h:61
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
u_int8_t raw_temp
bool z
Definition msa3xx.h:1
@ INTERRUPT_RISING_EDGE
Definition gpio_pin.h:52
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition i2c_bus.h:13
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
ESPHOME_LOG_TAG(TAG, "qmc5883l")
constexpr T convert_little_endian(T val)
Convert a value between host byte order and little endian (least significant byte first) order.
Definition helpers.h:1107
const void size_t len
Definition hal.h:68
void HOT delay(uint32_t ms)
Definition hal.cpp:85
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6