Measuring The Speed Of Light With A Raspberry Pi

(Plus a few other bits and bobs.)

This project was not undertaken to find out the physical value of the speed of light, but to test whether such measurements are possible at home with a Raspberry Pi. This kind of thing would normally be attempted in state of the art optics labs, and then probably by inferring the speed from some other characteristic. I went for a simple solution, turn on a light and see how long until a light sensor detects it.

The Pi used was an old Model B. The CPU is clocked at 700MHz, so it's not quick by modern PC standards. The Raspberry Pi 2 and 3 are faster, but I had a spare mark 1 doing nothing. There are plenty of IO pins available to control and sense things. I just needed one output to turn on a light, and one input to detect the light.

I installed Raspbian on the Pi, enabled SSH so I can log in from a laptop and created my personal user account. The first real task for the experiment was to see how well the Pi can measure time. This piece of C code queries the timing system to see what the resolution is.

//g++ -o querytimer querytimer.cpp -lrt #include #include int main (void) { struct timespec ts; clock_getres(CLOCK_MONOTONIC_RAW, &ts); printf ("Timer resolution: %d\n", ts.tv_nsec) ; return 0; }

The answer surprised me. Here's a cut 'n' paste of my console window.

geoff@raspberrypi ~ $ nano querytimer.cpp geoff@raspberrypi ~ $ g++ -o querytimer querytimer.cpp -lrt geoff@raspberrypi ~ $ ./querytimer Timer resolution: 1

1 nanosecond resolution seemed too good to be true. At 700MHz clock rate, that's faster than the CPU. But I just thought it must be some oddity of the system clocks.

Now I wanted to know if the time delay between outputting a signal on one pin and receiving it on another was detectable. I wrote the following program to test this. The program takes the average of a million readings and waits between each one to allow other threads to act and to allow the IO to settle. I used the WiringPi library to do the IO as I've used it before and know it performs well. After all, I'm going to be measuring the fastest thing there is. The code comments describe what it's doing in more detail.

// Build with this command line to link with WiringPi and the time functions // g++ -o loopbacktest loopbacktest.cpp -lwiringPi -lrt // This define is needed to print out 64 bit ints. #define __STDC_FORMAT_MACROS 1 // Lots of includes for various stuff #include #include #include #include #include #include #include // Parameters for the main code #define OUTPIN 0 #define INPIN 1 #define LOOPS 100000 #define WAIT 1000 int main (void) { // Declare two structs to hold the time before and after struct timespec ts1; struct timespec ts2; // Setup the IO if (wiringPiSetup () == -1) return 1 ; pinMode (OUTPIN, OUTPUT); pinMode (INPIN, INPUT); // Initialize the running totals and loop counter // The first value will have keyboard processing interfering // and the pin will probably be in the wrong initial state int loops = 100; int64_t iterations = 0; int64_t ns = 0; int interruptions = 0; // Use an infinite loop. Hit CTRL C to exit for (;;) { // Wait for IO to settle into a known good state // Also allows other threads to interrupt before we do the critical timing stuff usleep(WAIT); // Remember the time before we set the pin to 1 clock_gettime(CLOCK_MONOTONIC_RAW,&ts1); digitalWrite (OUTPIN, 1) ; // Loop while the input pin is 0 (it goes to 1 when the signal gets to it) while (digitalRead(INPIN) == 0) { } // Note the time now the input pin has responded clock_gettime(CLOCK_MONOTONIC_RAW,&ts2); // Set the output off, ready for the next test and wait for the input to respond digitalWrite (OUTPIN, 0) ; while (digitalRead(INPIN) == 1) { } // Now calculate how many nanoseconds elapsed between setting the output ans sensing the input long nsdiff = (ts2.tv_nsec - ts1.tv_nsec); // If we went over a second boundary, we need to add a billion times the seconds difference. nsdiff += (1000000000 * (ts2.tv_sec - ts1.tv_sec)); // Try to detect if another thread got in the way. if (nsdiff < 10000) { ns += nsdiff; iterations ++; } else { interruptions ++; } // Count down the number of loops to test over if (--loops == 0) { printf("%" PRId64" iterations in %" PRId64" ns = %f ns per loop. (%d interruptions)\n", iterations, ns, (float)ns / (float)iterations , interruptions); // Re-set the counters ready for the next set of measurements loops = LOOPS; ns = 0; iterations = 0; interruptions = 0; } } return 0; }

The result of running that with two 10cm Jumper wires attached to the Pi and joined together with a small wire to leave effectively no gap was as follows.

