Counting spins on an ESP32

I’ve had some time during my no-compete period between jobs; one distraction has been this little project, which aims to count how many times I spin around. The inspiration for this is twofold:

  1. xkcd 2882
For decades I've been working off the accumulated rotation from one long afternoon on a merry-go-round when I was eight.
"Net Rotations" by xkcd, CC BY-NC 2.5
  1. Over Christmas my family like to play a game where we make a bet on the behaviour of one of the dogs (how long will they sleep?, how long can we get them to stay?, etc). One that was floated was how many times will Bella spin around?. Bella you see, is a very excitable dog, something she communicates with pacing, barking, and spinning. This idea was discarded because keeping track would be onerous, but for next year it struck me that a technological solution was available.

I bought an M5StickC Plus2 (photo below), which is a small self-contained device containing an ESP32, 6-axis IMU, LiPo, and screen. It’s small and light enough to attach to a dog collar without bothering the dog. Because of the constraints of dog-mounted devices, it was important that the device work regardless of its mounting orientation. My rough implementation plan for this was:

  1. Use the gyroscope to keep track of the device’s orientation RSO(3).
  2. The accelerometer measures the “up” direction and uses this as an anchor to correct drift in orientation.
  3. The rotation rate measured by the gyroscope is transformed into the lab frame, and if the rotation is in the xy plane, added to an accumulated rotation angle.
  4. If the accumulated angle goes past a full turn in either direction, add a turn to the counter.

For numerical convenience, I tracked orientation as a Rotor, which actually lives in Spin(3), a double cover of SO(3). This approach is equivalent to using quaternions (popular in game engines) and is discussed in this great blog post.

An aside on topology: point 3 is somewhat more complex than I’ve stated. Imagine the device on a turntable; if the turntable is flat, rotating in the xy plane, it’s obvious when it has done a full turn left or right. If it’s on its side and rotating in the xz plane, it’s obvious that it is not spinning either left or right. What about if it’s on an angle? I think there are lots of defensible choices here, but more interesting is why it’s a hard problem at all.

In 2D, the orientation of an object is a point in SO(2). If we look at different trajectories in this space which take us from some orientation back to itself, we can group these into equivalence classes under smooth deformation (homotopy), these classes are identified by an integer winding number π1(SO(2))=Z. A consequence of this is that, in 2D, it is very easy to determine if an object has done a full spin left or right. In 3D the classes are just trivial and sign, π1(SO(3))=Z2, and a full rotation in any direction just moves you from one to the other (see the plate trick). In 3D you can smoothly deform a left turn into a right turn, in 2D you cannot. This has a lot of interesting implications for physics and also makes this toy project slightly harder. Another consequence is that the xkcd is wrong, and you only need to make sure you do an even number of rotations each day.

Spin counter results
Results of the counter after 2 hours of Ceroc.

In my own code, I just counted the rotation if it was within 45 deg of the xy plane. The rest of the implementation is pretty straightforward: I’m relatively generous in counting spins, especially if alternating directions, and there are some other corrections for drift in there. The code is here, Claude Code wrote a lot of the rotor implementation, although got some of it wrong. If you press the big button on the front of the device it will show you how many turns it’s counted in each direction, the total amount of twist/worldline torsion, and a little axis gizmo of its current orientation. The battery life is just over six hours, I think this could be stretched with some optimisation but it’s good enough for now.

To test this, I took it with me dancing. During the class and freestyle, I apparently did 352 clockwise and 138 anticlockwise turns, totalling a spin every ~15s. This feels high, but the (short) routine we were practicing did actually have quite a few spins and the ratio is right, so I think it’s probably close to accurate.

I’m not going to get a chance to visit my parents and their dog for quite a while, so dog-based experiments will have to wait.