Measuring Speed of Light with Arduinos and no moving parts

I have laser transmitterers and laser sensor modules for some time, and I am excited on the high performance of Arduino Due (that outperforms all other Arduino models including the new ones clearly).

My first thoughts on the current topic were these:

  • light takes 3.3ns to travel 1m
  • Arduino Due clock frequency is 84MHz
  • one clock cycle is therefore 1000/84=11.9ns
  • therefore light travels 3.6m in one Arduino clock cycle

That is a challenge. Our living room is 7m long, when opening kitchen door that can be increased to 9m, and with a mirror on the other side I can get 18m measurement distance at home -- which translates to 5 Arduino Due clock cycles.

I searched this forum for "speed of light" and found some threads and discussions, but no solution. Searching the web I found that speed of light typically gets measured with rotating mirrors or rotating gear wheels. I liked the idea to "just measure the time" for the light "to travel a given distance". I wanted to do it without moving parts, and I wanted to try it with Arduinos.

The laser transmitter has some delay, as well as the laser sensor modules. I wanted to cancel out these delays by delta measurements. So I came up with this experiment setup:

Of course (30+34.5) cm is by far too low, light travels less than 0.2 Arduino clock cycles for that distance, later the 2nd mirror would be placed 7m or 9m away from the laser.

Short description of the experiment:

  • laser transmitter sends a beam
  • half of the beam gets reflected at edge of mirror 1 (N) and goes to sensor 1
  • the other half does not hit mirror 1, gets reflected at mirror 2 and reaches sensor 2

I have a lot to write and photos to show, so my plan is to not write one monster posting but start this thread with several postings, the last one being on discussion of the results, discussions I had with colleagues at work, next steps, questions.

But before submitting this posting and starting this new thread, let me summarize the results obtained sofar:

  • speed of light c=299792.458 km/s
  • the order of magnitude of measurements is correct !
  • the 14m measurement calculation gives 56313 km/s (18.7% of c)
  • combining different measurements calculation gives 67355 km/s (22.5% of c)
  • bill of material for the whole experiment is 30$ only (2 Arduino Due, 1 Arduino Uno, ...)

The pictures/diagrams/photos that will be shown in the next postings are displayed not that big. You will get much better resolution by right clicking them and open in original size.


OK, first steps I did was to verify the experiment can be done and there is no road blocker.

As very first step I verified that splitting of laser bean can be easily done (even without Arduino, see the two laser points on wooden wardrobe):

Then I skrewed the laser transmitter and two laser sensors tightly on wooden Ivar shelf and verified that after 2x7m laser beam can hit sensor 2 (you can see that sensors shadow at kitchen door, both sensors are lit):

Next was the question on how precise timing can be done with Arduino Due. I started thread "Is there an Intel RDTSC equivalent clock counter on Due SAM3X8E microcontroller?" and was really happy on what I learned:

The tuple "( millies(), SysTick->VAL )" is a 84MHz precision counter, so measuring durations of less than 1ms can be done easily with Arduino clock cycle precision:

// only for durations <1ms
uint32_t ticks_diff(uint32_t t0, uint32_t t1) {
  return ((t0 < t1) ? 84000 + t0 : t0) - t1;

I did trigger two interrupt pins on a single Due (with another Arduino Due, just two digitalWrite()s) and they just executed "t0 = SysTick->VAL" and "t1 = SysTick->VAL" in Interrupt Service Routines (ISR) with volatile (needed) uint32_t variables t0 and t1. Unfortunately the durations reported were >200 Arduino clock cycles. That was a road blocker for measuring differences of less than 5 clock cycles with a single Arduino Due.

The solution I had in mind was just to use one Arduino Due per laser sensor.

Next I investigated the reliability of Arduino Due clock cycle duration measurements on different Arduino Dues. I used two Arduino Due with shared Reset line and did clock cycle duration measurement with same sketch on both. They show the same 206 clock cycle difference every time, with slightly different clock after reset values:

So with two Arduino Due for the two laser sensors I was good to go.

Next I had to "debug" laser beams which I had never done before. It turned out that a piece of paper

or a big rubber

can be used as screens for finding out current beam location and how to change it to finally hit the sensor. I used two 9V block batteries (depleted) for fixating the mirror. It worked, but is a really fagile solution!

I used an Arduino Uno to send a signal (over same length cables) to both Dues to take an initial timestamp, and then turned on laser after some delay (to make sure the ISRs on both Dues definitely had completed). So I had to control 3 Arduinos at the same time -- good that the laptop has 3 USB interfaces :slight_smile: I started three terminals (I use Linux -- left, middle, right), started Arduino 1.6.4 IDE in each of them and then used 3 Serial Monitors with that setup:

OK, this was the initial setup already described in diagram of initial posting of this thread:

So here seems to be the place to list bill of material for the experiment:

  • two 12$ Arduino Due
  • one 2.70$ Arduino Uno
  • one 0.48$ red laser transmitter
  • two 0.85$ laser sensor modules
  • one 0.35$ 5x5 mini breadboard
  • one 0.50$ bidirectional logic level converter
  • some breadboard connectors
    This is 30$ in total, prices are from where I did buy all that stuff (all with free shipping, 15-34 days delivery time from China).

With that setup I did first measurements as exercise to make sure hardware is correctly setup, sketches are as they should, get some measuring handson.

I did always press the reset button on one Arduino Due (that triggered Reset on both Dues by a shared Reset link), and then immediately pressed the reset button on the Uno. I did that 36 times to get an idea, here are the results:

average(medium distance duration) - average(short distance duration) is 5.5 but should be either 0 or 1 (because light travels 64.5cm in less than an Arduino Due clock cycle).

To complete the hardware side (there are not much connectors needed) an ASCII wiring diagram:

      Laser      Uno        Level Shifter 
      =====      ===        =============       D23(Due1)
      "-" ------ GND ------ GND       GND        |
      "M"        D4  ------ H1         L1 -------+
      "S" ------ D5         H234     L234        |
                 5V  ------ HV                  D23(Due2)
                 3.3V ---------------- LV
Sensor1    Due1   |   Due2    Sensor2
=======    ====   |   ====    =======
GND ------ GND ---+--- GND ------ GND
OUT ------ D25         D25 ------ OUT
VCC ------ 3.3V       3.3V ------ VCC
           Reset --- Reset

And here is the (simple) software side.

This is the Uno sketch used:

volatile unsigned cnt=0;

#define D(stmts) stmts; stmts; 

void setup() {

  // signal both Arduino DUEs to take 1st timestamp (via level switcher)

  // generate some small delay (96 increments of volatile unsigned var)

  // turn on laser for 100ms

  // turn off laser

  // cleanup

void loop() {}

This is the sketch running on both Due:

volatile uint32_t t0=0, t1=0, cnt=0;

// Interrup Service Routines (ISR)

// ISR for taking first timestamp, triggered by Arduino Uno
void int23(void) {
   t0 = SysTick->VAL;

// ISR for taking second timestamp, triggered by laser sensor
void int25(void) {
   t1 = SysTick->VAL;

void setup() {
  // register ISRs
  attachInterrupt(digitalPinToInterrupt(23), int23, RISING);
  attachInterrupt(digitalPinToInterrupt(25), int25, RISING);

  // wait 3 seconds for interrupts to happen

  // report timestamps taken
  Serial.print(" ");
  Serial.print(" (");

  // report timestamp delta
  Serial.println(") ");

void loop() {}

Ok, here is the 13.7m experiment done.

This is the "lonely" mirror at the terrace door on other end of the room:

This is the wooden Ivar shelf based experiment at kitchen door:

And these are the measurement results:

The difference of short and long averages is 20.1 and should be either 3 or 4 because light travels 10.8m / 14.4m in 3 / 4 Arduino clock cycles.

Anyway the 56314 km/s calculated (18.7% of c) are cool as a first shot.

In thread "Arduino Due high precision tick/timestamp taking and diff functions" I posted some functions based on what I learned while "Measuring Speed of Light" (MSoL) work:

I knew that Arduino Due can be overclocked and did that in the past (84/96/114 MHz):

But I was surprised to learn in that thread from ard_newbie that he was able to overclock his Due up to 234MHz. He said that this is at the edge of what a Due can do, and some Due might work that fast, while another do not.

I did some short experiments yesterday with the only Due at hand (will do more when back home on weekend, there are 3 more Dues), and my Due was not capable to report 234 MHz, but it successfully reported 216 MHz!

Next I tried simple Blink sample and found it did not work at 216 MHz -- I do not want to rely on an overclocked system that cannot run Blink sample. The reason I identified was that "delay()" does not work anymore.

I tried to push working Blink to the limit of my Due, and was surprised that replacing "delay(1000)" with "delayMicroseconds(1000000)" was all that was needed to get Blink sample working at 192 MHz (but not higher).

This Blink program now works, and it does so fine for some minutes. But at some point in time blinking just stops! I verified that original Arduino Blink sample can blink forever at 84 MHz. But some minutes is fine, I do need increased clock cycle timestamp measurement for only some seconds for MSoL experiments!

At 192 MHz light travels only 1.58m instead of 3.6m per Arduino clock cycle.
This increases clock cycles from 5 to more than 11 for my 18m living room+kitchen+mirror distance.

This is sketch from ard_newbie in the other thread with my Blink modifications:

volatile uint32_t cnt=0;

Pio *p = digitalPinToPort(13); // B  
uint32_t b13 = digitalPinToBitMask(13);

void setup() {
uint32_t SCC, mode=31, t0=0, t1=0; 
// 31: 192MHz  digitalwrite,delayMicroseconds not(delay)

  pinMode(13, OUTPUT);
  // 38 for 234 M Hz 
        SCC = SystemCoreClock;

  t0 = SysTick->VAL;
  t1 = SysTick->VAL;
  // 13 for 84 M Hz restore initial clock speed   
  Serial.print(" SystemCoreClock = "); 
  Serial.print(SCC/pow(10,6)); Serial.println(" M Hz"); 
  Serial.println( ((t0<t1)?84000+t0:t0) - t1 );

void loop() {
  digitalWrite(13,  HIGH);
//  p->PIO_SODR = b13;   
//  delay(1000);

  digitalWrite(13,  LOW);
//  p->PIO_CODR = b13;
//  delay(1000);

void Clock_System(uint8_t clock_mul) {

                            | CKGR_PLLAR_MULA(clock_mul) \ 
                            | CKGR_PLLAR_PLLACOUNT(0x3fUL) \
                            | CKGR_PLLAR_DIVA(0x1UL))

/* Initialize PLLA to X MHz */
while (!(PMC->PMC_SR & PMC_SR_LOCKA));
 /* Setting up prescaler */
while (!(PMC->PMC_SR & PMC_SR_MCKRDY));

 /* Update SystemCoreClock */

/* For your experiment you don't need to re-synchronize UART 
 so don't serial print when your uc is running at 236 M Hz */

Determining a better approximation to speed of light from the measurements done is possible.

Colleagues told me that I cannot rely on that both laser sensors used have identical delay behavior. That said I realized that I already had two measurements for same sensor 2, the medium and the long from both experiments done sofar.

Doing the math for the corresponding distances shows better result 67355 km/s (22.5% of c) :

$ bc -ql

I discussed what I did with a hardware engineer for the product I am working for since 9 years.

He told me that ultra short duration measurements can be done with using a Ramp interpolator. Using a 100μF capacitor and a 12bit A/D resolution the Arduino Due has a two digit picosecond measurement resolution can be achieved. That would allow to "see" differences for light moving every 3.3cm!

Another option is to use a delay genarator like the AD9501 and determine the real time taken by interval search of the programmed delay and the measured delay. That chip can generate delay at 10ps resolution and should give a difference of 3 for each centimeter the light travels.

Back to what can be done more easily with the experiment as described above.

Some colleagues told me the the brightness of the laser beam hitting the laser sensor also has an effect. As you can see here (copied out/together from two photos above) the laser beam is definitely not point-like after 14 meters:

So only a small part of the laser beam photons hit the laser sensor after a longer distance, and therefore less photons are there to hit laser sensor electrons for a given time frame, and therefore it takes longer for the sensor to trigger for longer distances.

OK, this seems to be all I have for now, next steps on the weekend:

  • try with 192MHz for better time precision (after more stability/reliability experiments!)
  • compare measurements with same sensor for longer distance,
    but closer together for having less brightness difference

If you have any recommendations or ideas on how to increase measurement precision for above described MSoL experiment, please respond here.


Although this is not rocket science, here are some ideas I would experiment to realize this measurement :

1/ Hook a first light sensor to ADC channel 7(A0)

1/ Code DAC0 to trigger the laser beam for 200 ns and at the same time start an
ADC PDC DMA to record the output of ADC channel 7. Code your ADC for a low resolution on 10 bits instead of 12 by default because the conversion is faster.

To wait for a precise number X of ticks, you can use X times the NOP instruction, because the NOP does nothing and waits exactly for 1 tick.

Calibrate your ADC PDC DMA to know how many words are affected by the high level of the light sensor for 200 ns and detect the level for a rising edge above the noise. Now you have a ratio : number of Words for 200 ns

2/ Hook a second laser sensor to ADC channel 6 (A1), same model as the first one.

Code DAC0 to trigger the laser beam for 200 ns and at the same time start an
ADC PDC DMA to record the outputs of ADC channels 7 and 6.

See how many words are affected by the rising edge of sensor 1 and how many words are affected by the rising edge of sensor 2 in the ADC PDC DMA recording, subtract the 2 numbers.

Permut laser sensors 1 and 2 and do II/2 again. You will have an average of the 2 measurements.

With the previous calibration of the ADC PDC DMA, you know exactly the corresponding time between the rising edge of sensor 1 and the rising edge of sensor 2 (which should be around 50 ns).

With this method, you don't have to bother with the laser starting time and/or the sensor reactivity .

With this method, you get the maximum precision thanks to the super fast PDC DMA.
There is an even faster recording method with the Peripheral to Memory Advanced High performance bus DMA (AHB DMA) but seems to be very tricky to use.

To enhance the UC speed, start Clock_System with the max you can just before you trigger DAC0 and restore 84 M Hz with Clock_System (13) just after the 200 ns recording. Of course Clock_System should be used with the same number for calibration and final experiment.

Thanks, I will try.

I never did "ADC PDC DMA", found this thread and hope it is related:

Code DAC0 to trigger the laser beam for 200 ns

The laser transmitter I have is a 650nm 5V laser. And I tested that it does not work with 3.3V (beam is nearly not existent). I did run "03 Analog Fading" demo on DAC0 and the scope says that not even 3V are reached:

Is this 3V laser transmitter what is needed for working with DAC0?

If I understand correctly your method to read input avoids use of ISR, at least for the initial steps. How is the >200 clock cycles overhead avoided when doing the measurements for two events later (on same Arduino Due?)?


I read the instructions again and now I see that step III does the measurements without any interrupt involved.

So instead of using a 3V laser I do not (yet) have I will try to trigger my 5V laser from Arduino Due for 200ns via the bidirectional level converter.

The only thing left for me is to understand how "ADC PDC DMA" works. "See how many words are affected by the rising edge of sensor 1" and "DMA" seems to mean that the measurements get stored in (consecutive) memory cells, sounds really interesting.


Hello HermannSW

For PDC DMA see for example this thread from reply 8 and after : speed of analogRead - Arduino Due - Arduino Forum

In your case, you will not use circular buffers, only one DMA buffer, no Next DMA buffer (0) so the sample rate would be better. A 1 M samples per second is clearly not enough. I think 10 M samples per second is a minimum for this experiment.

Then recording process could be that one:

Record one analog input at a time for maximum speed:
You place a sensor at the location of sensor 1, you hook this sensor on A0, start your program and record the rising edge with only A0 enabled. You record N1 words before the rising edge.

Then you place the same sensor at the location of sensor 2, you hook this sensor on A0 , start your program and record the rising edge with only A0 enabled. You record N2 words before the rising edge.

There is no overhead with the example pulse length of 200 ns used for calibration, it could be 2000, this is only for calibration: how many words recorded for which pulse length, this is proportional.

Use for calibration a buffer size sufficient to cover the pulse length. I think word aligned buffers should accelerate the process.

So you get N3 words between the rising and the falling edges of a 2000 ns pulse.
( (N2 – N1)/N3) * 2000 = T ns

With the distance D = (lasermirror2sensor2) – (lasermirror1sensor1)
D/T is the speed of light

Since your laser must be supplied with 5 volts, the DAC will not do the job because its output is in the range of 1/6 * 3.3V minimum and 5/6 * 3.3V maximum. A high level on a PIO and a logical level shifter should do the job.

In case ADC PDC DMA sampling is not sufficient, I think inline assembly code will be necessary to have a precise count of clock cycles inside interrupt functions.

If i would try this, i'd consider using SPI Dma
I quickly googled, and think this dma can go up to 42 Mhz. Which means you can at a speed of 42Mhz send data to a pin (the MOSI pin of the SPI), and receive data from an other pin (the MISO pin of the SPI).

So if you'd send the signal 1111111111 (with the MOSI pin connected to the laser, with a transistor in between i assume)
And received 0000111111 (with the MISO pin connected to the sensor, and its signal is strong enough te be registered as a 1)
Then the signal was underway for about one 10.000.000th of a second.

You'd first have to calibrate this by setting them right next to eachother, and seeing how much delay you have without any distance in between (since i assume the laser needs time to fire up, and the sensor to receive it).
And then you can start doing experiments with a very fine resolution :).

Thanks racemaniac, perhaps I will have to come back to this.

Thanks ard_newbie for the pointer to the thread needed, I took stimmer's sketch fronm this posting in the thread as basis for my experiment (I used DueVGA library from stimmer a lot):

The version with the differences I made is attached.

Let me discuss the differences I made.

First I added some variables, a macro for doubling statements passed in and D21 macro. D21 increments a variable 21 times which takes exactly 21 clock cycles. Compiler cannot optimize away (I always use -O3) the statements because the variable is declared volatile:

> // C.23 = D7
> Pio *p = digitalPinToPort(7);
> uint32_t b7 = digitalPinToBitMask(7);
> uint32_t volatile cnt=0;
> #define D(stmts) stmts; stmts;
> #define D21  D(D(D(D(cnt++)))) D(D(cnt++)) cnt++;

Next I added a buffer and made a new buffer cycling. The effect is that buf[0] gets filled once and will not be touched anymore. This allowed me to do my experiments without having to understand how to stop the analog reads:

< uint16_t buf[4][256];   // 4 buffers of 256 readings
> uint16_t buf[5][256];   // 4 buffers of 256 readings, 5th special
<   bufn=(bufn+1)&3;
>   bufn=(bufn&3)+1;      // cycle 0123412341...

I did measure the clock cycles a complete buffer fill with 256 converted analog values takes, it was less that 36000 clock cycles or 428μs(!).

I don't need native USB data output but just some debug output. I did connect pin D7 with A0, so set D7 to LOW initially:

<  SerialUSB.begin(0);
<  while(!SerialUSB);
>  Serial.begin(57600);
>  while(!Serial);
>  pinMode(7,OUTPUT);
>  p->PIO_CODR = b7; // digitalWrite(7,LOW);

Here comes the area where I did all my experiments. First 512 clock cycles are spent to be at the point the first analog value has been converted. Then each D21 uses 21 clock cycles and lets D7 on low for one more analog conversion. After setting D7 to HIGH and waiting for buf[0] being completely read, the first 10 values are written to Serial:

>  D(D(D(D(D(D(D(D(D(cnt++)))))))))  // wait until 1st analog capture
>  D21 D21 D21 D21 
> // D21 D21 D21 D21
>  p->PIO_SODR = b7; // digitalWrite(7,HIGH);
>  while(obufn==bufn); // wait for buffer to be full
>  obufn=(obufn&3)+1;    
>  for(uint32_t i=0; i<10; ++i) {
>    Serial.print(buf[0][i]);
>    Serial.print(" ");
>  }
>  Serial.println();

The last diff is just omitting sending back read data to Serial:

<  SerialUSB.write((uint8_t *)buf[obufn],512); // send it - 512 bytes = 256 uint16_t
<  obufn=(obufn+1)&3;    
>  obufn=(obufn&3)+1;

This is example output after pressing Reset button several times:

4 4 2 5 2 4095 4095 4095 4095 4095 
3 4 2 5 2 4095 4095 4095 4095 4095 
3 3 2 5 2 4095 4095 4095 4095 4095 
3 3 2 4 3 4095 4095 4095 4095 4095 
4 4 3 4 2 4095 4095 4095 4095 4095 
3 4 2 4 2 4095 4095 4095 4095 4095 
3 3 2 4 1 4095 4095 4095 4095 4095 
3 3 2 4 2 4095 4095 4095 4095 4095 
3 3 2 4 2 4095 4095 4095 4095 4095

The 21 clock cycles are absolutely reliable, I tested once with 38 D21 statents.

The rising edge is really steep, I reduced last D21 to 20, 19, ... increments. I saw "... 2 5 4081 4095 ...", which basically means that the rising edge takes just one ADC conversion.

Sofar so good, but 21 clock cycles per completed analog read means 21*11.9ns=250ns. Light travels 75m in that time ...

But I remember that you told me to reduce ADC resolution from 12 to 10 bits, will be the next thing to do.

The ADC frequency is set to maximum which is ADC_FREQ_MAX = 20,000,000.

12bit->10bit and overclocking might help to reduce the time needed for a single analog conversion, but maybe not by a factor of 5 to fit into my home.

Is Due chip capable of doing digital DMA reads? Instead of reading an analog pin and converting it in 21 clock cycles, just reading a digital pin and say write it in 2 clock cycles with DMA to Arduino Due's memory? That would be the solution, since my laser sensor already does the conversion to 0/1 on the module, and I used that signal in the experiment described.


sketch_jun11b.ino (1.71 KB)

Is Due chip capable of doing digital DMA reads? Instead of reading an analog pin and converting it in 21 clock cycles, just reading a digital pin and say write it in 2 clock cycles with DMA to Arduino Due's memory? That would be the solution, since my laser sensor already does the conversion to 0/1 on the module, and I used that signal in the experiment described.


That's why i said to use SPI dma :slight_smile:
it'll do exactly that for you :slight_smile:

The main reason i'd use SPI is because then you know you no longer have to worry about any other processor overhead. The spi will 42 million times per second send a bit to a pin, and receive data from a pin. You want something very predictabe, and very well synced, SPI will do that for you. Any other methods i'd expect you to have a lot of noise or delays to take into account since your code will take time to execute, the things you use have latencies, .... certainly when writing this in c :).