997794 iterations in 2989501000 ns = 2996.110352 ns per loop. (2206 interruptions)

The result proves a few things. Firstly, the Pi was lying about the 1 nanosecond timer, it has three zeroes at the end of the total nanoseconds value. This indicates that the timer is really only measuring to a resolution of 1 microsecond, or 1000 nanoseconds. I thought 1 nanosecond was a bit optimistic. Secondly, it proved that the time taken for the pins to react was longer than the time the code took to measure it, otherwise all readings would be zero. And thirdly, it proved that the experiment may just work.

The next thing was to replace the join in the jumpers with 1m of wire and see if I can detect a difference. Here's the result from running that.

998130 iterations in 2803629000 ns = 2808.881592 ns per loop. (1870 interruptions)

Something was wrong. The signal went down the long wire FASTER than the short one. With the complete lack of regard for signal condition, I believe that the wire had all sorts of ringing, resonance and other electronic nastiness going on, so I re-ran the experiments ten times the sleep, but only 100000 iterations to keep the speed up. This time the short wire gave this.

99662 iterations in 309974000 ns = 3110.252930 ns per loop. (338 interruptions)

And the long wire gave this.

99595 iterations in 312642000 ns = 3139.133301 ns per loop. (405 interruptions)

Success. The signal took longer to travel down a longer wire. The readings were also pretty consistent, so the lower sample count shouldn't be a problem.

Not it's time to design the light and receiver, then go shopping for parts. I don't want to have to move the Pi, the light, or the sensor as moving them would mean long wires could cause electrical effects to change the results. The simple solution was to bounce the light of a mirror and vary the length of the light path by moving the mirror. One potential problem is that the brightness of the light would decrease as the distance increases. To avoid this I needed a light that sends out a thin column, all in the same direction rather than have it fan out all around. The obvious choice is a laser, which also has the advantage of sounding really cool. The receiver I chose was a phototransistor as they are fast and tend to be optimized for switching on and off, rather than measuring varying light levels.

I found both of these components on Ebay for 99 pence each including delivery from China. The laser was describes as "650nm 5V red laser diode laser module diode modules laser dot diode module SP" The specification was given as follows.

  Spot mode: dot facula, continuous output
Laser wavelength: 650nm (Red)
Copperhead Diameter is 6mm
Power lead length: 75 mm
Shell Material: Brass
Package included: 1 x Laser Dot
Working life: more than 2000 hours
Light power: Less than 5mW
Supply Voltage: 5V DC
Operating Current: Less than 40mA
Operating temperature: -36 ~ 65
 

The colour being visible, and the power requirements are all that really concerned me. I would be able to see this laser spot and could drive it from the Pi 5V pin.

The Phototransistor was described as "2Pcs 3Du5c Silicon Phototransistor Transistor/2-Feet Metal Package New Ic Q" which left me wondering what Q means and why both e-bay items had weird endings. I actually got 2 because I have another project planned, but together they cost me less than a pound.

  NPN Silicon Phototransistor;Model : 3DU5C
Working Voltage(Max.) : 10V;
Reverse Breakdown Voltage : 15V;
Dark Current : 0.3uA
Photocurrent : 0.5-1mA;
Power Consumption : 30mW;
Peak Wavelength : 880nM
Body Size : 7 x 5mm/ 0.28" x 0.2"(L*D);
Total Length : 28mm/ 1.1";
External Material : Metal
Weight : 3g;
 

Again, most of the spec is unimportant, but the Pi uses 3.3V, so the 10V working voltage is plenty the peak wavelength isn;t perfectly matched to the laser, but it's good enough. I already had the rest of the materials so the total spent on the project was £1.98. Not bad for to measure the fastest speed there is. The full parts list is.

1 Laser diode 1 3DU5C Phototransistor 1 2N3906 Transistor (general purpose NPN) 1 470 Ohm resistor 1 22 kOhm resistor 1 5pin male header connector 5 10cm jumper wires 1 inch by 3 inch Veroboard A bit of solder 1 Raspberry Pi model B 1 8Gb sd Card 1 1A phone charger 1 Ethernet cable - long enough to reach out through the window.

The circuitry is divided into two sections, laser and detector. The detector circuit uses the 22k resistor to pull a GPIO input up to 3.3v, then the phototransistor pulls it down to ground when it detects the laser light. The laser has it's positive terminal connected to +5V from the Pi and is controlled by the 2N3906 transistor between the ground and the negative laser terminal. The base of the transistor is fed via the 470 Ohm resistor from a GPIO output pin. The transistor switch is needed to make sure the laser has enough current to operate while no damaging the Pi.

