1
+ /* !
2
+ * @file WipperSnapper_I2C_Driver_MLX90632.h
3
+ *
4
+ * Device driver for a Melexis MLX90632 thermal FIR sensor.
5
+ *
6
+ * Adafruit invests time and resources providing this open source code,
7
+ * please support Adafruit and open-source hardware by purchasing
8
+ * products from Adafruit!
9
+ *
10
+ * Copyright (c) Tyeth Gundry 2025 for Adafruit Industries.
11
+ *
12
+ * MIT license, all text here must be included in any redistribution.
13
+ *
14
+ */
15
+
16
+ #ifndef WipperSnapper_I2C_Driver_MLX90632_H
17
+ #define WipperSnapper_I2C_Driver_MLX90632_H
18
+
19
+ #include < Adafruit_MLX90632.h>
20
+
21
+ #include " WipperSnapper_I2C_Driver.h"
22
+
23
+ /* *************************************************************************/
24
+ /* !
25
+ @brief Sensor driver for the Melexis MLX90632 temperature sensor.
26
+ */
27
+ /* *************************************************************************/
28
+ class WipperSnapper_I2C_Driver_MLX90632 : public WipperSnapper_I2C_Driver {
29
+ public:
30
+ /* ******************************************************************************/
31
+ /* !
32
+ @brief Constructor for an MLX90632 sensor.
33
+ @param i2c
34
+ The I2C interface.
35
+ @param sensorAddress
36
+ 7-bit device address.
37
+ */
38
+ /* ******************************************************************************/
39
+ WipperSnapper_I2C_Driver_MLX90632 (TwoWire *i2c, uint16_t sensorAddress)
40
+ : WipperSnapper_I2C_Driver(i2c, sensorAddress) {
41
+ _i2c = i2c;
42
+ _sensorAddress = sensorAddress;
43
+ _deviceTemp = NAN;
44
+ _objectTemp = NAN;
45
+ _lastRead = 0 ;
46
+ }
47
+
48
+ /* ******************************************************************************/
49
+ /* !
50
+ @brief Destructor for an MLX90632 sensor.
51
+ */
52
+ /* ******************************************************************************/
53
+ ~WipperSnapper_I2C_Driver_MLX90632 () { delete _mlx90632; }
54
+
55
+ /* ******************************************************************************/
56
+ /* !
57
+ @brief Initializes the MLX90632 sensor and begins I2C.
58
+ @returns True if initialized successfully, False otherwise.
59
+ */
60
+ /* ******************************************************************************/
61
+ bool begin () {
62
+ _mlx90632 = new Adafruit_MLX90632 ();
63
+ // attempt to initialize MLX90632
64
+ if (!_mlx90632->begin (_sensorAddress, _i2c))
65
+ return false ;
66
+
67
+ return ConfigureAndPrintSensorInfo ();
68
+ }
69
+
70
+ /* ******************************************************************************/
71
+ /* !
72
+ @brief Configures the MLX90632 sensor and prints its information.
73
+ @returns True if configuration fetching and setting were successful.
74
+ */
75
+ /* ******************************************************************************/
76
+ bool ConfigureAndPrintSensorInfo () {
77
+ // Reset the device
78
+ if (!_mlx90632->reset ()) {
79
+ WS_PRINTER.println (F (" Device reset failed" ));
80
+ while (1 ) { delay (10 ); }
81
+ }
82
+ WS_PRINTER.println (F (" Device reset: SUCCESS" ));
83
+
84
+ uint64_t productID = _mlx90632->getProductID ();
85
+ WS_PRINTER.print (F (" Product ID: 0x" ));
86
+ WS_PRINTER.print ((uint32_t )(productID >> 32 ), HEX);
87
+ WS_PRINTER.println ((uint32_t )(productID & 0xFFFFFFFF ), HEX);
88
+
89
+ uint16_t productCode = _mlx90632->getProductCode ();
90
+ WS_PRINTER.print (F (" Product Code: 0x" ));
91
+ WS_PRINTER.println (productCode, HEX);
92
+
93
+ uint16_t eepromVersion = _mlx90632->getEEPROMVersion ();
94
+ WS_PRINTER.print (F (" EEPROM Version: 0x" ));
95
+ WS_PRINTER.println (eepromVersion, HEX);
96
+
97
+ // Decode product code bits
98
+ uint8_t fov = (productCode >> 8 ) & 0x3 ;
99
+ uint8_t package = (productCode >> 5 ) & 0x7 ;
100
+ uint8_t accuracy = productCode & 0x1F ;
101
+
102
+ WS_PRINTER.print (F (" FOV: " ));
103
+ WS_PRINTER.println (fov == 0 ? F (" 50°" ) : F (" Unknown" ));
104
+
105
+ WS_PRINTER.print (F (" Package: " ));
106
+ WS_PRINTER.println (package == 1 ? F (" SFN 3x3" ) : F (" Unknown" ));
107
+
108
+ WS_PRINTER.print (F (" Accuracy: " ));
109
+ if (accuracy == 1 ) {
110
+ WS_PRINTER.println (F (" Medical" ));
111
+ } else if (accuracy == 2 ) {
112
+ WS_PRINTER.println (F (" Standard" ));
113
+ } else {
114
+ WS_PRINTER.println (F (" Unknown" ));
115
+ }
116
+
117
+ // Set and get mode - choose one:
118
+ WS_PRINTER.println (F (" \n --- Mode Settings ---" ));
119
+ if (!_mlx90632->setMode (MLX90632_MODE_CONTINUOUS)) {
120
+ // if (!_mlx90632->setMode(MLX90632_MODE_STEP)) { // Uncomment for step mode testing
121
+ // if (!_mlx90632->setMode(MLX90632_MODE_SLEEPING_STEP)) { // Uncomment for sleeping step mode testing
122
+ WS_PRINTER.println (F (" Failed to set mode" ));
123
+ while (1 ) { delay (10 ); }
124
+ }
125
+
126
+ // TODO: use Step mode?
127
+ mlx90632_mode_t currentMode = _mlx90632->getMode ();
128
+ WS_PRINTER.print (F (" Current mode: " ));
129
+ switch (currentMode) {
130
+ case MLX90632_MODE_HALT:
131
+ WS_PRINTER.println (F (" Halt" ));
132
+ break ;
133
+ case MLX90632_MODE_SLEEPING_STEP:
134
+ WS_PRINTER.println (F (" Sleeping Step" ));
135
+ break ;
136
+ case MLX90632_MODE_STEP:
137
+ WS_PRINTER.println (F (" Step" ));
138
+ break ;
139
+ case MLX90632_MODE_CONTINUOUS:
140
+ WS_PRINTER.println (F (" Continuous" ));
141
+ break ;
142
+ default :
143
+ WS_PRINTER.println (F (" Unknown" ));
144
+ }
145
+
146
+ // set accuracy mode based on medical if detected
147
+ if (accuracy == 1 ) {
148
+ // Set and get measurement select (medical)
149
+ WS_PRINTER.println (F (" \n --- Measurement Select Settings ---" ));
150
+ if (!_mlx90632->setMeasurementSelect (MLX90632_MEAS_MEDICAL)) {
151
+ WS_PRINTER.println (F (" Failed to set measurement select to Medical" ));
152
+ while (1 ) { delay (10 ); }
153
+ }
154
+
155
+ mlx90632_meas_select_t currentMeasSelect = _mlx90632->getMeasurementSelect ();
156
+ WS_PRINTER.print (F (" Current measurement select: " ));
157
+ switch (currentMeasSelect) {
158
+ case MLX90632_MEAS_MEDICAL:
159
+ WS_PRINTER.println (F (" Medical" ));
160
+ break ;
161
+ case MLX90632_MEAS_EXTENDED_RANGE:
162
+ WS_PRINTER.println (F (" Extended Range" ));
163
+ break ;
164
+ default :
165
+ WS_PRINTER.println (F (" Unknown" ));
166
+ }
167
+ }
168
+
169
+ // Set and get refresh rate (default to 2Hz)
170
+ WS_PRINTER.println (F (" \n --- Refresh Rate Settings ---" ));
171
+ if (!_mlx90632->setRefreshRate (MLX90632_REFRESH_2HZ)) {
172
+ WS_PRINTER.println (F (" Failed to set refresh rate to 2Hz" ));
173
+ while (1 ) { delay (10 ); }
174
+ }
175
+
176
+ mlx90632_refresh_rate_t currentRefreshRate = _mlx90632->getRefreshRate ();
177
+ WS_PRINTER.print (F (" Current refresh rate: " ));
178
+ switch (currentRefreshRate) {
179
+ case MLX90632_REFRESH_0_5HZ:
180
+ WS_PRINTER.println (F (" 0.5 Hz" ));
181
+ break ;
182
+ case MLX90632_REFRESH_1HZ:
183
+ WS_PRINTER.println (F (" 1 Hz" ));
184
+ break ;
185
+ case MLX90632_REFRESH_2HZ:
186
+ WS_PRINTER.println (F (" 2 Hz" ));
187
+ break ;
188
+ case MLX90632_REFRESH_4HZ:
189
+ WS_PRINTER.println (F (" 4 Hz" ));
190
+ break ;
191
+ case MLX90632_REFRESH_8HZ:
192
+ WS_PRINTER.println (F (" 8 Hz" ));
193
+ break ;
194
+ case MLX90632_REFRESH_16HZ:
195
+ WS_PRINTER.println (F (" 16 Hz" ));
196
+ break ;
197
+ case MLX90632_REFRESH_32HZ:
198
+ WS_PRINTER.println (F (" 32 Hz" ));
199
+ break ;
200
+ case MLX90632_REFRESH_64HZ:
201
+ WS_PRINTER.println (F (" 64 Hz" ));
202
+ break ;
203
+ default :
204
+ WS_PRINTER.println (F (" Unknown" ));
205
+ }
206
+
207
+ // Clear new data flag before starting continuous measurements
208
+ WS_PRINTER.println (F (" \\ n--- Starting Continuous Measurements ---" ));
209
+ if (!_mlx90632->resetNewData ()) {
210
+ WS_PRINTER.println (F (" Failed to reset new data flag" ));
211
+ while (1 ) { delay (10 ); }
212
+ }
213
+ return true ;
214
+ }
215
+
216
+ /* ******************************************************************************/
217
+ /* !
218
+ @brief Checks if sensor was read within last 1s, or is the first read.
219
+ @returns True if the sensor was recently read, False otherwise.
220
+ */
221
+ /* ******************************************************************************/
222
+ bool HasBeenReadInLast200ms () {
223
+ return _lastRead != 0 && millis () - _lastRead < 200 ;
224
+ }
225
+
226
+ /* ******************************************************************************/
227
+ /* !
228
+ @brief Reads the sensor.
229
+ @returns True if the sensor was read successfully, False otherwise.
230
+ */
231
+ /* ******************************************************************************/
232
+ bool ReadSensorData () {
233
+ bool result=false ;
234
+
235
+ // Only check new data flag - much more efficient for continuous mode
236
+ if (_mlx90632->isNewData ()) {
237
+ WS_PRINTER.print (F (" New Data Available - Cycle Position: " ));
238
+ WS_PRINTER.println (_mlx90632->readCyclePosition ());
239
+
240
+ // Read ambient temperature
241
+ _deviceTemp = _mlx90632->getAmbientTemperature ();
242
+ WS_PRINTER.print (F (" Ambient Temperature: " ));
243
+ WS_PRINTER.print (_deviceTemp, 4 );
244
+ WS_PRINTER.println (F (" °C" ));
245
+
246
+ // Read object temperature
247
+ _objectTemp = _mlx90632->getObjectTemperature ();
248
+ WS_PRINTER.print (F (" Object Temperature: " ));
249
+ if (isnan (_objectTemp)) {
250
+ WS_PRINTER.println (F (" NaN (invalid cycle position)" ));
251
+ } else {
252
+ WS_PRINTER.print (_objectTemp, 4 );
253
+ WS_PRINTER.println (F (" °C" ));
254
+ }
255
+ result=true ;
256
+ // Reset new data flag after reading
257
+ if (!_mlx90632->resetNewData ()) {
258
+ WS_PRINTER.println (F (" Failed to reset new data flag" ));
259
+ }
260
+
261
+ WS_PRINTER.println (); // Add blank line between readings
262
+ } else {
263
+ WS_PRINTER.println (F (" No new data available, skipping read" ));
264
+
265
+ }
266
+
267
+ // Check if we need to trigger a new measurement for step modes
268
+ mlx90632_mode_t currentMode = _mlx90632->getMode ();
269
+ if (currentMode == MLX90632_MODE_STEP || currentMode == MLX90632_MODE_SLEEPING_STEP) {
270
+ // Trigger single measurement (SOC bit) for step modes
271
+ if (!_mlx90632->startSingleMeasurement ()) {
272
+ WS_PRINTER.println (F (" Failed to start single measurement" ));
273
+ }
274
+ }
275
+
276
+ _lastRead = millis ();
277
+ return result;
278
+ }
279
+
280
+ /* ******************************************************************************/
281
+ /* !
282
+ @brief Gets the MLX90632's current temperature.
283
+ @param tempEvent
284
+ Pointer to an Adafruit_Sensor event.
285
+ @returns True if the temperature was obtained successfully, False
286
+ otherwise.
287
+ */
288
+ /* ******************************************************************************/
289
+ bool getEventAmbientTemp (sensors_event_t *tempEvent) {
290
+ if (ReadSensorData () && _deviceTemp != NAN) {
291
+ // TODO: check max/min or error values in datasheet
292
+ // if (_deviceTemp < -40 || _deviceTemp > 125) {
293
+ // WS_PRINTER.println(F("Invalid ambient temperature"));
294
+ // return false;
295
+ // }
296
+ // if the sensor was read recently, return the cached temperature
297
+ tempEvent->temperature = _deviceTemp;
298
+ return true ;
299
+ }
300
+ return false ; // sensor not read recently, return false
301
+ }
302
+
303
+ /* ******************************************************************************/
304
+ /* !
305
+ @brief Gets the MLX90632's object temperature.
306
+ @param tempEvent
307
+ Pointer to an Adafruit_Sensor event.
308
+ @returns True if the temperature was obtained successfully, False
309
+ otherwise.
310
+ */
311
+ /* ******************************************************************************/
312
+ bool getEventObjectTemp (sensors_event_t *tempEvent) {
313
+ if (ReadSensorData () && _objectTemp != NAN) {
314
+ // if the sensor was read recently, return the cached temperature
315
+ tempEvent->temperature = _objectTemp;
316
+ return true ;
317
+ }
318
+ return false ; // sensor not read recently, return false
319
+ }
320
+
321
+ protected:
322
+ double _deviceTemp; // /< Device temperature in Celsius
323
+ double _objectTemp; // /< Object temperature in Celsius
324
+ uint32_t _lastRead; // /< Last time the sensor was read in milliseconds
325
+ Adafruit_MLX90632 *_mlx90632 = nullptr ; // /< MLX90632 object
326
+ };
327
+
328
+ #endif // WipperSnapper_I2C_Driver_MLX90632
0 commit comments