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