 Laurent Brunel. Creative Common License CC-BY: Knowledge is open if anyone is free to access, use, modify, and share it — subject, at most, to measures that preserve provenance and openness.
    Laurent Brunel. Creative Common License CC-BY: Knowledge is open if anyone is free to access, use, modify, and share it — subject, at most, to measures that preserve provenance and openness.
This projet use the Persitence Of Vision property of our eyes to display things with the wheel of a bicycle. The device is installed in the bike in one second by inserting it between the spokes: the centrifugal force does the rest… We decided to display the bike speed and some characters changeable with the mobile phone.
 We 3D printed a body design with FreeCAD to carry an Arduino micro pro connected to a Bluetooth module HC05 (Mixtraltech).


This POV is equipped with 8 bright LEDs (Cree C566C-AFN-CU0W0251 Amber LED, 596 nm RS 810-7235) connected directly to the arduino pins (with a resistor) and a IR sensor (ref LTH-1550-01) able to detect when the POV pass in front of a white target pasted in the frame.

A 1400mAh Li-po battery is placed for energy. The IR sensor permits to measure the time taken
 by a wheel turn, to calculate the bike speed knowing the wheel diameter and display it.
The bluetooth allows a simple APP for android made with AppInventor to send by serial a new word to display beside the speed value.
 Arduino code, STL 3D model of the body and FreeCAD source file here
Inspired by shrimpov.
————————————————————————————————————————————-
arduino code:
————————————————————————————————————————————–
#define maxMessageLenght 50 //max nb of char in message before CR and LF
//inspired by shrimpov: https://github.com/embecosm/shrimp-pov/blob/master/code/shrimPov/shrimPov.ino
 // Font: commodore_64_pixelised
 // 528 bytes
 byte fontHeight = 8;
 int fontOffsets[] = {
 /*EXCLAMATION*/ 16, /*”*/ 48, /*#*/ 64, /*$*/ /*48*/ 64, /*%*/ 48, /*&*/ 56, /*’*/ 32, /*(*/ 32, /*)*/ 32, /***/ 64,
 /*+*/ 48, /*,*/ 24, /*-*/ 48, /*STOP*/ 16, /*FWDSLASH*/ 56, /*0*/ 48, /*1*/ 48, /*2*/ 48, /*3*/ 48, /*4*/ 56, /*5*/ 48,
 /*6*/ 48, /*7*/ 48, /*8*/ 48, /*9*/ 48, /*:*/ 16, /*;*/ 24, /*<*/ 48, /*=*/ 48, /*>*/ 48, /*QUESTION*/ 48, /*@*/ 48,
 /*A*/ 48, /*B*/ 48, /*C*/ 48, /*D*/ 48, /*E*/ 48, /*F*/ 48, /*G*/ 48, /*H*/ 48, /*I*/ 32, /*J*/ 48, /*K*/ 48, /*L*/ 48,
 /*M*/ 56, /*N*/ 48, /*O*/ 48, /*P*/ 48, /*Q*/ 48, /*R*/ 48, /*S*/ 48, /*T*/ 48, /*U*/ 48, /*V*/ 48, /*W*/ 56, /*X*/ 48,
 /*Y*/ 48, /*Z*/ 48, /*[*/ 32, /*BACKSLASH*/ 56, /*]*/ 32, /*^*/ 48, /*_*/ 64, /*FWDAPOS*/ 32, /*a*/ 48, /*b*/ 48, /*c*/ 40,
 /*d*/ 48, /*e*/ 48, /*f*/ 40, /*g*/ 48, /*h*/ 48, /*i*/ 32, /*j*/ 32, /*k*/ 48, /*l*/ 32, /*m*/ 56, /*n*/ 48, /*o*/ 48,
 /*p*/ 48, /*q*/ 48, /*r*/ 48, /*s*/ 48, /*t*/ 48, /*u*/ 48, /*v*/ 48, /*w*/ 56, /*x*/ 48, /*y*/ 48, /*z*/ 48, /*{*/ 48,
 /*|*/ 16, /*}*/ 48, 0
 };
