Sunday, May 3, 2015

Configuration and Synchronous (polling-based) Communication with MPL3115A2 Precision Altimeter

To facilitate configuration and synchronous and asynchronous (interrupt-based) communication with MPL3115A2 precision barometer/altimeter and potential use in other projects I created a MPLlibrary, which relies on my generic I2Clibrary, which we discussed in the previous posts. In this post we will discuss components of the MPLlibrary, which are responsible for configuration and synchronous communication with MPL3115A2. Please note, that configuration of the sensor requires reading and writing data to sensor’s control registers, so we would have to first look at the low-level communication primitives prior to discussing configuration of the sensor.

Like every library that I build, the first component of the MPLlibrary is the MPL_Profile.h header. This header defines the I2C address of the sensor, the interrupt that will be associated with sensors READY line for asynchronous (interrupt-driven) communication and provide function to configure the PIC MCU pin that will trigger this interrupt.

MPL_Local.h header provides definitions for some common constants (like address of CTRL_REG1 register and masks to set and reset OST) and variables used to control the state of the library and store intermediate sensor data. Variables defined in MPL_Local.h header are instantiated in MPL_Local.c code file. The MPL_Local.h header also provides definitions for functions internal to the library that are being used for synchronous and asynchronous communication.

Two basic functions implementing core of synchronous communication are _MPL_Read(…) and _MPL_Write(…).  These functions allow for reading or writing, respectively, of one or more bytes starting from a specified sensor’s register. Both of these functions are rather simple wrappers around the respective I2CSyncRead(…) and I2CSyncWrite(…) functions from the I2Clibrary  providing parameters (like device address and I2C bus number from the MPL_Profile.h) and augmented with simple check to assure that the MPLlibrary is not engaged in asynchronous operation . Implementation of these functions provided in MPL_Sync.c code file.

Using these two functions it was quite easy to implement the whole slew of very simple management functions to access values of other MPL3115A2 control registers. These functions are implemented in MPL_Mgmt.c code file. As you may see from the implementation, these functions provide just a thin wrapper around the basic _MPL_Read(…) function. The wrapper provides the hard-coded addresses of the respective control and information registers and default the length to one byte. One function in this group stands on its own – MPLGetBase() just returns the value of the current _MPL_BaseAlt variable, which we will discuss a little later while reviewing processing of the raw data received from the sensor.

Now we are ready to review initialization of the library and initial configuration of the sensor implemented in function MPLInit(OSR), which is implemented in the MPL_Init.c code file. Parameter of this function, OSR, takes values from 0 to 7 and matches exactly the definition of the OSR from the MPL3115A2  documentation. First, function MPLInit(…) assures that this is the first call to library initialization and obtains the Interrupt Level (IL) from the I2C library – as we mentioned while discussing asynchronous I2C communication, I2Clibrary  clients should use the same interrupt level as the library itself so that critical section (synchronization mechanism) could assure exclusive access to library elements.

MPLlibrary requires timing support, so through the call to TMRInitDefault() (please see post related to TMRlibrary and timing support) function MPLInit(…) makes sure that timer is initialized. If timer was already initialized with specific parameters, call to TMRInitDefault() is ignored. Then MPLInit(…) initializes external interrupt line used to capture READY signal from the sensor (used for asynchronous access) and initializes respective MCU pins through the call to _MPLInitPinMap (), defined in MPL_Profile.h header. This concludes initialization of the library and now it is time to initialize the sensor itself.

Originally initialization of the sensor was part of the MPLInit(…) routine, but then I realized that it is quite possible to change configuration of the sensor several times after library is initialized. Obviously, this hardly ever required during quads flight, but having this functionality available would make it much easier to investigate sensor’s operation outside of the quad control. So the final decision was to implement sensor configuration in a separate routine MPLReset(…), which is called from the MPLInit(…), but also can be called any time after the library is initialized. This function is implemented in the same MPL_Init.c code file as the MPLInit(…) function.

