////////////////////////////////////////////////////////////////////////////// // pov.c - persistence of vision display for bicycle wheels // Authored by John R. Dawson (www.johndawson.org), circa 2009 // email: povboard johndawson org // // APPLICATION NOTES: // Chip used is Atmel ATMega644p // Ports: // Ports A and C comprise one row of LED's (LEFT side) // Ports B and D comprise the other row (RIGHT side) // POV board is two-sided, 15-LED's per side, individually controlled. // // All pins on ports A and B are used (top 8 LED's in each row), and // the first 7 pins on ports C and D are used (bottom 7 LED's in each // row). Total of 15 LED's in each row. // // Pin 21 (PORTD7/PIND7) is the input for the "mode select" shorting block. // Pin 29 (PORTC7/PINC7) is the input for the hall effect rotation sensor. // // We should be able to get away with writing a full 8 bits to ports C // and D for convenience. The only side effect will be constantly toggling // the pull-up resistor for pin 7 of each port. This shouldn't affect the // operation of the circuit because we have used external pull-ups on // these pins. // // Timers: // timer0 is used for short delays (controlling on/off time of LED's). // The timer0 compare match interrupt vector (TIMER0_COMPA_vect) is used. // timer1 is used for timing wheel rotation. // The timer1 overflow vector (TIMER1_OVF_vect) is used. // timer2 is used for ignoring spurious/multiple triggers from hall sensor. // The timer2 overflow vector (TIMER2_OVF_vect) is used. ////////////////////////////////////////////////////////////////////////////// // BUGS: // 1. At certain speeds the display seems to be "smeared" while the LED's // freeze in their current state for an extended period of time (maybe 1/4 // to half a revolution). Looks like the on-time (sometimes the off-time) // for the LED's gets stretched out as though the chip is zoning out and // ignoring me. This seems to be speed/timing related since it happens // frequently at certain fixed speeds, and not much at all at other speeds. // If you have any ideas on what may be causing this, please educate me via // email: povboard johndawson org ////////////////////////////////////////////////////////////////////////////// // FUTURE ENHANCEMENTS: // 1. Add an IR detector in place of the mode selector. Use this to program // the board with a remote control. // 2. Add a clock crystal to allow displaying date/time. May have to get // creative with the timers since all of them are already in use. // To avoid having to set the time every time the board is powered up, // may have to leave the board running all the time. Must research the // power saving modes and turn off every system not required for keeping // time. This also would require recharging while the board is still under // power. Considering the hassle factor and the not-very-useful factor, // this may never happen. // 3. Use the non-volatile memory to store total distance traveled so that // we have a permanent odometer. ////////////////////////////////////////////////////////////////////////////// // GENERAL NOTES: // m644p interrupt vector mnemonics are defined in: // /usr/local/avr/avr/include/avr/iomxx4.h (which is included by iom644.h) ////////////////////////////////////////////////////////////////////////////// #include #include #include // For sleep_enable(), etc. #include #include #include //#include //#include #include "pov.h" #include "characters8.h" #include "characters15.h" #include "font8_Plain.h" //#include "font15_Chancery.h" // // Local function prototypes // void ShowChar8(unsigned char, char); void ShowChar15(unsigned char, char); void ShowString8(char *, char); void ShowSpeed(void); void ShowSpaceInvaders(void); void TinyPause(unsigned char, char); void PowerOnVisual(void); void GetTptr8(unsigned char, unsigned char **); // // Global variables // // Number of clock counts per wheel revolution static volatile unsigned long Clicks=0, TmpClicks=0; static volatile unsigned long Revolutions=0; // Revs since last mile mark static volatile int Miles=0; // Miles traveled (duh) static volatile float MPH=0; // Miles per hour (duh) // Used to ignore spurious sensor signals static volatile char DetectRotation=1; // Used as a condition variable for pause/wait loops static volatile char TinyPauseDone=0; // Revolution interrupt triggered - start anew static volatile char RestartCycle=0; // Character display on/off times static volatile int PauseTime, OnPauseTime, OffPauseTime; static volatile int PrescaleFactor; // Doubled these values to get scale factor correct after 8MHz clock change. static const float BASE_ONTIME=0.13; static const float BASE_OFFTIME=0.20; static const float SHORT_ONTIME=0.09; static const float SHORT_OFFTIME=0.13; static const float LONG_ONTIME=0.20; static const float LONG_OFFTIME=0.32; static volatile float CONST_ONTIME=0.13; static volatile float CONST_OFFTIME=0.20; static volatile unsigned char Mode; // Mode of operation (jumper on board) ////////////////////////////////////////////////////////////////////////////// // ISR for hall effect sensor interrupt (PCINT23/PORTC7) ////////////////////////////////////////////////////////////////////////////// ISR(PCINT2_vect) { if( ! DetectRotation ) { // Ignore spurious signals from rotation sensor return; } else { DetectRotation=0; // Ignore signals after this run. // Start timer2. When it overflows a set number of times, signal // detection will be re-enabled. TCNT2=0; TCCR2B &= ~( (1<= REVS_PER_MILE ) { Miles++; Revolutions=0; } // Tell main() to restart output from beginning RestartCycle=1; // Check external jumpers for display mode (Note: Pins are active low) Mode=( PIND & (1<<7) ? 1 : 0 ); } // End ISR for hall effect sensor interrupt (PCINT23/PORTC7) ////////////////////////////////////////////////////////////////////////////// // ISR for Compare Match on timer0 // // This timer is used for short delays such as for determining on-time and // off-time of the LED's. ////////////////////////////////////////////////////////////////////////////// ISR(TIMER0_COMPA_vect) { // Disable timer0 by selecting no clock source TCCR0B &= ~( (1<= TARGET_OVF_COUNT ) { // Disable timer2 by selecting no clock source TCCR2B &= ~( (1<= SwitchPatternTrigger ) { SwitchPatternCounter=0; if( PatternNum++ >= MaxPatternNum ) { PatternNum=0; } } } } // End if() for Revs % X else { AdjustCounter=1; } if( RestartCycle ) continue; // // Display the actual image/text pattern // switch( Mode ) { case MODE_1: switch( PatternNum ) { case 0: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; ShowChar15(BLACKBAT,DEFAULT_STYLE); ShowChar8(' ',DEFAULT_STYLE); ContinuousDisplay=1; break; case 1: CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar15(ALLSUITS,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 2: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; ShowChar15(CHECKERED,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 3: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; ShowChar15(Egypt,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 4: CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar15(Bug,DEFAULT_STYLE); ShowChar8(' ',DEFAULT_STYLE); ContinuousDisplay=1; break; case 5: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; ShowChar15(Circuit,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 6: CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowSpaceInvaders(); ContinuousDisplay=1; break; case 7: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; ShowChar15(WAVYTRIANGLES,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 8: CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar15(MULTIDOTS,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 9: CONST_ONTIME=SHORT_ONTIME; CONST_OFFTIME=SHORT_OFFTIME; ShowChar15(OPPOSEDTREES,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 10: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; ShowChar15(OPPOSEDTRIANGLES,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; case 11: CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar8(Notes,DEFAULT_STYLE); ContinuousDisplay=1; break; case 12: CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar8(ZigZag,ADJACENT_CHARACTERS); ContinuousDisplay=1; break; /* case 13: CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar15(LightningBolt,DEFAULT_STYLE); ShowChar8(' ',DEFAULT_STYLE); ContinuousDisplay=1; break; */ default: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; char TexT[25]; sprintf(TexT,"MODE %d: BAD IMG %d ",Mode,PatternNum); ShowString8(TexT,DEFAULT_STYLE); ContinuousDisplay=0; break; break; } // End switch() for MODE_1 break; case MODE_2: // Rotate between web addr, speed, and disc images switch( PatternNum ) { /* case 0: // Web address CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar15(CCC,DEFAULT_STYLE); ContinuousDisplay=0; break; */ case 1: // Speed/distance CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; ShowSpeed(); ContinuousDisplay=0; break; /* case 2: // Disc/diskette images CONST_ONTIME=LONG_ONTIME; CONST_OFFTIME=LONG_OFFTIME; ShowChar15(Discs,DEFAULT_STYLE); ContinuousDisplay=1; break; */ default: CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; char TexT[25]; sprintf(TexT,"MODE %d: BAD IMG %d ",Mode,PatternNum); ShowString8(TexT,DEFAULT_STYLE); ContinuousDisplay=0; break; break; } // End switch() for MODE_2 patterns break; default: // WTF??? CONST_ONTIME=BASE_ONTIME; CONST_OFFTIME=BASE_OFFTIME; char TexT[20]; sprintf(TexT,"BAD MODE: 0X%02x ",Mode); ShowString8(TexT,DEFAULT_STYLE); ContinuousDisplay=0; break; break; } // End switch() for displaying patterns if( RestartCycle ) continue; // Now go to sleep. We'll be awakened by the revolution sensor interrupt. if( ! ContinuousDisplay ) { sleep_enable(); while( ! RestartCycle ) { sleep_cpu(); } sleep_disable(); } } // End while() loop return 0; } // End function main() ////////////////////////////////////////////////////////////////////////////// // TinyPause() - function puts the MCU to sleep for very short periods of // time, such as the length of time a single row of character data is // on, or the length of time all LED's are off to create space between // adjacent characters. // // Parameters: // char clicks [in] - The number of clock ticks to sleep. // // Returns: // (none) // // Notes: // Prescaler settings (from data sheet pg. 105): // CS02 CS01 CS00 Description // 0 0 0 No clock source selected // 0 0 1 System clock (no prescaling) // 0 1 0 /8 prescaler // 0 1 1 /64 prescaler // 1 0 0 /256 prescaler // 1 0 1 /1024 prescaler // 1 1 0 External clock on T0 pin (clock on falling edge) // 1 1 1 External clock on T0 pin (clock on rising edge) ////////////////////////////////////////////////////////////////////////////// void TinyPause(unsigned char clicks, char prescaler) { // Set OCR0A (TOP) to desired value OCR0A=clicks; // Set counter to zero TCNT0=0; // Initialize our (global) condition variable TinyPauseDone=0; // Select clock source to enable timer TCCR0B &= ~( (1<': tptr=Punct_GreaterThan; break; case '\'': tptr=Punct_Tic; break; case '"': tptr=Punct_DoubleQuotes; break; case '(': tptr=Punct_OpenParen; break; case ')': tptr=Punct_CloseParen; break; default: tptr=Punct_Question; break; } // End switch() for(countF=0,countB=size-1;countF=0;countF++,countB--) { if( style & INVERTED_TEXT ) { PORTA=~(tptr[countF]); // Dark text, light background, front side PORTB=~(tptr[countB]); // Dark text, light background, back side } else { PORTA=tptr[countF]; // Light text, dark background, front side PORTB=tptr[countB]; // Light text, dark background, back side } // Next we'll calculate on-time based on speed (for a stable display). // We also need to time how long this calculation takes so we can // subtract it from the total on-time. Floating point arithmetic // is extremely slow, so we really need to allow for it here. // At 1MHz MCU speed, it is so slow that it took twice as long to // do the arithmetic as we wanted to wait. Adding the actual wait // on top of that made the characters 3 times as wide as desired. OCR0A=0xff; // Set top to MAX (which we hopefully won't reach) TCNT0=0; // Set counter to zero TCCR0B |= (1< SLOW_SPEED_TRANSITION ) { PrescaleFactor=PRESCALE_256; PauseTime/=4; } // Final sanity check if( PauseTime > 255 ) { PauseTime=255; } else if( PauseTime <= 0 ) { PauseTime=1; } // Do the actual on-time pause. TinyPause((unsigned char)PauseTime,PrescaleFactor); if( RestartCycle ) { // Revolution interrupt occurred - we're done with this run. // Extinguish output if( style & INVERTED_TEXT ) { PORTA=PORTB=0xff; } else { PORTA=PORTB=0x00; } return; } } // End for() loop if( ! (style & ADJACENT_CHARACTERS) ) { // Create a small gap between characters if( style & INVERTED_TEXT ) { PORTA=PORTB=0xff; } else { PORTA=PORTB=0x00; } // Calculate size of gap based on speed (for stable display) // Time how long the calculation takes (it's significant!) OCR0A=0xff; // Set top to MAX (which we hopefully won't reach) TCNT0=0; // Set counter to zero TCCR0B |= (1< SLOW_SPEED_TRANSITION ) { PrescaleFactor=PRESCALE_256; PauseTime/=4; } // Final sanity check if( PauseTime > 255 ) { PauseTime=255; } else if( PauseTime <= 0 ) { PauseTime=1; } // Do the actual pause for our gap between characters TinyPause((unsigned char)PauseTime,PrescaleFactor); } // End if() for styles } // End function ShowChar8() ////////////////////////////////////////////////////////////////////////////// // GetTptr8() - function maps an ASCII character to its dot definition. // // Parameters: // unsigned char character [in] - the character to be displayed. // unsigned char *tptr [out] - pointer to dot definition. // // Returns: // (none) ////////////////////////////////////////////////////////////////////////////// void GetTptr8(unsigned char character, unsigned char **tptr) { switch(character) { // // Space Invaders // case SI_1: *tptr=si_1; break; case SI_2: *tptr=si_2; break; case SI_3: *tptr=si_3; break; case SI_4: *tptr=si_4; break; case Notes: *tptr=notes; break; case 'A': *tptr=A; break; case 'B': *tptr=B; break; case 'C': *tptr=C; break; case 'D': *tptr=D; break; case 'E': *tptr=E; break; case 'F': *tptr=F; break; case 'G': *tptr=G; break; case 'H': *tptr=H; break; case 'I': *tptr=I; break; case 'J': *tptr=J; break; case 'K': *tptr=K; break; case 'L': *tptr=L; break; case 'M': *tptr=M; break; case 'N': *tptr=N; break; case 'O': *tptr=O; break; case 'P': *tptr=P; break; case 'Q': *tptr=Q; break; case 'R': *tptr=R; break; case 'S': *tptr=S; break; case 'T': *tptr=T; break; case 'U': *tptr=U; break; case 'V': *tptr=V; break; case 'W': *tptr=W; break; case 'X': *tptr=X; break; case 'Y': *tptr=Y; break; case 'Z': *tptr=Z; break; case 'a': *tptr=a; break; case 'c': *tptr=c; break; case 'e': *tptr=e; break; case 'i': *tptr=i; break; case 'g': *tptr=g; break; case 'l': *tptr=l; break; case 'm': *tptr=m; break; case 'n': *tptr=n; break; case 'o': *tptr=o; break; case 'p': *tptr=p; break; case 'r': *tptr=r; break; case 't': *tptr=t; break; case 'u': *tptr=u; break; case '1': *tptr=num_1; break; case '2': *tptr=num_2; break; case '3': *tptr=num_3; break; case '4': *tptr=num_4; break; case '5': *tptr=num_5; break; case '6': *tptr=num_6; break; case '7': *tptr=num_7; break; case '8': *tptr=num_8; break; case '9': *tptr=num_9; break; case '0': *tptr=num_0; break; case ' ': *tptr=Punct_Space; break; case '!': *tptr=Punct_Exclaim; break; case '?': *tptr=Punct_Question; break; case ':': *tptr=Punct_Colon; break; case ',': *tptr=Punct_Comma; break; case '.': *tptr=Punct_Period; break; case '_': *tptr=Punct_Underline; break; case '-': *tptr=Punct_Minus; break; case '/': *tptr=Punct_Slash; break; case ';': *tptr=Punct_Semicolon; break; case '#': *tptr=Punct_Pound; break; case '$': *tptr=Punct_Dollar; break; case '%': *tptr=Punct_Percent; break; case '&': *tptr=Punct_Ampersand; break; case '=': *tptr=Punct_Equals; break; case '+': *tptr=Punct_Plus; break; case '<': *tptr=Punct_LessThan; break; case '>': *tptr=Punct_GreaterThan; break; case '\'': *tptr=Punct_Tic; break; case '"': *tptr=Punct_DoubleQuotes; break; case '(': *tptr=Punct_OpenParen; break; case ')': *tptr=Punct_CloseParen; break; default: *tptr=Punct_Question; break; } // End switch() } // End function GetTptr8() ////////////////////////////////////////////////////////////////////////////// // ShowChar15() - function displays a single 15-dot character on the POV device. // // Parameters: // unsigned char character [in] - the character to be displayed. // int size [in] - the number of columns which comprise the character. // char style [in] - controls the way in which the character is displayed. // (i.e., reverse video, etc) // // Returns: // (none) ////////////////////////////////////////////////////////////////////////////// void ShowChar15(unsigned char character, char style) { unsigned char (*tptr)[2]; int countF, countB, size; PrescaleFactor=PRESCALE_64; switch(character) { // case CCC: tptr=ccc; size=sizeof(ccc)/2; break; // web address // case Discs: tptr=discs; size=sizeof(discs)/2; break; // 5.25" // case Flame: tptr=flame; size=sizeof(flame)/2; break; // case LightningBolt: tptr=lightningbolt; size=sizeof(lightningbolt)/2; // break; case ALLSUITS: tptr=allsuits; size=sizeof(allsuits)/2; break; case BLACKBAT: tptr=blackbat; size=sizeof(blackbat)/2; break; case CHECKERED: tptr=checkered; size=sizeof(checkered)/2; break; case MULTIDOTS: tptr=multidots; size=sizeof(multidots)/2; break; case OPPOSEDTREES: tptr=opposedtrees; size=sizeof(opposedtrees)/2; break; case OPPOSEDTRIANGLES: tptr=opposedtriangles; size=sizeof(opposedtriangles)/2; break; case WAVYTRIANGLES: tptr=wavytriangles; size=sizeof(wavytriangles)/2; break; case Bug: tptr=bug; size=sizeof(bug)/2; break; case Circuit: tptr=circuit; size=sizeof(circuit)/2; break; case Egypt: tptr=egypt; size=sizeof(egypt)/2; break; // case AnalogClock: tptr=analogclock; size=sizeof(analogclock)/2; break; // case ENVELOPE: tptr=envelope; break; // case SLANTYSTRIPES: tptr=slantystripes; break; // case STAR_1: tptr=star_1; break; // case TRACEFAMILY: tptr=tracefamily; break; // case TRIANGLEXES: tptr=trianglexes; break; // case Jack_O_Lantern: tptr=jack_o_lantern; break; // case Ghost: tptr=ghost; break; // case Grid: tptr=grid; break; // case SkullSolid: tptr=skull_solid; break; default: tptr=egypt; size=sizeof(egypt)/2; break; } // End switch() for(countF=0,countB=size-1;countF=0;countF++,countB--) { if( style & INVERTED_TEXT ) { PORTA=~(tptr[countF][0]); // front side, top half PORTB=~(tptr[countB][0]); // back side, top half PORTC=~(tptr[countF][1]); // front side, bottom half PORTD=~(tptr[countB][1]); // back side, bottom half } else { PORTA=tptr[countF][0]; // front side, top half PORTB=tptr[countB][0]; // back side, top half PORTC=tptr[countF][1]; // front side, bottom half PORTD=tptr[countB][1]; // back side, bottom half } // Next we'll calculate on-time based on speed (for a stable display). // We also need to time how long this calculation takes so we can // subtract it from the total on-time. Floating point arithmetic // is extremely slow, so we really need to allow for it here. // At 1MHz MCU speed, it is so slow that it took twice as long to // do the arithmetic as we wanted to wait. Adding the actual wait // on top of that made the characters 3 times as wide as desired. OCR0A=0xff; // Set top to MAX (which we hopefully won't reach) TCNT0=0; // Set counter to zero TCCR0B |= (1< SLOW_SPEED_TRANSITION ) { PrescaleFactor=PRESCALE_256; PauseTime/=4; } // Final sanity check if( PauseTime > 255 ) { PauseTime=255; } else if( PauseTime <= 0 ) { PauseTime=1; } // Do the actual on-time pause. TinyPause(PauseTime,PrescaleFactor); if( RestartCycle ) { // Revolution interrupt occurred - we're done with this run. // Extinguish output if( style & INVERTED_TEXT ) { PORTA=PORTB=0xff; PORTC=PORTD=0xfe; } else { PORTA=PORTB=PORTC=PORTD=0x00; } return; } } // End for() loop if( ! (style & ADJACENT_CHARACTERS) ) { // Create a small gap between characters if( style & INVERTED_TEXT ) { PORTA=PORTB=0xff; PORTC=PORTD=0xfe; } else { PORTA=PORTB=PORTC=PORTD=0x00; } // Calculate size of gap based on speed (for stable display) // Time how long the calculation takes (it's significant!) OCR0A=0xff; // Set top to MAX (which we hopefully won't reach) TCNT0=0; // Set counter to zero TCCR0B |= (1< SLOW_SPEED_TRANSITION ) { PrescaleFactor=PRESCALE_256; PauseTime/=4; } // Final sanity check if( PauseTime > 255 ) { PauseTime=255; } else if( PauseTime <= 0 ) { PauseTime=1; } // Do the actual pause for our gap between characters TinyPause(PauseTime,PrescaleFactor); } // End if() for styles } // End function ShowChar15() ////////////////////////////////////////////////////////////////////////////// // PowerOnVisual() - Do a little light show so user knows board is on/working. ////////////////////////////////////////////////////////////////////////////// void PowerOnVisual() { int Slot, iters; for(iters=0;iters<2;iters++) { // Move down the top 8 bits for(Slot=0;Slot<8;Slot++) { PORTA=PORTB=(1<=0;Slot--) { PORTC=PORTD=(1<=1" instead of "Slot>=0" for our test condition // because we don't want to repeat the top dot when "bouncing", // which would cause the bounce to hang on the top dot just a bit // too long. This requires us to hit the top dot manually after the // outer loop completes so it isn't skipped on the last run. for(Slot=7;Slot>=1;Slot--) { PORTA=PORTB=(1< SLOW_SPEED_TRANSITION ) { PrescaleFactor=PRESCALE_256; OnPauseTime/=4; } // Final sanity check if( OnPauseTime > 255 ) { OnPauseTime=255; } else if( OnPauseTime <= 0 ) { OnPauseTime=1; } if( RestartCycle ) { return; } // Now calculate "Off" time if( ! (style & ADJACENT_CHARACTERS) ) { OffPauseTime=(int)( (float)((float)Clicks*CONST_OFFTIME) ); // Now if we are moving very slowly (say, below 10 MPH), we need to // adjust our timer so we can wait even longer periods of time to // keep the display stable. if( Clicks > SLOW_SPEED_TRANSITION ) { //PrescaleFactor=PRESCALE_256; // Already done for "on" time. OffPauseTime/=4; } // Final sanity check if( OffPauseTime > 255 ) { OffPauseTime=255; } else if( OffPauseTime <= 0 ) { OffPauseTime=1; } } if( RestartCycle ) { return; } // Now display the characters int size=7; // Size (in bytes) of 8-dot character int idxLeft, idxRight, countLeft, countRight; unsigned char *tptrLeft, *tptrRight; for(idxLeft=0, idxRight=strlen(string)-1; idxLeft=0; idxLeft++, idxRight--) { // Get character definition pointers GetTptr8(string[idxLeft],&tptrLeft); GetTptr8(string[idxRight],&tptrRight); if( RestartCycle ) { break; } for(countLeft=0,countRight=size-1; countLeft=0; countLeft++,countRight--) { if( style & INVERTED_TEXT ) { PORTA=~(tptrLeft[countLeft]); // Dark text, light background PORTB=~(tptrRight[countRight]); } else { PORTA=tptrLeft[countLeft]; // Light text, dark background PORTB=tptrRight[countRight]; } // Do the actual on-time pause. TinyPause((unsigned char)OnPauseTime,PrescaleFactor); if( RestartCycle ) { // Revolution interrupt occurred - we're done with this run. // Extinguish output if( style & INVERTED_TEXT ) { PORTA=PORTB=0xff; } else { PORTA=PORTB=0x00; } return; } } // End for() loop // Create a small gap between characters if( ! (style & ADJACENT_CHARACTERS) ) { if( style & INVERTED_TEXT ) { PORTA=PORTB=0xff; } else { PORTA=PORTB=0x00; } // Do the actual pause for our gap between characters TinyPause((unsigned char)OffPauseTime,PrescaleFactor); } // End if() for "off" time } // End for() loop // Turn off display after run. if( style & INVERTED_TEXT ) { PORTA=PORTB=0xff; } else { PORTA=PORTB=0x00; } } // End function ShowString8() ////////////////////////////////////////////////////////////////////////////// // ShowSpeed() - function displays speed in MPH, and possibly other info // as needed, such as distance traveled or clicks/revolution. ////////////////////////////////////////////////////////////////////////////// void ShowSpeed() { volatile char text[50]; // If we are moving too slowly, extinguish output if( Clicks > MIN_SPEED ) { PORTA=PORTB=PORTC=PORTD=0x00; return; } // Calculate MPH MPH=CONST_26_INCH_WHEEL / (float)Clicks; volatile int LeftPart=(int)MPH; volatile int RightPart=(int)( (MPH-(float)LeftPart)*100.0 ); // Calculate fractional-mile distance traveled int FractionalMiles=(Revolutions*100)/REVS_PER_MILE; // Output results sprintf((char *)text,"%d.%02d MPH %d.%02d MILES", LeftPart,RightPart,Miles,FractionalMiles); if( RestartCycle ) { return; } ShowString8((char *)text,DEFAULT_STYLE); return; } // End function ShowSpeed() void ShowSpaceInvaders() { ShowChar8(SI_1,DEFAULT_STYLE); if( RestartCycle ) return; ShowChar8(' ',DEFAULT_STYLE); if( RestartCycle ) return; ShowChar8(SI_2,DEFAULT_STYLE); if( RestartCycle ) return; ShowChar8(' ',DEFAULT_STYLE); if( RestartCycle ) return; ShowChar8(SI_3,DEFAULT_STYLE); if( RestartCycle ) return; ShowChar8(' ',DEFAULT_STYLE); if( RestartCycle ) return; ShowChar8(SI_4,DEFAULT_STYLE); if( RestartCycle ) return; ShowChar8(' ',DEFAULT_STYLE); return; } // End function ShowSpaceInvaders()