# MPU6050 Timing and Sampling: Why a 1 kHz Sensor Does Not Sample Every 1000 µs

> How to measure an MPU6050's real sampling interval from inside the MCU, using the Cortex-M DWT cycle counter on the data-ready interrupt. Covers the clock source that made the sensor 1.2 % fast, how measurement resolution hides jitter, and why an integral needs a measured dt.

![A GY-521 sensor module and an STM32F4 Discovery board on a dark grid, linked by a glowing pulse train, with a magnifier enlarging a few of the pulses and dimension arrows marking the interval between them](https://embed-studio.com/assets/hero.fgSRcs0S.jpg)

- Thread: MPU-6050 Journey (Experiment #004)
- Published: 2026-08-16
- Tags: Embedded, IMU, MPU6050, STM32, Timing, Jitter, Sensor Fusion, EmbedStudio
- Canonical page: https://embed-studio.com/experiments/mpu6050/experiment-004-mpu6050-timing-and-sampling

The [bring-up article](https://embed-studio.com/experiments/mpu6050/experiment-001-bringup.md) has one line
in it that the rest of this series has been quietly standing on. While clearing
the sleep bit, it says, it is worth selecting a gyroscope PLL as the sensor's
clock source instead of the internal oscillator.

That line came out of a measurement, and the measurement deserves more than a
line. On its default clock source, this MPU6050 was **1.2 % away from the sample
rate it had been configured for**, and its sampling was ten times less steady.
Nothing in the sensor data showed either. Every angle integrated from that data
would have carried the error, and no amount of calibrating the gyroscope would
have removed it.

That is what this article is about.

## How close is 1 kHz to the real thing?

The MPU6050 can be configured for a nominal **1 kHz sample rate**, and that
sounds simple enough: one sample every millisecond.

But a real embedded system is not an ideal clock. The sensor has its own timing
system, the MCU has its own clock reference, and firmware execution adds effects
of its own. The sample rate register does not really set a period. It sets a
divider, and the period it divides is whatever the sensor's clock happens to be
running at.

For many applications, small timing variations are irrelevant. For others, they
matter a great deal. Gyroscope integration depends directly on the elapsed time
between measurements. Frequency analysis depends on the sampling frequency.
Sensor-fusion algorithms need to know how much time has passed between updates.

So instead of assuming that 1 kHz means exactly 1000 µs, let's measure it.

## The Experiment

The sensor's **DATA_RDY** signal is connected to an STM32 GPIO configured to
generate an interrupt. Every time the MPU6050 indicates that a new sample is
available, the interrupt handler captures the current value of the DWT cycle
counter.

For consecutive samples, the firmware calculates:

```
dt[n] = t[n] - t[n-1]
```

The measured `dt` is then recorded alongside the sensor data.

The important point is that the timing is measured **inside the MCU**, a few
cycles from the event being investigated. We are not measuring the time between
packets arriving at a PC. That would include communication and software delays
which have nothing to do with the sensor's actual sampling timing.

## Why Not Use the Standard HAL Tick?

The STM32 HAL already provides convenient timing functions, so why introduce a
separate timing module?

The answer is **resolution**.

A typical HAL system tick provides a time base with millisecond resolution. That
is perfectly adequate for timeouts, delays and task scheduling, but it is not
suitable for investigating variation around a 1 ms sampling period. Consider
these intervals:

```
998 µs    1001 µs    1003 µs    997 µs
```

They are all the same number of milliseconds. A ruler graduated in millimetres
cannot measure the difference between two objects a millimetre long.

The DWT cycle counter works at the MCU core-clock resolution instead. It is a
free-running 32-bit register inside the Cortex-M core that increments once per
core clock cycle, so at 168 MHz it resolves **5.95 ns**. Reading it is a single
load from a memory-mapped address — no peripheral, no interrupt and no HAL call
in the path, which is what makes it usable from inside a handler.

Two properties are worth knowing before relying on it. It rolls over every 2³²
cycles, which at 168 MHz is 25.6 s. That does not matter for intervals, because
unsigned subtraction in C is defined modulo 2³², so the difference of two
timestamps stays exact as long as the interval itself is shorter than the
wraparound period. One millisecond has a comfortable margin. And on some parts
the DWT is held in reset until a debugger attaches, so the counter has to be
enabled explicitly rather than assumed.

This makes the DWT particularly useful for:

- measuring short time intervals;
- measuring firmware execution time;
- investigating interrupt latency;
- evaluating sampling jitter.

The timing functionality used in this experiment is implemented as a reusable
project module. Its detailed implementation and API are outside the scope of this
article and will be covered separately.

## Measuring the Sampling Interval

The timing reference is the MPU6050 **DATA_RDY event**. Each event indicates that
a new sensor sample is available. The STM32 captures the current DWT counter
value and compares it with the previous one, and the difference between the two
is the actual interval between consecutive sensor events.

![The MPU6050 internal clock drives the data-ready line, which raises an STM32 interrupt in which the DWT cycle counter is read, and below it two consecutive data-ready pulses with the interval between them marked](https://embed-studio.com/assets/timing-measurement-chain.B79V18gj.svg)
*Figure 1 — Measuring the sampling interval. Two consecutive MPU6050 DATA_RDY events are timestamped by the STM32, and the difference between the timestamps gives the measured `dt`.*

This gives us something the MPU6050 configuration registers cannot provide: **an
actual measurement of the timing produced by the sensor.** `SMPLRT_DIV` says what
the sensor was asked to do. The interval between two data-ready edges says what
it did.

## Experimental Setup

The hardware is the same basic platform used in the previous MPU6050 experiments:

- STM32F4 Discovery
- GY-521 module containing the MPU6050
- I²C connection
- MPU6050 DATA_RDY signal connected to the STM32

The sensor is configured for a nominal 1 kHz output rate with the digital
low-pass filter enabled.

The STM32 is clocked from the external **HSE oscillator** rather than the
internal HSI. This provides a more stable and predictable clock reference for the
DWT-based timing measurement, and a timing reference is exactly the wrong place
to save a crystal. Every number below is quoted against the resulting 168 MHz
core clock.

The I²C interface is used to read the sensor data, but it is **not** the timing
reference for this experiment.

![STM32F4 Discovery board wired to a GY-521 MPU-6050 module over a ribbon cable, carrying the I²C pair and the sensor's interrupt line](https://embed-studio.com/assets/hardware-setup.Cz8z0si6.png)
*Figure 2 — Experimental setup showing the STM32F4 Discovery and GY-521/MPU6050, with the I²C connection and the DATA_RDY signal used as the timing reference.*

The distinction is important. The I²C transaction tells the MCU **how to obtain
the data**. The DATA_RDY event tells us **when the sensor indicates that a new
sample is available**. For this experiment, the second one is the timing
reference, so a slow or retried I²C transaction cannot move the result.

## Part 1 — What Is the Real Sampling Period?

### The First Measurement

The nominal sampling period for 1 kHz is:

```
Fs = 1000 Hz
T  = 1 / Fs = 1 ms = 1000 µs
```

So the first expectation is simple: `dt ≈ 1000 µs`.

But the first measurement did not look as clean as expected. Instead of a tight
distribution around 1000 µs, the measured intervals showed a substantial
deviation from the nominal value. Averaged over 41,729 samples, the observed
period was **987.7 µs**, which corresponds to an effective sampling frequency of:

```
Fs = 1 / 987.7 µs = 1012.4 Hz
```

This was unexpected. The MPU6050 was configured for 1 kHz, so why were the
measured intervals so far from 1 ms?

![A plot of the measured sampling interval against time, the trace sitting as a noisy band around 988 microseconds well below the nominal 1000 microsecond reference line](https://embed-studio.com/assets/sample-period-internal-clock.CsSfk_M_.png)
*Figure 3 — Initial timing measurement, with the sensor on its default internal oscillator. The trace sits about 12 µs below the `nominal (1000)` line and wanders across roughly two microseconds; the cursor reads one sample at 987.5 µs, and the mean over all 41,729 is 987.7.*

A little over one percent is small enough to dismiss and large enough to matter.
It is a rounding error in a control loop and a serious problem in an integral. At
this point, simply reporting the measured number would not be enough. The
interesting question became:

> **What is causing the discrepancy?**

### Investigating the Clock Source

The MPU6050 does not derive its internal sampling timing from the STM32 clock. It
has its own internal clocking system, including a selectable clock source, and
`SMPLRT_DIV` divides that internal clock down to the output rate. The divider is
exact. The clock being divided is not necessarily what you assume.

The initial configuration used the MPU6050's default source, which is an internal
8 MHz relaxation oscillator — cheap, always available, and not built to be a
frequency reference. The register map is unusually direct about this and
recommends selecting a gyroscope PLL instead, which ties the sensor's timing to
the gyroscope's own resonator.

So the `CLKSEL` field of `PWR_MGMT_1` was changed from `0x00` to `0x03`,
selecting the PLL referenced to the Z gyroscope. Any of the three gyroscope
references will do — `0x01`, `0x02` and `0x03` select X, Y and Z, and the
bring-up article picks X.

The STM32 clock and the DWT timing reference were not changed. Neither was the
sample rate divider, the low-pass filter, or either full-scale range.

![The PWR_MGMT_1 register with its lowest three bits highlighted as the clock select field, and the two values compared side by side](https://embed-studio.com/assets/clock-source-config.BqDSzjcd.svg)
*Figure 4 — MPU6050 clock-source configuration using the gyroscope PLL. The sample rate divider was identical in both measurements; only the clock it divides changed.*

After repeating the measurement with the same 1 kHz sensor configuration, the
measured period moved to **1000.9 µs** — 999.1 Hz, within 0.1 % of nominal. The
sample-to-sample variation improved at the same time, and by more: the standard
deviation of the interval fell from **271 ns to 27 ns**, a factor of ten.

![The same plot after the clock-source change, the trace now a thin flat line lying just above the nominal 1000 microsecond reference](https://embed-studio.com/assets/sample-period-gyro-pll.oe-wOmHy.png)
*Figure 5 — The same axes and the same channels as figure 3, with one register value different. The interval lands on the nominal line and the band collapses to a fraction of its former width; the cursor reads one sample against a mean of 1000.89 µs over all 32,114.*

Nothing changed on the board but that one register, and temperature cannot
account for the shift. Four PLL captures spanning **30.6 to 33.9 °C** agree
within **0.06 µs** of each other. Over much the same interval the internal
oscillator moved **2.36 µs**, from 990.1 µs at 31 °C to 987.7 µs at 33 °C. It
drifts about forty times further with temperature than the PLL does.

And the change of clock source moved the period **13.1 µs**. That is two hundred
times the PLL's own drift, so there is nothing else it could be.

Those are raw movements between captures, not a fitted coefficient, and none is
offered here. But the bring-up article's "measurably more stable over
temperature" is now a number rather than a claim.

This was a particularly useful result, because it showed that the observed timing
was not a property of the STM32 measurement system. The sensor configuration had
not changed from 1 kHz. The STM32 timing reference had not changed. The internal
clock source of the MPU6050 had changed — and the measured sampling period
changed with it.

This is also where the bring-up article's one-line recommendation came from. It
was written as a stability argument, and the frequency error above is the rest of
the reason. The write has been in the firmware ever since, so **every published
capture in this series was taken on the PLL and no earlier result moves**. The
gyroscope article measured the sensor at 999 Hz over 961,000 samples on two
independent clocks, which agrees with 1001 µs to better than a tenth of a
percent.

The uncomfortable part is what it would have cost to leave alone. A 1.2 % error
in the sample period is not visible in a sensor reading and trips no check. It
also survives a full calibration, because a calibration that uses the wrong `dt`
on both sides of its arithmetic just folds the error into its own constants.

### The Corrected Measurement

After switching to the gyro PLL clock source, the sampling period was very close
to the expected 1 ms. In microseconds, the result looks remarkably stable:

```
1001 µs    1001 µs    1001 µs    1001 µs    ...
```

That is not an abbreviation. Rounded to whole microseconds, all 32,114 samples in
the capture reported **1001, and nothing else** — minimum 1001, maximum 1001,
standard deviation exactly zero.

At first glance it would be tempting to conclude that there is essentially no
jitter. But that conclusion would be premature. We have only looked at the
measurement with **microsecond resolution**.

What happens if we look closer?

## Part 2 — How Much Jitter Is Really There?

### Measurement Resolution Matters

The same measured interval can be expressed in microseconds, in nanoseconds, or
in raw CPU clock cycles. All three represent the same physical interval, but they
do not reveal the same amount of detail.

At microsecond resolution, several slightly different measurements collapse into
the same displayed value. For example:

```
1000.7 µs    1001.1 µs    1001.4 µs    1001.8 µs
```

may all appear as approximately `1001 µs`. Inspect the raw cycle count instead
and those differences become visible.

The firmware measures the interval once, in cycles, because that is what the
counter produces. It then exports the same number three ways: raw cycles, whole
microseconds, and microseconds as a floating-point value. All three describe one
event.

Choosing which of them to look at is a workspace decision in EmbedStudio rather
than a rebuild, and a histogram there counts whatever range a scope hands it. So
both of the figures below are the same capture over the same samples. Only the
ruler differs.

### The Microsecond View

The histogram in microseconds shows a very tight distribution. Most measurements
fall within approximately one microsecond of the central value. It looks almost
like a single solid peak.

![A histogram of the sampling interval in whole microseconds, showing a single tall bar at 1001 standing alone in an otherwise empty axis](https://embed-studio.com/assets/jitter-histogram-us.DS_jBOap.png)
*Figure 6 — The interval in whole microseconds, over a 64 µs wide axis. Every one of the 32,114 samples falls in the same bar, and the panel reports minimum 1001, maximum 1001 and a standard deviation of zero.*

From a practical perspective, this is an excellent result. For many applications,
sub-microsecond variation is completely irrelevant. But the measurement contains
more information than this histogram shows.

### Looking at the Cycle Counter

Now we change only the representation. Instead of displaying the interval in
microseconds, we display the number of **CPU cycles** between consecutive
DATA_RDY events. The underlying measurement has not changed. Only the resolution
has changed.

And suddenly the distribution is no longer a single solid peak. It is a bell.
The same 32,114 samples that took one value in microseconds take **28 distinct
values** in cycles.

The reason is a mismatch of scale. At 168 MHz one microsecond is **168 cycles**,
and the real variation here spans about **22 cycles** — roughly an eighth of the
rounding step. Anything that much narrower than the ruler cannot survive being
measured with it.

![A histogram of the same intervals counted in CPU clock cycles, resolving into a bell-shaped distribution about twenty cycles wide](https://embed-studio.com/assets/jitter-histogram-cycles.BePODN3I.png)
*Figure 7 — The same 32,114 samples, counted in core clock cycles instead. The single bar resolves into a distribution centred on 168,149 cycles with a standard deviation of 4.50 — 27 nanoseconds, and about an eighth of the microsecond that hid it.*

This is an important lesson:

> **The absence of visible jitter does not necessarily mean the absence of
> jitter. It may simply mean that the measurement resolution is too coarse to
> reveal it.**

Neither view is more "correct" than the other. Figure 6 answers "is this good
enough for my control loop", and the answer is yes with room to spare. Figure 7
answers "what is actually happening", which is the question you need when
something later does not add up.

### What Does the Jitter Actually Mean?

It is tempting to look at the cycle-level histogram and conclude that every
variation represents a problem. That would also be incorrect.

A real digital system contains many sources of small timing variation. The MCU
clock is not an infinitely precise mathematical reference. Interrupt entry takes
a variable number of cycles. The sensor's internal timing and the MCU clock are
not locked to each other.

The important question is therefore not:

> "Is every sample exactly the same number of CPU cycles apart?"

but rather:

> "Is the timing variation small enough for the intended application?"

For the MPU6050, the answer after correcting the sensor clock configuration is
clearly much better than the initial measurement suggested.

## Part 3 — Can We Trust the Measurement?

### The Timestamp Is Taken Inside an Interrupt

There is another subtlety. The DATA_RDY signal causes an interrupt, and the
timestamp is obtained while servicing that interrupt, so the measurement is not
completely independent of the MCU's interrupt system.

The hardware DATA_RDY event occurred at one moment. The timestamp is captured
later, once the core has finished whatever it was doing, entered the handler and
reached the instruction that reads the counter. Normally that is a fixed, small
number of cycles and it cancels out of the subtraction entirely.

It stops cancelling if something delays entry. This is normally a very small
effect. At microsecond resolution it may be invisible. At cycle resolution,
however, it can become measurable.

### Why Interrupt Priority Matters

This is easy to test, because it is a property of the configuration rather than
of the hardware. Build the firmware twice, changing nothing but which of the two
interrupts outranks the other, and capture the same 61 seconds each way.

![Two stacked plots of the sampling interval in cycles over sixty seconds, the upper one an even band and the lower one the same band with occasional tall spikes above and below it](https://embed-studio.com/assets/interrupt-priority-jitter.CwUnlgXb.png)
*Figure 8 — The same firmware, the same board, the same minute of sampling. Above, the data-ready interrupt outranks SysTick and the interval never leaves a twenty-cycle band. Below, the two are swapped, and a tick arriving at the wrong moment pushes the timestamp out by up to sixty cycles.*

The lower trace is not noisier everywhere. Its band is the same width as the one
above it, and the excursions are **33 events in 32,454 samples — one sample in a
thousand**. Each is about **55 cycles, or 330 ns**.

Two details identify the cause beyond doubt. Where both halves were captured the
spikes come in pairs of opposite sign, a long interval and then an equally short
one, which is what a *delayed timestamp* looks like as opposed to a genuinely
different sampling interval. And they are not random in time: they arrive every
**1.11 seconds**. SysTick runs at exactly 1000 µs and the sensor at 1000.9, so
the two drift through coincidence at 0.902 Hz — one collision every 1.109
seconds. That is the cadence in the figure, predicted before it was measured.

The important lesson here is broader than the MPU6050:

> **A high-resolution timer does not automatically make a measurement
> high-accuracy. The measurement mechanism itself can influence what is being
> measured.**

The practical rule follows directly. **If a measurement is taken inside an
interrupt, that interrupt has to outrank everything that is allowed to interfere
with it.** Here that is one line of firmware, it costs nothing, and the upper
trace in figure 8 is what it buys.

There is a trap in how priorities are applied, and it cost a wasted afternoon
here. Setting a priority is not enough on its own, because the number is
interpreted against the current **priority grouping**, and the grouping decides
how many of the bits mean *pre-emption* at all. This project was generated with
`NVIC_PRIORITYGROUP_0`, which allocates **none** of them. Under that grouping
nothing can pre-empt anything, and the priority numbers only decide which of two
*already pending* interrupts is served first.

So a priority set under it looks entirely correct and does nothing at all. Both
configurations above needed the grouping set as well as the numbers, and the
firmware linked at the end of this article now does exactly that — it selects
`NVIC_PRIORITYGROUP_4` and then assigns both priorities explicitly, rather than
inheriting whatever the generated code left behind.

Interrupt latency deserves more than this, and it applies to any
interrupt-driven sampling system rather than to this sensor, so the general
treatment belongs in a note of its own.

### Why Not Simply Use the Average?

At this point we have several different pieces of information:

- nominal sampling rate: **1 kHz**, i.e. 1000 µs;
- initial measured period: **987.7 µs**, an effective 1012.4 Hz;
- corrected measured period: **1000.9 µs**, an effective 999.1 Hz;
- interval variation: **271 ns** before the change, **27 ns** after it;
- microsecond-resolution distribution: a single bar;
- cycle-resolution distribution: 28 distinct values across about 22 cycles.

A single average value would hide most of this story. That is why timing analysis
should look at both the central value and the distribution around it.

## Why Does `dt` Matter?

The timing experiment would be little more than an interesting measurement if
timing had no effect on the algorithms that process the sensor data. It does.

Consider gyroscope integration. Angular velocity is measured in degrees per
second, and to obtain an angle we integrate it over time:

```
angle[n] = angle[n-1] + gyro[n] × dt[n]
```

If we simply assume `dt = 1 ms` for every sample, we are assuming the sampling
interval is perfectly constant. But we have just demonstrated that the actual
interval can differ from the nominal value. If the true interval is 987.7 µs and
the firmware assumes 1000, every angle it computes is 1.2 % too large, and it
grows without limit for as long as the integration runs.

For scale: the [previous experiment](https://embed-studio.com/experiments/mpu6050/experiment-003-mpu6050-gyroscope-calibration.md)
measured one gyroscope axis at 0.9 % above its nominal scale factor and treated
that as worth an article. An unmeasured clock source is the same size of error,
applied to every axis at once and in the same direction — and it costs one byte
to remove rather than a calibration session.

If the firmware has measured `dt[n]`, it can simply use that actual value. This
removes the need to pretend that every sample took exactly the same amount of
time.

### Timing and Frequency Analysis

Timing also matters for frequency analysis, where the sampling frequency is
related to the sample period:

```
Fs = 1 / dt
```

If the actual sampling period differs from the nominal value, the effective
sampling frequency differs as well. A 1.2 % error in `dt` puts a 1.2 % error on
every frequency in the result — a vibration at 100 Hz reads as 101.2 Hz. No
amount of resolution in the transform fixes it, because the error is in the axis
rather than in the data.

Again, the nominal configuration tells us what we **asked the system to do**. The
measured timing tells us what the system **actually did**.

### Timing and Sensor Fusion

The timing information becomes even more important when the accelerometer and
gyroscope are combined into an IMU.

A sensor-fusion algorithm does not only need sensor values. It also needs to know
how much time has elapsed between updates, because that is what sets how much it
trusts the integrated rate before pulling it back towards gravity. Feeding it a
constant while the real interval differs puts a systematic error into the tuning
of the filter itself, which is a much harder thing to debug than a wrong
constant.

![Accelerometer, gyroscope and the measured sampling interval all feeding a sensor fusion stage that produces an orientation](https://embed-studio.com/assets/dt-into-fusion.CryoB3oO.svg)
*Figure 9 — Accelerometer data, gyroscope data and the measured `dt` entering the future sensor-fusion stage. The interval is a third input of equal standing, not a constant baked into the filter.*

This is why timing is worth investigating before building the complete IMU. It
gives us confidence that the `dt` supplied to the algorithm is a measured
quantity rather than an assumption.

## The Timing Module

The DWT-based timing functionality used in this experiment is implemented as a
reusable module in the project. It initialises the cycle counter, caches the core
clock frequency, and provides timing information at different resolutions —
nanoseconds, microseconds, milliseconds, seconds, and the underlying clock-cycle
count. Cycles stay the primary representation, and a conversion happens only when
a human needs to read the value.

It has no dependencies beyond CMSIS. It uses no timer peripheral, no SysTick, no
interrupt and no RTOS, which is what makes it safe to call from inside a handler
and from inside the code being measured. It is useful not only for measuring
sensor timing, but also for measuring firmware execution time and investigating
other timing-related problems.

The implementation and API are intentionally not covered in detail here. A
separate article will describe the module itself, including:

- how the DWT cycle counter works;
- initialization;
- time conversion;
- available measurement resolutions;
- API usage;
- execution-time measurements;
- practical limitations.

The module is available in the project repository for anyone who wants to reuse
it.

## Conclusion

A sensor configured for 1 kHz does not automatically mean that every observed
sample interval will be exactly 1000 µs. This experiment showed several layers of
the problem.

First, the initial measurement revealed a surprisingly large deviation,
**987.7 µs**, despite the nominal 1 kHz configuration. Investigating the MPU6050
clock configuration led to switching its internal clock source to the **gyro
PLL**. The STM32 clock and DWT timing reference were not changed. After that
change, the measured period moved to **1000.9 µs**, and the interval variation
fell from 271 ns to 27 ns.

Then we looked more closely at the measurement. At microsecond resolution, the
sampling period appeared almost perfectly stable. At CPU-cycle resolution, small
variations that had been hidden by the coarse representation became visible.

Finally, we saw that even a high-resolution measurement has its own limitations.
Interrupt latency and interrupt priority can influence when the timestamp is
actually captured.

The main lessons are therefore:

- a nominal sample rate is not the same as a measured sample period;
- the MPU6050's internal clock-source selection can significantly affect its
  actual sampling timing, and the fix is one byte written once at bring-up;
- measurement resolution determines which timing variations are visible;
- high-resolution measurements can reveal effects hidden at lower resolution;
- interrupt latency can influence measurements made inside interrupt handlers,
  and giving the measurement the higher priority removes it;
- measured `dt` can be used directly by time-dependent algorithms.

The most important principle is simple:

**If an algorithm depends on time, measure time instead of assuming it.**

The measurement itself was cheap. The firmware records a cycle count per sample
and does nothing else with it. Everything after that happened in EmbedStudio,
against a recording that was already on disk — switching the histogram from
microseconds to cycles was a change of channel, not a rebuild.

With the sensor calibrated and its timing understood, the next step is to look at
what the numbers underneath are actually made of.

## References

- InvenSense, **MPU-6000 and MPU-6050 Register Map and Descriptions**, Rev. 4.2
  (RM-MPU-6000A-00, 2013) —
  [PDF](https://cdn.sparkfun.com/datasheets/Sensors/Accelerometers/RM-MPU-6000A.pdf).
  Source of the `PWR_MGMT_1` clock-select field, and of the recommendation to run
  the device from a gyroscope reference rather than its internal oscillator.
- Arm, **ARMv7-M Architecture Reference Manual** — the Data Watchpoint and Trace
  unit and its `CYCCNT` cycle counter, the timing reference used throughout.

## Next Experiment

### Experiment #005: How Noisy Is the Signal Underneath?

The interval between samples is measured now, and the values arriving at each of
those instants are calibrated. What has not been characterised is the part of
each reading that is neither signal nor bias. That is the noise: its size, its
shape, and how much of it a filter can remove before it starts removing the
measurement as well.

That question needs a trustworthy time axis before it can even be asked, which is
why it comes after this one. The point of looking at noise is to find where in
the band it lives. A spectrum computed against a sample rate that is 1.2 % wrong
puts every one of those frequencies in the wrong place.

## Frequently Asked Questions

### Does an MPU6050 configured for 1 kHz really sample every 1000 microseconds?

Not necessarily. On the module measured here the interval between data-ready events came out at 987.7 µs with the sensor running from its default internal oscillator, which is an effective rate of 1012 Hz rather than 1000 Hz. Selecting a gyroscope PLL as the clock source in the PWR_MGMT_1 register moved the same configuration to 1000.9 µs, and cut the sample-to-sample variation from 271 ns to 27 ns. The sample rate register sets a divider, not a guaranteed period, and the period it divides is only as accurate as the clock behind it.

### Why does the MPU6050 clock source affect the sample rate?

The sample rate divider counts an internal clock, and the MPU6050 lets you choose which clock that is. The power-on default is an internal 8 MHz relaxation oscillator whose frequency is not tightly specified. Selecting one of the gyroscope PLL references instead ties the timing to the gyroscope's own resonator, which is far more stable. The register map itself recommends this, and the effect is directly measurable at the data-ready pin.

### How do you measure interrupt timing on an STM32 accurately?

Use the DWT cycle counter in the Cortex-M core rather than the HAL millisecond tick. It counts core clock cycles, so at 168 MHz one count is 5.95 ns against the system tick's 1 ms. Read it inside the interrupt handler and subtract the previous reading. Interval measurements survive the counter's 32-bit wraparound because unsigned subtraction is exact modulo two to the power thirty-two.

### Why does a timing measurement look perfectly stable in microseconds but not in cycles?

Because whole microseconds are a coarse ruler. At 168 MHz one microsecond is 168 core clock cycles, and in the capture measured here the real variation spanned only 22 cycles, about an eighth of that step. Rounded to whole microseconds every one of the 32,114 samples reported the same value, so the distribution was a single bar. The same recording displayed as raw cycle counts spread out over 28 distinct values. Absence of visible jitter can mean the resolution is too coarse to show it.

### Should firmware use the measured sample interval or the nominal one?

The measured one, wherever the result depends on elapsed time. Integrating a gyroscope rate with an assumed 1 ms step when samples actually arrive every 987.7 µs scales the resulting angle by 1.2 %, which is the same size as the scale factor error a full calibration exists to remove. A frequency axis derived from the nominal rate is wrong by the same fraction. The measured interval costs one subtraction per sample.

### Can a timestamp taken inside an interrupt handler be trusted?

It is accurate to the extent that the handler starts promptly. The hardware event happens at one instant and the timestamp is captured after the core has entered the handler, so anything delaying entry moves the measurement. A higher-priority interrupt arriving at the wrong moment adds its own service time and shows up as occasional values away from the main distribution. The effect is invisible at microsecond resolution and measurable at cycle resolution, which is a useful reminder that the measuring apparatus is part of the experiment.

## Source Code

The firmware and supporting files for this experiment: https://github.com/Embed-Studio/MPU-6050/releases/tag/experiment_004

- Previous: [MPU6050 Gyroscope Calibration: Bias Compensation and Scale Factor Without a Rate Table](https://embed-studio.com/experiments/mpu6050/experiment-003-mpu6050-gyroscope-calibration.md)
- Next: [MPU6050 Noise and Filtering: How Much Is Noise, and What Does a Filter Cost?](https://embed-studio.com/experiments/mpu6050/experiment-005-mpu6050-noise-and-filtering.md)