Laser control circuit.


Light detector circuit.


Finished circuitry.


The software needs changing slightly for this as the light detector gives a zero when light is present. All places where the input is checked need to be swapped from 1 to 0 or visa-versa.

For the first run of the experiment a small shaving mirror was used, but aligning this proved next to impossible over the longer distances. A better solution is shown here.

The picture on the left shows the new mirror setup in full. It consists of two L shaped pieces of wood, one screwed to a camera tripod, the other. The corner and ends of the back piece have a nut glued in place and a coach bolt passing through from the back to the front so it can be freely turned. The front piece of wood rests on these three bolts and is held in place with elastic bands. So if the bolt at the top of the L is turned, it pushes the front piece of wood at the top and silts the mirror slightly downwards. Left and right adjustment is made with the coach bolt at the end of the horizontal part of the L shape. The horizontal adjuster is shown in the picture on the right. The actual mirror is a platter from a hard disk drive. These are very shiny and very accurately made to be very flat. Exactly what is needed for the laser mirror.

Experimental procedure

The electronics parts were placed on a raised platform at one end of the patio. An ethernet cable from the Pi was passed through the window to the network switch, which was luckily nearby. The laser was switched on and carefully levelled so vertical adjustment of the mirror wouldn't been needed very much. The tripod with the mirror was placed so two feet were aligned to the edge of the paving slab facing the laser as shown below.

Because I had to align the laser from up to 6m away, I had to modify the software to give quicker feedback. The delay was set to 1ms and the loop count was set to 1000, this gave me a reading approximately once per second. The accuracy suffered, but many readings can be averaged to compensate there.

The experiment had to be performed outside as the wooden floor of the house would flex slightly when anyone moved. This altered the mirror alignment. Outside is concrete paving slabs which move very little. I had assumed they wouldn't move at all, but I found if I stood on the same slab as the tripod and mirror, it would move when I stepped off. The experiment was performed at night with no house lights on. I live in the city, so it wasn't completely dark, but as close as I can manage. The method involved placing the tripod and mirror as shown above, aligning the beam by adjusting the coach bolts, then taking a few readings. The laptop was positioned along side the tripod and mirror so it's screen light would be facing away from the sensor. After I had enough data, I moved to the next slab and repeated the procedure.

Results

  Over on the right is a table of the raw data, but it looks more interesting plotted on a graph. I used Libre Office Calc for this. The best fit straight line was calculated with the trend line tool. Ideally all points would lie right on that line, but in reality they never do in experiments.
The number 3.6882167654 is the slope of the graph. In this case it tells us the speed of light in ns per paving slab.
The size of each slab is 600mm, but the light goes both ways over each one so each slab adds 1.2m to the path of the light. Now we know the speed is 3.6882167654 ns per 1.2m. Speed is described as distance divided by time (for instance miles per hour) so we divide the distance by the time as follows.
1.2 / 3.6882167654 = 0.3253604862 m/ns.
Multiply by 1000000000 to convert from m/ns to m/s as this is the usual unit.
0.3253604862 * 1000000000 = 325360486.2 m/s

The speed of light measured with a Raspberry Pi is 325360486.2 m/s

Google say the speed of light is 299792458 m/s
My result was only 8.5% different from the recognised value. Not too bad for a total expendature of under £2.
Distance
(Slabs)
Time
(ns)
03902
03938
03911
03961
03773
03831
03922
03880
13890
13899
13775
13778
13794
13842
13850
13830
13846
13823
13832
13848
13789
13879
13821
13850
23874
23839
23909
23825
23823
23979
23969
23823
33751
33697
33751
33814
33876
33729
33691
33887
33888
33720
33895
33774
33775
33797
33818
33862
33795
43934
43867
43813
43783
43829
43846
43932
43825
43822
53843
53754
53961
53890
53770
53788
53850
53683
53793
53900
53782
53890
53788
53773
53789
63841
63812
64029
63963
63852
63962
63860
64005
63889
73751
73757
73809
73913
73924
73850
73900
73814
73691
73932
73872
73964
73772
83849
83752
83921
83809
83864
83789
83830
83842
83824
83752
83901
83864
93904
93990
93868
93884
93889
94033
93975
93995
93988
93971
93902
103857
103972
103697
103938
103724
103746
103883
103874
103823
103788
103880
103916
103841
103887
103894
103981