Arduino and Multiple MLX90614 Sensors Take Two ############################################## :title: Arduino and Multiple MLX90614 Sensors Take Two :date: 2017-09-02 :category: Projects :tags: arduino, electronics :slug: arduino-and-multiple-mlx90614-sensors-take-two :related_posts: cloud-detection-part-one,cloud-detection-part-two,arduino-and-multiple-mlx90614-sensors-take-two :author: Chris Ramsay :status: published :language: en :show_source: True .. contents:: Multiple MLX90614 Sensors Take Two ---------------------------------- .. PELICAN_BEGIN_SUMMARY Over two years ago I wrote `this article`_ about giving an MLX90614 a new slave address, different from the default they are all set with from the factory (`0x5A`), in order to enable having more than one individually addressable device on the bus. As before I am still using two of these MLX90614 sensors in my `cloud detection`_ project and have changed slave addresses on these devices many times before with very few problems. Why am I writing a new post about this? Well I have had quite a few questions on my original article and I decided to hopefully make a few things a little clearer with some updated code examples thrown into the mix. What are we going to do in this article? Focus on the following: 1. `Take a quick peek at the MLX90614 again`_ 2. `Look at some updated code for changing slave addresses`_ 3. `See the range of supported addresses`_ 4. `Look at actually running the code`_ .. PELICAN_END_SUMMARY .. _`Take a quick peek at the MLX90614 again`: The MLX90614 ~~~~~~~~~~~~ Let's take a quick look at the sensor itself; once again, note the reference image of a pinout diagram of the device from the `Melexis MLX90614 Datasheet`_: .. image:: https://farm8.staticflickr.com/7585/17189577425_ee4a026197.jpg :width: 500 :height: 267 :scale: 100 :alt: MLX90614 Pinout Bottom *MLX90614 Pinout - courtesy of Melexis NV* .. _`Look at some updated code for changing slave addresses`: Required Code for Changing Slave Address ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Let's take a look at the improved code. Code layout +++++++++++ I've gone for a slightly different layout with the Arduino code in the hope of making the main functionality simpler. The `ReadTemp` function has been moved into a separate file (`read_temp.ino`) alongside the main `remap_mlx.ino` file. .. code-block:: bash . ├──README.md └──remap_mlx ├──README.md ├──remap_mlx.ino └──read_temp.ino remap_mlx.ino +++++++++++++ What have I done to "improve" the code? Not much really: I've mostly concentrated on making the serial console output a little clearer so that users should have more of an idea what is going on. Let's break the first 30 lines down a little - those are the most interesting part; they run the show procedure-wise. I start with including the `i2cmaster` library and setting a slave address, that is, the address we want the MLX90614 to finish up with. The `setup()` function runs only once, as always happens in Arduino land; Serial communications are set up along with a message. i2c is initiated along with a bit of `manipulation of the port registers`_ and then a five second sleep. Next, I read the current slave address of the MLX90614 using the universal address (0) - this returns three pieces of data, the first of which is the slave address: .. code-block:: text > [ReadAddr] Reading address Using MLX univeral address, Data: 2B, 0, FF The above example is with a slave address of `2B`. Once the current address is read out, I start the process of setting a new slave address (`NewMLXAddr`). Contents of `remap_mlx.ino`: .. code-block:: c :linenos: table // remap_mlx90614.ino #include "i2cmaster.h" // New slave address (default is 0x5A) byte NewMLXAddr = 0x2B; void setup(){ Serial.begin(9600); Serial.println("Setup..."); // Initialise some stuff i2c_init(); PORTC = (1 << PORTC4) | (1 << PORTC5); delay(5000); // Read on universal address (0) ReadAddr(0); // Change to new address NewMLXAddr ChangeAddr(NewMLXAddr, 0x00); // Read on universal address (0) ReadAddr(0); Serial.println("**---READY---**"); // Signal user to cycle power Serial.println("> [setup] Cycle power NOW to store new address - you have 10 seconds"); delay(10000); // Read on universal address (0) ReadAddr(0); Serial.println(" Warning, next ReadTemp() may fail if address has not been set."); ReadTemp(NewMLXAddr); Serial.println("**---DONE---**"); } /** * Read temperature from MLX at new address once setup() is done. * */ void loop(){ delay(5000); ReadTemp(NewMLXAddr); } /** * Changes the address of the MLX to NewAddr1. * */ word ChangeAddr(byte NewAddr1, byte NewAddr2) { Serial.print("> [ChangeAddr] Will change address to: "); Serial.println(NewAddr1, HEX); i2c_start_wait(0 + I2C_WRITE); i2c_write(0x2E); i2c_write(0x00); i2c_write(0x00); if (i2c_write(0x6F) == 0) { i2c_stop(); Serial.println("> [ChangeAddr] Data erased."); } else { i2c_stop(); Serial.println("> [ChangeAddr] Failed to erase data"); return -1; } Serial.print("> [ChangeAddr] Writing data: "); Serial.print(NewAddr1, HEX); Serial.print(", "); Serial.println(NewAddr2, HEX); for (int a = 0; a != 256; a++) { i2c_start_wait(0 + I2C_WRITE); i2c_write(0x2E); i2c_write(NewAddr1); i2c_write(NewAddr2); if (i2c_write(a) == 0) { i2c_stop(); delay(100); Serial.print("> [ChangeAddr] Found correct CRC: 0x"); Serial.println(a, HEX); return a; } } i2c_stop(); Serial.println("> [ChangeAddr] Correct CRC not found"); return -1; } /** * Reads the MLX address. * */ byte ReadAddr(byte MLXAddress) { byte NewMLXAddress; Serial.println("> [ReadAddr] Reading address"); if (MLXAddress == 0) { Serial.print(" Using MLX univeral address"); } else { Serial.print(" Using MLX address: "); Serial.print(MLXAddress, HEX); } Serial.print(", Data: "); i2c_start_wait(MLXAddress + I2C_WRITE); i2c_write(0x2E); i2c_rep_start(MLXAddress + I2C_READ); NewMLXAddress = i2c_readAck(); Serial.print(NewMLXAddress, HEX); Serial.print(", "); Serial.print(i2c_readAck(), HEX); Serial.print(", "); Serial.println(i2c_readNak(), HEX); i2c_stop(); return NewMLXAddress; } I've moved the often called `ReadTemp()` function into a separate file in order to try and simplify the content of `remap_mlx` - see below: read_temp.ino +++++++++++++ Contents of `read_temp.ino`: .. code-block:: c :linenos: table // read_temp.ino /** * Utility function to read temperature from MLX at address * */ float ReadTemp(byte address) { int data_low = 0; int data_high = 0; int pec = 0; byte MLXAddress = address<<1; Serial.print("> [ReadTemp] Read temperature "); if (MLXAddress == 0) { Serial.print("using MLX univeral address"); } else { Serial.print("using MLX address "); Serial.print(address, HEX); }; Serial.print(": "); i2c_start_wait(MLXAddress + I2C_WRITE); i2c_write(0x07); i2c_rep_start(MLXAddress + I2C_READ); data_low = i2c_readAck(); data_high = i2c_readAck(); pec = i2c_readNak(); i2c_stop(); float temp = 0x0000; temp = (float)(((data_high & 0x007F) << 8) + data_low); temp = (temp * 0.02) - 273.16; Serial.print(temp); Serial.println(" C"); return temp; } As long as `remap_mlx.ino` and `read_temp.ino` are side by side in a project directory as described above, your Arduino development environment won't have any issues compiling the code. .. _`See the range of supported addresses`: Address Range Supported by the MLX90614 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once again, the `Melexis MLX90614 Datasheet`_ states that "the SA feature allows connecting up to 127 devices (SA=0x00...0x07F) with only 2 wires". I have experimented with many different addresses between 0x00 and 0x07F. I cannot really emphasise that enough - I've yet to find an address in that range that would not be accepted as a slave address. Purely for the avoidance of doubt, I've run a little Python one liner below to show the range of acceptable slave addresses. These will all work! .. code-block:: python >>> [format(x, '#04x') for x in range(1,128)] [ '0x00', '0x01', '0x02', '0x03', '0x04', '0x05', '0x06', '0x07', '0x08', '0x09', '0x0a', '0x0b', '0x0c', '0x0d', '0x0e', '0x0f', '0x10', '0x11', '0x12', '0x13', '0x14', '0x15', '0x16', '0x17', '0x18', '0x19', '0x1a', '0x1b', '0x1c', '0x1d', '0x1e', '0x1f', '0x20', '0x21', '0x22', '0x23', '0x24', '0x25', '0x26', '0x27', '0x28', '0x29', '0x2a', '0x2b', '0x2c', '0x2d', '0x2e', '0x2f', '0x30', '0x31', '0x32', '0x33', '0x34', '0x35', '0x36', '0x37', '0x38', '0x39', '0x3a', '0x3b', '0x3c', '0x3d', '0x3e', '0x3f', '0x40', '0x41', '0x42', '0x43', '0x44', '0x45', '0x46', '0x47', '0x48', '0x49', '0x4a', '0x4b', '0x4c', '0x4d', '0x4e', '0x4f', '0x50', '0x51', '0x52', '0x53', '0x54', '0x55', '0x56', '0x57', '0x58', '0x59', '0x5a', '0x5b', '0x5c', '0x5d', '0x5e', '0x5f', '0x60', '0x61', '0x62', '0x63', '0x64', '0x65', '0x66', '0x67', '0x68', '0x69', '0x6a', '0x6b', '0x6c', '0x6d', '0x6e', '0x6f', '0x70', '0x71', '0x72', '0x73', '0x74', '0x75', '0x76', '0x77', '0x78', '0x79', '0x7a', '0x7b', '0x7c', '0x7d', '0x7e', '0x7f' ] .. _`Look at actually running the code`: Running the Code ~~~~~~~~~~~~~~~~ As in the previous post, the Arduino, MLX and a power cycle switch, below: .. image:: https://farm8.staticflickr.com/7719/16981712297_e7e29d0e2b_c.jpg :width: 800 :height: 559 :scale: 100 :alt: 1 x MLX90614 sensors and an Arduino with NC switch *An Arduino and MLX90614 with NC switch on the supply* Again, these instructions remain the same as `the previous post`_, but note the warning! .. WARNING:: One note of caution - when attempting to change addresses **do not** have more than one MLX90614 on the circuit at the same time. Upload this program above to your Arduino and run it, being ready to pounce with that wire or push switch during the 10 second pause. Here's what my output looked like when changing the address of a device from 0x2A to 0x2B, using the new progam from this post: .. code-block:: text Setup... > [ReadAddr] Reading address Using MLX univeral address, Data: 2A, 0, FF > [ChangeAddr] Will change address to: 2B > [ChangeAddr] Data erased. > [ChangeAddr] Writing data: 2B, 0 > [ChangeAddr] Found correct CRC: 0xE1 > [ReadAddr] Reading address Using MLX univeral address, Data: 2B, 0, FF **---READY---** > [setup] Cycle power NOW to store new address - you have 10 seconds > [ReadAddr] Reading address Using MLX univeral address, Data: 2B, 0, FF Warning, next ReadTemp() may fail if address has not been set. > [ReadTemp] Read temperature using MLX address: 2B, 18.64 C **---DONE---** > [ReadTemp] Read temperature using MLX address: 2B, 18.62 C Finally ~~~~~~~ Hopefully you'll find some success in remapping MLX addresses - try it out and give me some feedback on how things go. The code is available `here at my Github repository`_. .. footnotes .. links .. _`this article`: {filename}/projects/2014-09-08_arduino-and-multiple-mlx90614-sensors.rst .. _`cloud detection`: {filename}/projects/2014-03-04_cloud-detection-part-one.rst .. _`Melexis MLX90614 Datasheet`: http://www.melexis.com/Assets/IR-sensor-thermometer-MLX90614-Datasheet-5152.aspx .. _`manipulation of the port registers`: https://www.arduino.cc/en/Reference/PortManipulation .. _`this forum post`: http://forum.arduino.cc/index.php?topic=54170.msg539601#msg539601 .. _`the previous post`: {filename}/projects/2014-09-08_arduino-and-multiple-mlx90614-sensors.rst .. _`here at my Github repository`: https://github.com/chrisramsay/arduino-projects/tree/master/mlx90614