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