First, MPLReset(…) assures that the library is initialized and if there is an asynchronous operation in progress, attempts to stop it. Then based upon the values of the OSR parameter and using estimates for maximum sample time discussed in the previous post, MPLReset(…) calculates maximum delay between samples and stores calculated value in the library shared variable _MPL_MaxInt. This value is important for assuring stability of the asynchronous read, which we will discuss in the further post.

To implement sensor’s reset MPLReset(…) function first puts the sensor into the STANDBY mode by setting respective bit in the CTRL_REG1 register. Transitioning to STANDBY may take some time, so MPLReset(…) continues polling CTRL_REG1 register until the STANDBY state is indicated by the sensor.

When sensor is in STANDBY mode, MPLReset(…) initiates sensor’s internal RESET by setting RESET bit in CTRL_REG1 register. After sensor completes internal RESET, it returns to STANDBY mode. Again, this transition may take some time so MPLReset(…) continues polling CTRL_REG1 register until the STANDBY state is indicated by the sensor. Now that the sensor is in STANDBY mode after implementing internal RESET, we may configure other parameters.

This includes configuring sensor to generate interrupt on READY by setting INT1 line high in the push-pull (versus the optional open-drain) mode and setting the OSR bits to control oversampling. As we are using sensor in the one-shot mode (OST) as discussed in the previous post, we can leave sensor in the STANDBY mode. To conclude reset of the sensor and make library ready for operation in the new mode we need to read one sample from the sensor, which is achieved through the call to MPLlibrary function MPLReadSample(…), which reads next sample from the sensor in synchronous mode.

MPLReadSample(…) function, implemented in MPL_Sync.c code file, takes as a parameter a pointer to MPLData structure, defined in MPL.h header file. First this function assures that the library is initialized and that there is no asynchronous operation in progress. If these conditions are met, it calls MPLlibrary internal function _MPLReadRawData(…), which outputs the latest sample (subject to OSR setting) from the sensor in binary format. If the call is successful, MPLReadSample(…) obtains current timestamp and then converts raw binary sample data from the sensor to floating point format, scales resulting value to meters (LSB in raw data sample is 6.25 centimeters), and, if the “ground level” (value stored in _MPL_BaseAlt, initially set to zero) is set, converts the altitude to the value relative to the ground altitude. Calculated altitude and the current timestamp are stored in the MPLData structure provided by the caller.

The brunt of the work to actually read data from the sensor is carried out by the _MPLReadRawData(…) function, implemented in the same MPL_Sync.c code file. This function first waits until the new sample is ready and then reads the STATUS register, 3 bytes of altitude data, and two bytes of temperature data. Temperature data is ignored, but had to be read to reset the READY line. Three bytes of altitude data are converted to long integer to be returned to the caller. As we use the sensor in the OST (one-shot) mode, to avoid wait for the next sample in subsequent reads, upon reading the latest sample _MPLReadRawData(…) function immediately instructs the sensor to initiate acquisition of the next sample by first reading and then setting the OST bit in the CTRL_REG1 register.

If we want to receive altitude measurements relative to the ground level in the flight zone (which is typical for quad controller), we need to establish “ground level”. MPLlibrary stores the ground level in the local variable _MPL_BaseAlt, initially set to zero in MPLInit(…) function. Adjusting value of this variable to the actual ground level could be achieved through the call to the MPLSetGround() function, which is considered a part of the initialization routines and implemented in MPL_Init.c code file.

This function first resets ground level to zero and then collects altitude samples for 1 second. These samples are being averaged and resulting value is stored in _MPL_BaseAlt variable as the ground level. All subsequent altitude samples obtained either in synchronous mode through calls to MPLReadSample(…) function or in asynchronous mode, which we will discuss later, are adjusted to be relative to the established ground level.


This concludes the overview of configuration and synchronous (polling-based) communication with MPL3115A2 Precision Altimeter. Now it is time to put everything discussed thus far together and have a look at the actual program that works with MPL3115A2 barometric altimeter, but that would be the topic of the next post.

No comments:

Post a Comment