byte fontBytes[] = {
 /*EXCLAMATION*/ 95, 95,
 /*”*/ 3, 3, 0, 0, 3, 3,
 /*#*/ 20, 127, 127, 20, 20, 127, 127, 20,
 ///*$*/ 36, 46, 107, 107, 58, 18,
 /*$ [smilie] */ 60, 108, 219, 223, 223, 219, 108, 60,
 /*%*/ 99, 51, 24, 12, 102, 99,
 /*&*/ 50, 127, 77, 77, 119, 114, 80,
 /*’*/ 4, 6, 3, 1,
 /*(*/ 28, 62, 99, 65,
 /*)*/ 65, 99, 62, 28,
 /***/ 8, 42, 62, 28, 28, 62, 42, 8,
 /*+*/ 8, 8, 62, 62, 8, 8,
 /*,*/ 128, 224, 96,
 /*-*/ 8, 8, 8, 8, 8, 8,
 /*STOP*/ 96, 96,
 /*FWDSLASH*/ 64, 96, 48, 24, 12, 6, 2,
 /*0*/ 62, 127, 73, 69, 127, 62,
 /*1*/ 64, 68, 127, 127, 64, 64,
 /*2*/ 98, 115, 81, 73, 79, 70,
 /*3*/ 34, 99, 73, 73, 127, 54,
 /*4*/ 24, 24, 20, 22, 127, 127, 16,
 /*5*/ 39, 103, 69, 69, 125, 57,
 /*6*/ 62, 127, 73, 73, 123, 50,
 /*7*/ 3, 3, 121, 125, 7, 3,
 /*8*/ 54, 127, 73, 73, 127, 54,
 /*9*/ 38, 111, 73, 73, 127, 62,
 /*:*/ 99, 99,
 /*;*/ 128, 227, 99,
 /*<*/ 8, 28, 54, 99, 65, 65,
 /*=*/ 20, 20, 20, 20, 20, 20,
 /*>*/ 65, 65, 99, 54, 28, 8,
 /*QUESTION*/ 2, 3, 81, 89, 15, 6,
 /*@*/ 62, 127, 65, 77, 79, 46,
 /*A*/ 124, 126, 11, 11, 126, 124,
 /*B*/ 127, 127, 73, 73, 127, 54,
 /*C*/ 62, 127, 65, 65, 99, 34,
 /*D*/ 127, 127, 65, 99, 62, 28,
 /*E*/ 127, 127, 73, 73, 65, 65,
 /*F*/ 127, 127, 9, 9, 1, 1,
 /*G*/ 62, 127, 65, 73, 123, 58,
 /*H*/ 127, 127, 8, 8, 127, 127,
 /*I*/ 65, 127, 127, 65,
 /*J*/ 32, 96, 65, 127, 63, 1,
 /*K*/ 127, 127, 28, 54, 99, 65,
 /*L*/ 127, 127, 64, 64, 64, 64,
 /*M*/ 127, 127, 6, 12, 6, 127, 127,
 /*N*/ 127, 127, 14, 28, 127, 127,
 /*O*/ 62, 127, 65, 65, 127, 62,
 /*P*/ 127, 127, 9, 9, 15, 6,
 /*Q*/ 30, 63, 33, 97, 127, 94,
 /*R*/ 127, 127, 25, 57, 111, 70,
 /*S*/ 38, 111, 73, 73, 123, 50,
 /*T*/ 1, 1, 127, 127, 1, 1,
 /*U*/ 63, 127, 64, 64, 127, 63,
 /*V*/ 31, 63, 96, 96, 63, 31,
 /*W*/ 127, 127, 48, 24, 48, 127, 127,
 /*X*/ 99, 119, 28, 28, 119, 99,
 /*Y*/ 7, 15, 120, 120, 15, 7,
 /*Z*/ 97, 113, 89, 77, 71, 67,
 /*[*/ 127, 127, 65, 65,
 /*BACKSLASH*/ 2, 6, 12, 24, 48, 96, 64,
 /*]*/ 65, 65, 127, 127,
 /*^*/ 4, 6, 127, 127, 6, 4,
 /*_*/ 64, 64, 64, 64, 64, 64, 64, 64,
 /*FWDAPOS*/ 1, 3, 6, 4,
 /*a*/ 32, 116, 84, 84, 124, 120,
 /*b*/ 126, 126, 72, 72, 120, 48,
 /*c*/ 56, 124, 68, 68, 68,
 /*d*/ 48, 120, 72, 72, 126, 126,
 /*e*/ 56, 124, 84, 84, 92, 24,
 /*f*/ 8, 124, 126, 10, 10,
 /*g*/ 152, 188, 164, 164, 252, 124,
 /*h*/ 127, 127, 4, 4, 124, 120,
 /*i*/ 68, 125, 125, 64,
 /*j*/ 128, 128, 250, 122,
 /*k*/ 127, 127, 16, 56, 104, 64,
 /*l*/ 65, 127, 127, 64,
 /*m*/ 124, 124, 24, 56, 28, 124, 120,
 /*n*/ 124, 124, 4, 4, 124, 120,
 /*o*/ 56, 124, 68, 68, 124, 56,
 /*p*/ 252, 252, 36, 36, 60, 24,
 /*q*/ 24, 60, 36, 36, 252, 252,
 /*r*/ 124, 124, 4, 4, 12, 8,
 /*s*/ 72, 92, 84, 84, 116, 36,
 /*t*/ 4, 4, 62, 126, 68, 68,
 /*u*/ 60, 124, 64, 64, 124, 124,
 /*v*/ 28, 60, 96, 96, 60, 28,
 /*w*/ 28, 124, 112, 56, 112, 124, 28,
 /*x*/ 68, 108, 56, 56, 108, 68,
 /*y*/ 156, 188, 160, 224, 124, 60,
 /*z*/ 68, 100, 116, 92, 76, 68,
 /*{*/ 8, 8, 62, 119, 65, 65,
 /*|*/ 127, 127,
 /*}*/ 65, 65, 119, 62, 8, 8, 0
 };
