My initial test of using the accelerometer to determine the rotational position of a bicycle wheel and keep the pattern stationary. Had to dust off my trigonometry skills for this one!
So cool! You’re going to look like you’re floating out there!
Wow, that is super cool!
Thanks guys! I still need to figure out the right formula for negating the added common centrifugal force on the X & Y axes when the wheel is turning. It becomes noticeable at higher RPMs.
Share the code and perhaps we’ll have some ideas.
@Scruffynerf I would love some help!
Here’s the code. Note that due to a mixup on my part, the y-axis is oriented backwards on my prototype, and you will see a line of code that compensates for that.
/*
* wheel test - stationary rainbow
*/
export var accelerometer;
// exported for debugging
export var angle;
export var angle1;
export var x, y;
// the weight used in the accelerometer averaging/smoothing calculation
var weight = .95;
function rotation () {
// read x and y from the accelerometer,
// and use a weighted average to smooth out the noise
x = (weight * x) + ((1 - weight) * accelerometer[0]);
//y = (weight * y) + ((1 - weight) * accelerometer[1]); // this would be the way if y-axis wasn't reversed
y = (weight * y) + ((1 - weight) * (accelerometer[1] * -1)); // fix for y-axis being reversed on my prototype
// calculate the rotational angle of the wheel based on the x and y values
// result of atan2 will be between (+PI) and (-PI)
angle = atan2 (x, y);
// normalize the angle to a value between 0 and 1
angle1 = clamp (((angle + PI) / (PI * 2)), 0, 1);
}
// the beforeRender function is called once before each animation frame
export function beforeRender(delta) {
}
// the render function is called for every pixel
export function render(index) {
// get the current rotational position of the wheel
// this can be done in beforeRender, but there will be a bit of lag
rotation ();
// calculate the static rainbow hue for this pixel
h = index / pixelCount;
// add the hue offset based on the current position of the wheel
h += angle1;
hsv (h, 1, 1)
}
Thanks in advance!
So let’s clarify what’s happening when the speed increases, it’s getting some form of lag, because the acceleration values aren’t accurate enough anymore? Example?
Both the x and y axes are oriented to point from the hub to the outer rim of the wheel. But since the accelerometer is positioned a small distance outside the hub, when the wheel spins, an additional force is added in equal amounts to both the x any y axes. This is added to the normal force of gravity, and skews the atan calculation of the rotational angle. The visible result is that the angle calculation goes +/- from where it would be if the wheel was static, with one +/- cycle per revolution. The +/- deviation of course gets worse as the RPM is increased.
I think there must be a clever way to mathematically detect when this force is being added, and subtract it out of the x and y values.
Ah, I get it. The wheel rotation causes a force you want to measure and remove.
You consider X and Y to be centered at the hub, but your measurement device is not at the hub, it’s rotating as part of the device (PB sensor), and that’s throwing off the calculation with the extra data/input involved.
Measuring it (for example) at top and bottom of wheel rotation, in a static position, works but the extra forces while moving need to be controlled for. You might have to add a sensor for speed (of rotation) measuring.
One other option would be change where the sensor board is. It can be moved, doesn’t have to be next to the PB. Mount it next to the axis?
Exactly! The sensor board is mounted inside an enclosure which is mounted as close to the hub as physically possible, which puts the accelerometer chip only about 2" from the rotational axis of the wheel, but it’s still enough to create this problem.
Your idea for using rotational speed to determine how much force to remove is great. I might be able to accomplish that without additional sensors, using a calculation that involves the angle, and time, to determine instantaneous rotational speed. There would also need to be a fudge-factor based on wheel position to correct the instantaneous angle, but I think its mathematically do-able.
Thanks for your help!
Ok, I finally found the formula for negating the extra force on the x & y axes when the wheel is spinning. First, I measure the change in wheel angle (which I already calculate) vs time, to determine the angular velocity, and convert that to RPM.
This formula calculates the force, where R = distance of accelerometer from the center of the wheel in centimeters:
gForce = RPM^2 Ă— 1.118 Ă— 0.00001 Ă— R
Then I normalize gForce to the 1g value of the accelerometer (0.02), and subtract it from both the x & y values read from the accelerometer. I found that R needs to be very accurate for it to work. Here’s the web page I found with the explanation:
https://www.sigmaaldrich.com/technical-documents/articles/biology/g-force-calculator.html
I know this is a bit of an older topic, but would you mind posting the rest of your code that compensates for the spin of the sensor board? I successfully got your code running on my build, but am having a lot of trouble figuring out how to implement the math from your last post. Hopefully you see this lol
Here is my attempt so far. It sort of works, but I think i’m doing something wrong. On my bike, I have the new firmware sending different code to the wheels and the body of the bike using the nodeID.
/*
* wheel test - stationary rainbow
*/
export var accelerometer;
scale = pixelCount / 2
// the weight used in the accelerometer averaging/smoothing calculation
var weight = .95;
function rotation () {
// read x and y from the accelerometer,
// and use a weighted average to smooth out the noise
x = (weight * x) + ((1 - weight) * accelerometer[0] );
y = (weight * y) + ((1 - weight) * accelerometer[1]); // this would be the way if y-axis wasn't reversed
//y = (weight * y) + ((1 - weight) * (accelerometer[1] * -1)); // fix for y-axis being reversed on my prototype
// calculate the rotational angle of the wheel based on the x and y values
// result of atan2 will be between (+PI) and (-PI)
angle = atan2 (x - normalGForce, y - normalGForce);
// normalize the angle to a value between 0 and 1
angle1 = clamp (((angle + PI) / (PI * 2)), 0, 1);
angleInDegrees = angle * (180 / PI); //convert radians to degrees
rpm = angleInDegrees / 360; //convert degrees to RPM
gForce = rpm^2 * 1.118 * 0.00001 * 13; //equation for getting gForce from rotation
normalGForce = gForce * (0.02 / 1); //normalize gForce to 1g = 0.02 for the sensor
}
// the beforeRender function is called once before each animation frame
export function beforeRender(delta) {
t1 = time(.1) // Time it takes for regions to move and melt
}
// the render function is called for every pixel
export function render(index) {
if(nodeId()===1){
// get the current rotational position of the FRONT wheel
// this can be done in beforeRender, but there will be a bit of lag
rotation ();
// calculate the static rainbow hue for this pixel
h = index / pixelCount*2;
// add the hue offset based on the current position of the wheel
if(h>1) {h += angle1;}
else{h-=angle1}
hsv (h, 1, 1)
}
else if (nodeId()===2){
// get the current rotational position of the REAR wheel
// this can be done in beforeRender, but there will be a bit of lag
rotation ();
// calculate the static rainbow hue for this pixel
h = index / pixelCount*2;
// add the hue offset based on the current position of the wheel
if(h>1) {h += angle1;}
else{h+=angle1}
hsv (h, 1, 1)
}
else{
c1 = 1 - abs(index - scale) / scale // 0 at strip endpoints, 1 in the middle
c2 = wave(c1)
c3 = wave(c2 + t1)
v = wave(c3 + t1) // Separate the colors with dark regions
v = v * v
hsv(c1 + t1, 1, v)
}
}
@Irwin @daderaide Curious if this progressed any further? My use case is similar, but also want to explore added static images on the spokes.
Funny you should mention this…
My bike with all of the Pixelblazes, LEDs, batteries, etc was stolen at Burning Man last year (i’m not sure why - it was just a cheap cruiser), and I’m currently working on re-building the lighting system using the new Pico w/6-axis controller. Hoping to finish in time for this year!
I plan to use this tutorial on the 6-axis controller, and the new “pattern rotate” function of the PB.
I’ll try to update this thread with my results after the burn.
@Irwin That’s absolutely brutal, I’ve heard similar stories and am planning to lock my bike. I have the same exact use case for Burning Man, let me know if you want to share tips/code. I can update this thread if I get something functional.
Thanks, John. My bike was actually locked, parked on the Esplanade for a couple of days because I couldn’t even walk it home in the mud. Came back on Monday and it was gone.
If you haven’t already seen it, there’s recent pattern “Accelerometer level example” with an example of the rotation function. It has an issue with the angle wrapping through the 0/360 boundary but I think that can be solved.
Oh man, sorry that happened! And thanks for the tip, I hadn’t seen that pattern. I’ll play around with it.
I’ve come up with some preliminary code where I modified the “Accelerometer level example” pattern to use the gyro in the Pico 6-axis accelerometer, and it’s quite simple! The correction factor on line 14 is not really required for my purposes, but it gives better accuracy over long periods time. I’m using the Z axis.
Note that by using the gyro instead of the g-force values, there is no need to compensate for the g-forces generated by the spinning wheel.
// Modified from "Accelerometer level example" pattern
export var sixAxis
export var gyro
export var currentAngleDegs
export var currentAngleRads
export function beforeRender(delta) {
// get the current value of the Z gyro
gyro = sixAxis[5]
// add error correction (varies by device)
gyro += -0.9
// calculate the current wheel angle in degrees
currentAngleDegs = mod (currentAngleDegs + ((gyro * (delta) / 1000)), 360)
// convert angle from degrees to rads
currentAngleRads = currentAngleDegs * (PI / 180)
//you may need to reverse this angledepending on the
//sensor board orientation relative to the LEDs
//currentAngleRads = -currentAngleRads
//apply this angle as a rotation
//first reset the transform
resetTransform ()
//center coordinates so we rotate around the center, not the corner
translate (-.5, -.5)
//rotate by the correction angle
rotate (currentAngleRads)
}
export function render2D(index, x, y) {
//draw a vertical line
var v = clamp (1 - abs (y * 5), 0, 1)
hsv (x + .5, 1, v * v)
}
export function render(index) { hsv(0,1,1); } // all red
Just gave this a go, it works great! Are you planning to port other patterns into this code? I’m going to fool around with some of the 1D patterns and see if tI can fit them into this structure. I’ll report back.