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