int numSides = 4; //the number of sides in the tilt switch
 int tiltPins[] = {8,7,5,6}; //listed in clockwise direction
 int numLights = 8;
// pins for the pov bici
 int povPins[] = {9,8,7,6,5,4,3,2};
 int pinSensor=10;//sensor to measure period (or angular speed)
 //pins for the test on the 16leds pov:
 //int povPins[] = {22,24,26,28,30,32,34,36};
 //int pinSensor=2;//sensor to measure period (or angular speed)
int columnDelay = 1;
 int spaceCols = 4;
//String message = ” 0 “;
 char message[maxMessageLenght];
 int messageLength=1;
int lastState=0;
 unsigned long time0,time1,period;
void drawMessage(){
for(int letterPos = 0; letterPos < messageLength; letterPos++){
//use ascii charcode to index font information starting with 33 = first printable char
 int fontPos = int(message[letterPos]) – 33;
//draw space or letter
 if(fontPos == -1){
 //it’s a space
 drawSpace();
 }
 else{
 //it’s a printable character. draw it
 drawCharacter(fontPos);
 }
//draw a space between each character of message
 drawBlankCol();
 }
 }
 void drawMess(char* mess,int messLength)
 {
for(int letterPos = 0; letterPos < messLength; letterPos++){
//use ascii charcode to index font information starting with 33 = first printable char
 int fontPos = int(mess[letterPos]) – 33;
//draw space or letter
 if(fontPos == -1){
 //it’s a space
 drawSpace();
 }
 else{
 //it’s a printable character. draw it
 drawCharacter(fontPos);
 }
//draw a space between each character of message
 drawBlankCol();
 }
 }
void drawBlankCol(){
 for(int row = 0; row < fontHeight; row++){
 digitalWrite(povPins[row],LOW);
 }
 delay(columnDelay);
 }
void drawSpace(){
 for(int col = 0; col < spaceCols; col++){
 drawBlankCol();
 }
 }
