Now that we are quite familiar with MPL3115A2 sensor, it is
time to look at how we may read samples from this sensor without wasting time
on polling. The implementation of asynchronous sample acquisition is split
between the rather generic features implemented in the I2Clibrary, which we discussed earlier, and specific to this sensor operations implemented in the MPLlibrary. Features of the MPLlibrary responsible for asynchronous communication implemented in two code
files MPL_Async.c
and MLP_ISR.c.
The former implements related user-accessible and some internal helper
functions, while the latter implements the interrupt service routine (ISR) to process DREADY (data ready) interrupt from the sensor and the callback
function invoked by I2Clibrary in response to I2C
interrupts.
Let
us first review functions implemented in MPL_Async.c code file. The first one is the MPLAsyncStart()
function. It first checks for library initialization and then checks for Asynchronous
Mode (_MPL_Async) flag. If it is already set, the function returns
success code and does nothing else. Otherwise it sets the Asynchronous Mode
flag in the library, clears external interrupt request, if any, and then
enables external interrupt linked (in MPLInit() function through the
call to _MPLInitPinMap() defined in MPL_Profile.h)
to the sensor DREADY line.
One
special point need to be noted here – as the sensor operates in the OST
(one-shot) mode, if the last read was unsuccessful the one-shot was not
triggered, so DREADY line may never go high triggering the interrupt. We
deal with this possibility in the Read functions. However to facilitate this we
need to capture the time of last “successful” read so that we may compare the
interval since then with the maximum time to acquire the sample _MPL_MaxInt,
which was calculated in MPLReset(…)
function based upon the specified value of OSR during initialization of
the sensor. As a “safe bet” in the MPLAsyncStart() function we set the time of the last “successful”
read to the current timestamp to guarantee that at most after _MPL_MaxInt,
time the interrupt will be forcefully triggered in one of the Read functions. We
will see shortly how that is being implemented.
On
the other hand, if the one-shot was successfully triggered in preceding read
operation, the DREADY line may already be high, but the respective interrupt
request was ignored as the Asynchronous Mode was not enabled. To address this
contingency, MPLAsyncStart() function
checks for the line status to be high and, if so, triggers “missed” interrupt
forcefully by setting interrupt flag MPL_IF (which is link to the
selected interrupt port in MPL_Profile.h).
This concludes transition to Asynchronous Mode.
If
we need to terminate Asynchronous Mode, for example – to reset sensor with a
different value of OSR, we need to call MPLAsyncStop() function.
This function resets all the flags associated with Asynchronous Mode and
disables external interrupt due to DREADY line going high.
MPLlibrary provides two functions in MPL_Async.c
file to read samples from MPL3115A2 in Asynchronous Mode. The first one, MPLAsyncReadIfReady(…), updates the MPLData structure specified as a
parameter only if the new sample is available; otherwise the structure remain
unchanged and respective return code (MPL_NRDY
- data sample is not ready) provided to
the caller. If the new sample is not ready, the function compares the current
time-stamp with the time-stamp of last successful read operation – if the time
since last read exceeds maximum sample acquisition time, a DREADY interrupt is
triggered to restore the cycle. We will get into more details of this when we
review the ISR routines shortly. As the call to this function is non-blocking,
this function is being used in the quad control loop to avoid slowing down the
loop with the wait for altitude sample.
The second function, MPLAsyncReadWhenReady(…),
is very similar to the former one with the exception that it waits until the
next sample is available. I usually use this function in some test projects; it
also can be used in situations when obtaining the next altitude sample prior to
proceeding further may be important for the application.
To actually read the sample data from the library both MPLAsyncReadIfReady(…) and MPLAsyncReadWhenReady(…) functions call
library internal function _MPLAsyncRead(…).
This function establishes a Critical Section (to avoid potential corruption of
data by the interrupt routine), within which retrieves the number of samples
accumulated _MPL_Ready by the
library, the sum of accumulated samples _MPL_Data,
and the time-stamp of the last sample _MPL_DataTS.
When these values are retrieved and stored in local variables, both the sample
counter _MPL_Ready and the sum of
sample _MPL_Data values are set to
zero, which indicates that samples asynchronously retrieved by the library are
consumed. This concludes the Critical Section.
The altitude values then converted from long integer in
sensor units to a floating-point value representing altitude in meters. If more
than one sample summed up in _MPL_Data,
as indicated by _MPL_Ready > 1,
the resulting value of altitude averaged over collected samples. Finally the
value of altitude is adjusted by the altitude of the “ground” (if it was set
earlier) to convert it from the altitude over the sea level to altitude over
ground.
So what is the difference between this function and
synchronous read function MPLReadSample(…)
that we discussed in the earlierpost? The synchronous read function MPLReadSample(…)
ties up the main execution thread for the whole duration of the read operation.
Contrary to that, the asynchronous read function MPLAsyncReadWhenReady(…) after reading current sample immediately
triggers acquisition of the next sample asynchronously and then returns control
to the main thread, which may do some processing of the sample while the next
one is being acquired asynchronously. Thus at the next call to MPLAsyncReadWhenReady(…) if the next
sample already acquired there would not be any wait or, if sample is not yet
ready, the wait will be reduced by the time the main execution thread spent
processing previous sample.
Now we are ready to look at the interrupt routines
implemented in MLP_ISR.c
code file. The first one, MPL_Interrupt(),
is the true interrupt routine linked to the DREADY line. When the interrupt is
enabled (in MPLAsyncStart() routine)
and the DREADY line goes from low to
high MCU interrupts the main execution thread and, subject to interrupt
priority, transfers control to MPL_Interrupt()
function. Alternatively interrupt could be forced from the main execution
thread by setting interrupt flag MPL_IF
in code, which, under some circumstances discussed above, could be done in MPLAsyncStart() or Asynchronous Mode
read routines.
MPL_Interrupt() function
first and foremost resets the interrupt flag MPL_IF – otherwise upon return it will be called again by MCU
indefinitely. Second, it captures the status of the DREADY line to distinguish between natural and forced interrupts. Then
it prepares the I2CAsyncRqst structure
and attempts to subscribe for I2C processing through the call to I2CAsyncStart(…). If subscription is
successful – request could be initiated right away or put in a queue by I2CAsyncStart(…), MPL_Interrupt() function resets the state of the State Machine
implemented in the _MPLCallBack(…)
function and returns. If I2C subscription is successful, I2C interrupts will be
routed by the I2C ISR (as discussed in the earlier
post) to the _MPLCallBack(…)
function.
The core of communication is implemented in the _MPLCallBack(…) function – let’s look
at how it works. Basically the whole _MPLCallBack(…)
function is a big SWITCH statement
with each case corresponding to a single state in the communication process, starting with the initial state 0 corresponding to the first interrupt
generated by the I2C module after the START
condition. Each CASE block performs
a simple operation like setting a flag or reading/writing one byte from/to I2C
read/write registers following the sequencing protocol required to construct a
complete message for MPL3115A2. At the end of each CASE block the state is usually advanced to the next one with a few
exceptions.
State 5 may advance state either to the state 6 or to the
state 7 depending on the counter of the bytes read from the sensor. We need to
read 5 bytes from the sensor to reset the DREADY line – 3 bytes representing
the altitude and 2 bytes representing the temperature; we ignore the
temperature measurement, but we still have to read it. Thus, at State 5 if there
are more bytes to read we advance to State 6; if all 5 bytes are read we
advance to State 7.
State 6 is another exception – after preparing I2C bus for
read of the next byte it moves the state back to State 5 to read the byte and
make decision on further protocol steps based upon the number of bytes read
from the sensor in this session.
Finally, at State 18 the message protocol is completed and
there is no need to advance state – State 18 is the final one. When _MPLCallBack(…) function reaches State
18 it terminates current I2C operation by calling I2CAsyncStop(…). If the status of the DREADY line was high as captured in variable _MPL_PortLvl by the MPL_Interrupt()
function, then the value read from the sensor represent the new sample. Bytes
read from the sensor are interpreted according to the specified data format and
converted into a long integer representing measurement of altitude in sensor
units (LSB corresponds to 6.25 centimeters).
If this is the first sample in a series of samples that were
not yet consumed by the client, the just calculated value of the altitude (in
sensor units) is stored in the library variable _MPL_Data. If this is not the first sample (_MPL_Ready > 0), the new value is added to the value stored in _MPL_Data. The latter case takes place
when the frequency of reading asynchronously collected samples is less than the
frequency at which sensor produces samples.
There is a special case in this logic – if the number of
samples summed up in the _MPL_Data variable
exceeds 512, the series is ignored – the new value of altitude is stored in the
_MPL_Data variable and _MPL_Ready is set to one. _MPLCallBack(…) function does this to
avoid overflow of the long integer _MPL_Data
with the sum of multiple samples. However, this is almost an impossible
situation in a control loop – even if we set OSR to 0, the shortest sample
acquisition time would be 6 milliseconds, so to accumulate 512 samples would
take over 3 seconds, which is a too large interval for any reasonable control
loop.
No comments:
Post a Comment