{"id":99,"date":"2013-02-27T20:09:40","date_gmt":"2013-02-27T20:09:40","guid":{"rendered":"http:\/\/www.henrylahr.com\/wp\/?p=99"},"modified":"2018-04-12T08:16:16","modified_gmt":"2018-04-12T08:16:16","slug":"mpl3115a2-altitude-sensor-one-shot-mode","status":"publish","type":"post","link":"https:\/\/www.henrylahr.com\/?p=99","title":{"rendered":"MPL3115A2 Altitude Sensor One Shot Mode"},"content":{"rendered":"<p>The good people at <a href=\"http:\/\/www.hobbytronics.co.uk\/\">hobbytronics<\/a> sell a neat altimeter based on the MPL3115A2 pressure sensor. Its datasheet mentions a one shot mode, which can be a practical solution if one needs a high data acquisition frequency. For some reasons, I did not get the FIFO polling mode to produce new readings at intervals shorter than 512ms. Maybe someone can enlighten me. The main benefit of using the one shot mode is not having to constantly poll the interrupt register to see whether new data has arrived (if you don&#8217;t want to connect an additional wire to one of the interrupts pin). As soon as the one shot bit is cleared and reset, a new measurement cycle begins. Its duration is determined by the oversampling bits. The trick is to match loop time with this measurement cycle. For example, if you want to run the loop every 500ms, set oversampling to 128x and you will get roughly one new value every time the registers are read. I clear the one shot bit immediately after reading the register to start a new measurement cycle, so we can do other things while the sensor is busy. The other improvement is reading all registers at once instead of querying them individually, which saves a few bytes of I2C communication.<\/p>\n<p>In general, filtering the data yourself (for example, reading at shorter intervals, but with less oversampling) does not increase precision, but may be more timely depending on the filter you use. Unfortunately, the datasheet does not say how exactly individual readings are combined by the sensor, but it looks like simple averaging (which, of course, is the best linear estimator of the mean).<\/p>\n<p>In case you wonder what the lengthy setup() function does: The aim is to calibrate the altitude readings using the actual sea level pressure, which can only be inferred from a current pressure reading at a <em>known<\/em> altitude. Hence, you have to provide an altitude (e.g. by hard-coding the altitude or via GPS). The algorithm calculates the sea level pressure using the formula in the datasheet. <del datetime=\"2013-04-21T09:59:52+00:00\">Theoretically, feeding the sea level pressure back to the sensor, that is, writing to the barometer offset register, should yield an altimeter reading of the known altitude we just used to calculate the pressure. It does not, which implies that the sensor is not (only) using the formula provided in the datasheet. The resulting altitude offset is then saved in a variable to be used when printing the altitude. You could also write this offset to the sensor register to save one addition, but this is less precise (integers only), and you would have to set it back to zero before calculating the sea level pressure at start-up.<\/del> [The offset was caused by a bug in the pressure calculation code. Thanks to Mike for pointing this out. The altitude setup now works without any offset.]<\/p>\n<p>There also is some <a href=\"cache.freescale.com\/files\/sensors\/doc\/app_note\/AN4528.pdf\">background information<\/a> available from Freescale on how the sensor works in principle and how various offsets can be accounted for.<\/p>\n<p>This is my working example for Arduino:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/*\r\n\r\nMPL3115A2 Altitude Sensor One Shot Mode Example\r\n Henry Lahr, 2013-02-27\r\n loosely based on: https:\/\/github.com\/sparkfun\/MPL3115A2_Breakout\/blob\/master\/firmware\/mpl3115a2\/mpl3115a2.ino\r\n There is no warranty for this code; feel free to use this code for your projects.\r\n Hardware Connections: VCC = 3.3V; SDA = A4; SCL = A5; INT pins not connected\r\n Usage:\r\n - Serial terminal at 115200bps\r\n - Prints altitude in meters or temperature in degrees C, depending on whether ALTMODE is defined\r\n *\/\r\n\r\n#include &lt;Wire.h&gt; \/\/ for I2C communication\r\n\r\n#define ALTMODE; \/\/comment out for barometer mode; default is altitude mode\r\n#define ALTBASIS 18 \/\/start altitude to calculate mean sea level pressure in meters\r\n\/\/this altitude must be known (or provided by GPS etc.)\r\n\r\nconst int SENSORADDRESS = 0x60; \/\/ address specific to the MPL3115A1, value found in datasheet\r\n\r\nfloat altsmooth = 0; \/\/for exponential smoothing\r\nbyte IICdata&#x5B;5] = {0,0,0,0,0}; \/\/buffer for sensor data\r\n\r\nvoid setup(){\r\n  Wire.begin(); \/\/ join i2c bus\r\n  Serial.begin(115200); \/\/ start serial for output\r\n  Serial.println(&quot;Setup&quot;);\r\n  if(IIC_Read(0x0C) == 196); \/\/checks whether sensor is readable (who_am_i bit)\r\n  else Serial.println(&quot;i2c bad&quot;);\r\n\r\n  IIC_Write(0x2D,0); \/\/write altitude offset=0 (because calculation below is based on offset=0)\r\n  \/\/calculate sea level pressure by averaging a few readings\r\n  Serial.println(&quot;Pressure calibration...&quot;);\r\n  float buff&#x5B;4];\r\n  for (byte i=0;i&lt;4;i++){\r\n    IIC_Write(0x26, 0b00111011); \/\/bit 2 is one shot mode, bits 4-6 are 128x oversampling\r\n    IIC_Write(0x26, 0b00111001); \/\/must clear oversampling (OST) bit, otherwise update will be once per second\r\n    delay(550); \/\/wait for sensor to read pressure (512ms in datasheet)\r\n    IIC_ReadData(); \/\/read sensor data\r\n    buff&#x5B;i] = Baro_Read(); \/\/read pressure\r\n    Serial.println(buff&#x5B;i]);\r\n  }\r\n  float currpress=(buff&#x5B;0]+buff&#x5B;1]+buff&#x5B;2]+buff&#x5B;3])\/4; \/\/average over two seconds\r\n\r\n  Serial.print(&quot;Current pressure: &quot;); Serial.print(currpress); Serial.println(&quot; Pa&quot;);\r\n  \/\/calculate pressure at mean sea level based on a given altitude\r\n  float seapress = currpress\/pow(1-ALTBASIS*0.0000225577,5.255877);\r\n  Serial.print(&quot;Sea level pressure: &quot;); Serial.print(seapress); Serial.println(&quot; Pa&quot;);\r\n  Serial.print(&quot;Temperature: &quot;);\r\n  Serial.print(IICdata&#x5B;3]+(float)(IICdata&#x5B;4]&gt;&gt;4)\/16); Serial.println(&quot; C&quot;);\r\n\r\n  \/\/ This configuration option calibrates the sensor according to\r\n  \/\/ the sea level pressure for the measurement location (2 Pa per LSB)\r\n  IIC_Write(0x14, (unsigned int)(seapress \/ 2)&gt;&gt;8);\/\/IIC_Write(0x14, 0xC3); \/\/ BAR_IN_MSB (register 0x14):\r\n  IIC_Write(0x15, (unsigned int)(seapress \/ 2)&amp;0xFF);\/\/IIC_Write(0x15, 0xF3); \/\/ BAR_IN_LSB (register 0x15):\r\n\r\n  \/\/one reading seems to take 4ms (datasheet p.33);\r\n  \/\/oversampling 32x=130ms interval between readings seems to be best for 10Hz; slightly too slow\r\n  \/\/first bit is altitude mode (vs. barometer mode)\r\n\r\n  \/\/Altitude mode\r\n  IIC_Write(0x26, 0b10111011); \/\/bit 2 is one shot mode \/\/0xB9 = 0b10111001\r\n  IIC_Write(0x26, 0b10111001); \/\/must clear oversampling (OST) bit, otherwise update will be once per second\r\n  delay(550); \/\/wait for measurement\r\n  IIC_ReadData(); \/\/\r\n  altsmooth=Alt_Read();\r\n  Serial.print(&quot;Altitude now: &quot;); Serial.println(altsmooth);\r\n  Serial.println(&quot;Done.&quot;);\r\n}\r\n\r\nvoid loop(){\r\n  sensor_read_data();\r\n  \/\/ your code here\r\n}\r\n\r\nvoid sensor_read_data(){\r\n  \/\/ This function reads the altitude (or barometer) and temperature registers, then prints their values\r\n  \/\/ variables for the calculations\r\n  int m_temp;\r\n  float l_temp;\r\n  float altbaro, temperature;\r\n\r\n  \/\/One shot mode at 0b10101011 is slightly too fast, but better than wasting sensor cycles that increase precision\r\n  \/\/one reading seems to take 4ms (datasheet p.33);\r\n  \/\/oversampling at 32x=130ms interval between readings seems to be optimal for 10Hz\r\n  #ifdef ALTMODE \/\/Altitude mode\r\n    IIC_Write(0x26, 0b10111011); \/\/bit 2 is one shot mode \/\/0xB9 = 0b10111001\r\n    IIC_Write(0x26, 0b10111001); \/\/must clear oversampling (OST) bit, otherwise update will be once per second\r\n  #else \/\/Barometer mode\r\n    IIC_Write(0x26, 0b00111011); \/\/bit 2 is one shot mode \/\/0xB9 = 0b10111001\r\n    IIC_Write(0x26, 0b00111001); \/\/must clear oversampling (OST) bit, otherwise update will be once per second\r\n  #endif\r\n  delay(100); \/\/read with 10Hz; drop this if calling from an outer loop\r\n\r\n  IIC_ReadData(); \/\/reads registers from the sensor\r\n  m_temp = IICdata&#x5B;3]; \/\/temperature, degrees\r\n  l_temp = (float)(IICdata&#x5B;4]&gt;&gt;4)\/16.0; \/\/temperature, fraction of a degree\r\n  temperature = (float)(m_temp + l_temp);\r\n\r\n  #ifdef ALTMODE \/\/converts byte data into float; change function to Alt_Read() or Baro_Read()\r\n    altbaro = Alt_Read();\r\n  #else\r\n    altbaro = Baro_Read();\r\n  #endif\r\n\r\n  altsmooth=(altsmooth*3+altbaro)\/4; \/\/exponential smoothing to get a smooth time series\r\n\r\n  Serial.print(altbaro); \/\/ in meters or Pascal\r\n  Serial.print(&quot;,&quot;);\r\n  Serial.print(altsmooth); \/\/ exponentially smoothed\r\n  Serial.print(&quot;,&quot;);\r\n  Serial.println(temperature); \/\/ in degrees C\r\n}\r\n\r\nfloat Baro_Read(){\r\n  \/\/this function takes values from the read buffer and converts them to pressure units\r\n  unsigned long m_altitude = IICdata&#x5B;0];\r\n  unsigned long c_altitude = IICdata&#x5B;1];\r\n  float l_altitude = (float)(IICdata&#x5B;2]&gt;&gt;4)\/4; \/\/dividing by 4, since two lowest bits are fractional value\r\n  return((float)(m_altitude&lt;&lt;10 | c_altitude&lt;&lt;2)+l_altitude); \/\/shifting 2 to the left to make room for LSB\r\n}\r\n\r\nfloat Alt_Read(){\r\n  \/\/Reads altitude data (if CTRL_REG1 is set to altitude mode)\r\n  int m_altitude = IICdata&#x5B;0];\r\n  int c_altitude = IICdata&#x5B;1];\r\n  float l_altitude = (float)(IICdata&#x5B;2]&gt;&gt;4)\/16;\r\n  return((float)((m_altitude &lt;&lt; 8)|c_altitude) + l_altitude);\r\n}\r\n\r\nbyte IIC_Read(byte regAddr){\r\n  \/\/ This function reads one byte over I2C\r\n  Wire.beginTransmission(SENSORADDRESS);\r\n  Wire.write(regAddr); \/\/ Address of CTRL_REG1\r\n  Wire.endTransmission(false); \/\/ Send data to I2C dev with option for a repeated start. Works in Arduino V1.0.1\r\n  Wire.requestFrom(SENSORADDRESS, 1);\r\n  return Wire.read();\r\n}\r\n\r\nvoid IIC_ReadData(){\u00a0 \/\/Read Altitude\/Barometer and Temperature data (5 bytes)\r\n  \/\/This is faster than reading individual register, as the sensor automatically increments the register address,\r\n  \/\/so we just keep reading...\r\n  byte i=0;\r\n  Wire.beginTransmission(SENSORADDRESS);\r\n  Wire.write(0x01); \/\/ Address of CTRL_REG1\r\n  Wire.endTransmission(false);\r\n  Wire.requestFrom(SENSORADDRESS,5); \/\/read 5 bytes: 3 for altitude or pressure, 2 for temperature\r\n  while(Wire.available()) IICdata&#x5B;i++] = Wire.read();\r\n}\r\n\r\nvoid IIC_Write(byte regAddr, byte value){\r\n  \/\/ This function writes one byto over I2C\r\n  Wire.beginTransmission(SENSORADDRESS);\r\n  Wire.write(regAddr);\r\n  Wire.write(value);\r\n  Wire.endTransmission(true);\r\n}\r\n\r\n<\/pre>\n<p><strong>Update (2013-06-26):<\/strong><br \/>\nI have not tested the sensor&#8217;s stability and precision. However, I did use it in combination with an accelerometer to obtain vertical position and velocity. Because obtaining altitude and velocity involves trading off precision in altitude against precision in velocity, the graph below can only give an indication of the sensor&#8217;s precision. I am quite happy with the performance so far. This graph shows a ride in the lift from 20m down to about 5m (never mind the arbitrary altitude offset):<\/p>\n<p><a href=\"https:\/\/www.henrylahr.com\/wp-content\/uploads\/2013\/02\/MPL3115A2_test.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-215\" src=\"https:\/\/www.henrylahr.com\/wp-content\/uploads\/2013\/02\/MPL3115A2_test.jpg\" alt=\"MPL3115A2_test\" width=\"781\" height=\"259\" srcset=\"https:\/\/www.henrylahr.com\/wp-content\/uploads\/2013\/02\/MPL3115A2_test.jpg 781w, https:\/\/www.henrylahr.com\/wp-content\/uploads\/2013\/02\/MPL3115A2_test-300x99.jpg 300w\" sizes=\"auto, (max-width: 781px) 100vw, 781px\" \/><\/a><\/p>\n<p>Time on the x-axis is in seconds. Altitude in meters is the red line with the scale on the left. The blue line is vertical velocity in m\/s, measured on the right hand scale. Both values are calculated from acceleration and altitude in two loops, based on estimated average loop time of 22ms:<\/p>\n<p>Altitude: zpos=0.95*(zpos + zvel*G_Dt + netacc\/2 * G_Dt*G_Dt) + 0.05*curralt,<\/p>\n<p>Velocity: zvel=0.95*(zvel+ netacc*G_Dt) + 0.05*(curralt-altold)\/(5*G_Dt),<\/p>\n<p>where netacc is vertical acceleration, G_Dt is about 22ms, curralt is the altimeter reading and altold is the previous altimeter reading. The factor of 5 accounts for the different update rate in accelerometer (once per loop) and altimeter (once every 5 loops) readings.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The good people at hobbytronics sell a neat altimeter based on the MPL3115A2 pressure sensor. Its datasheet mentions a one shot mode, which can be a practical solution if one needs a high data acquisition frequency. For some reasons, I did not get the FIFO polling mode to produce new readings at intervals shorter than &hellip; <\/p>\n<p><a class=\"more-link btn\" href=\"https:\/\/www.henrylahr.com\/?p=99\">Continue reading<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-99","post","type-post","status-publish","format-standard","hentry","category-musings","nodate","item-wrap"],"_links":{"self":[{"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=\/wp\/v2\/posts\/99","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=99"}],"version-history":[{"count":22,"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=\/wp\/v2\/posts\/99\/revisions"}],"predecessor-version":[{"id":448,"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=\/wp\/v2\/posts\/99\/revisions\/448"}],"wp:attachment":[{"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=99"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=99"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.henrylahr.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=99"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}