void drawCharacter(int fontPos)
 {
 int startBit = fontOffsets[fontPos]; //first bit of character
 int endBit = fontOffsets[fontPos + 1]; //start of next char (or dummy char at end)
for(int colStart = startBit; colStart < endBit; colStart += fontHeight)
 {
 for(int row = 0; row < fontHeight; row++)
 {
 //calculate the bit position
 int bitPos = colStart + row;
 int byteOffset = bitPos / 8;
 int bitOffset = bitPos % 8;
 //read the individual bit
 if(((fontBytes[byteOffset]) & (1 << bitOffset)) == 0)
 {
 digitalWrite(povPins[row], LOW);
 }
 else
 {
 digitalWrite(povPins[row], HIGH);
 }
 }
 delay(columnDelay); //wait between columns
 }
 }
 /** Configure input and output pins corresponding with each tilt direction to begin. */
 void setup()
 {
 Serial.begin(9600);
message[0]=’F’;
 message[1]=’A’;
 message[2]=’B’;
 message[3]=’_’;
 message[4]=’L’;
 message[5]=’A’;
 message[6]=’B’;
 messageLength=7;
for(int side = 0; side < numSides; side++){
 //configure tilt pins as input and set high resistance
 pinMode(tiltPins[side],INPUT);
 digitalWrite(tiltPins[side],LOW);
 }
for(int light = 0; light < numLights; light++){
 //configure lights as output and turn off
 pinMode(povPins[light], OUTPUT);
 digitalWrite(povPins[light],LOW);
 }
//translate offsets from relative (character width) into absolute (cumulative width so far)
 int numOffsets = sizeof(fontOffsets) / sizeof(int);
 int bitCount = 0;
 for(int offsetIdx = 0; offsetIdx < numOffsets; offsetIdx++){
 int charWidth = fontOffsets[offsetIdx];
 fontOffsets[offsetIdx] = bitCount;
 bitCount += charWidth;
 }
pinMode(pinSensor, INPUT);
}
/** visit each side in turn to check if it’s connected
 * and display each direction using different LEDs
 */
 void loop()
 {
int i;
 char messPeriod[9];
 if (Serial.available())
 {
 readMessage();
 //Serial.println(message);
 }
if (digitalRead(pinSensor) == HIGH) // read the state of the speed sensor
 {
 // turn LED on:
 //digitalWrite(13, HIGH);
 if (lastState==LOW)
 {
 time0=millis();
 period=time0-time1;
 if (period>800)period=800;
 //delay(100);
 time1=time0;
//wait half turn
 delay(period*0.4);
//print the speed:
 int v100=(int)(735100/period);
 int i2=v100/100;
 int i1=(v100-i2*100)/10;
 int i0=v100-i2*100-i1*10;
 messPeriod[0]=i2+48;
 messPeriod[1]=’.’;
 messPeriod[2]=i1+48;
 messPeriod[3]=i0+48;
 messPeriod[4]=’ ‘;
 messPeriod[5]=’k’;
 messPeriod[6]=’m’;
 messPeriod[7]=’/’;
 messPeriod[8]=’h’;
 drawMess(messPeriod,9);
drawSpace();
 drawSpace();
//for (int i=0;i<5;i++)
 {
 drawMessage();
 //if ((millis()-time0)>(period*0.8)) break;
 //drawSpace();
 //drawSpace();
 }
 }
 lastState=HIGH;
 }
 else
 {
 // turn LED off:
 // digitalWrite(13, LOW);
 lastState=LOW;
 }
}
 void readMessage()
 {
 int i=0;
 for(;;)
 {
 char incomingByte = Serial.read();
 delay(2);
 if (incomingByte==-1) break;
 if (incomingByte==’;’) break;//until ;
 if (incomingByte==10) break;//until New Line;
 if (incomingByte==13) break;//until Carriage Return;
 if (i==maxMessageLenght-1) //Message too long
 {
 break;
 }
 message[i]=incomingByte;
 i++;
 }
messageLength=i;
 if (messageLength>10) messageLength=10;
 /*
 Serial.print(“messageLenght “);
 Serial.println(messageLenght, DEC);
 Serial.print(“*”);
 for (int i=0;i<messageLenght;i++) Serial.print(message[i]);
 Serial.print(“*”);
 Serial.println();
 */
 }
 /** Keeps a value within bounds when overflowing or underflowing,
 * so that incrementing or decrementing a number stays in bounds.
 * (workaround for the fact that % in C isn’t a mathematical modulus and behaves weird with negative numbers) */
 int wrap(int value, int upperBound){
 return value < 0 ? ((value % upperBound) + upperBound) % upperBound : value % upperBound;
 }
 
			 
			 
			 
			 
	