📄 spokepov.lss
字号:
TCCR1B &= ~0x7;
set_all(~0x00);
return;
}
pNum = 0; // reset to first pixel
cNum = (cNum+1) & 0x0F; // move, with wrap, to next character position
// If we have wrapped around to the first character, turn off the pixel
// timer, clear the display, and exit.
if (cNum == 15) {
TCCR1B &= ~0x7;
set_all(~0x00);
return;
}
// Now we need to reset the pointers to the correct addresses
// in the EEPROM for the characters they display. The earlier
// decision to store these in a format that makes them display
// nicely on the original SpokePOV software makes this a little
// bit difficult.
cCode = topLine[cNum]-32; // character number for the top char, 0-95
// In the character set, 2 characters are stored vertically stacked.
// So each group of 2 characters takes up 64 bytes (4x16).
tChar = ((cCode >> 1) << 6) | ( (cCode & 0x01) << 1 );
// Ditto for bChar...
cCode = botLine[cNum]-32;
bChar = ((cCode >> 1) << 6) | ( (cCode & 0x01) << 1 );
#ifdef SMOOTHSCROLL
// and if smooth scrolling, sChar
cCode = scrollLine[cNum]-32;
sChar = ((cCode >> 1) << 6) | ( (cCode & 0x01) << 1 );
#endif
} else {
// If we haven't wrapped around a character boundary, we just move
// to the next line in the character set, which is 4 pixels offset
// in each case
tChar += 4;
bChar += 4;
#ifdef SMOOTHSCROLL
sChar += 4;
#endif
}
// Unfortunately, we can't do the cute "read from the EEPROM right
// into the LEDs trick" that limor can do in SpokePOV. We have to
// read the data into the ATMEL and then write it out.
spieeprom_read(tChar,fleds,2); // the top 2 bytes
spieeprom_read(bChar,fleds+2,2); // and the bottom 2
#ifdef SMOOTHSCROLL
spieeprom_read(sChar,fleds+4,2); // and the scroll characters
#endif
// However, we do have a fancy trick of our own. If we are
// smooth scrolling, then we clock out an extra line_shift
// BITS, thus implementing the smooth scrolling
#ifdef SMOOTHSCROLL
clock_scroll(line_shift); // send 0-15 extra bits..
#else
clock_scroll(0); // just send the 4 bytes
#endif
// Now we increment the variables. However, we
// only do it IF haven't been touched by some other part
// of the code while we were displaying the pixels. Also, we
// turn interrupts off so that nobody else can touch them
// while we are at it
#ifdef USE_LOCAL_TIMER1
cli();
if (clean) {
topChar = tChar;
botChar = bChar;
charNum = cNum;
pixelNum = pNum;
#ifdef SMOOTHSCROLL
scrollChar = sChar;
#endif
} else {
// Since we didn't update the data, we know it was changed, and
// we know that next time, we CAN update the data. So everything
// is clean now!
clean = 1;
}
sei();
#endif
#ifdef DONOTCOMPILE
// The following code is obsoleted by the fact that we turn off the
// timer in the main code, above.
} else {
// We have not seen the magnet in a while, so turn off the
// pixel timer...
// *** PORTA |= 0x2;
// Turn off this pixel timer
// Question: this is different from the code in SIG_INT1. Why?
TCCR1B &= ~0x7;
// display reason for turnoff
set_all(~0x07);
// *** PORTA &= ~0x2;
#endif
}
// *** PORTB &= ~0x2;
}
// Interrupt 0 executes when the button is pressed.
// QUESTION: unlike the pixel output interrupt, this one
// doesn't sei(). Why?
SIGNAL (SIG_INT0) {
uint16_t timer;
// *** PORTB |= 0x4;
// Twiddle our thumbs until the user releases the
// button - but measure how long he takes...
timer = 0;
while (! (BUTTON_PIN & _BV(BUTTON))) {
timer++;
delay_ms(1);
}
// A short (<500ms) press will just restart the watchdog
// timer. I think this explains the structure of the
// main() function; it doesn't have a loop to keep it
// listening for commands if it times out, but pressing
// the button will restart it.
// We do expect that the button will be down at least
// a small period of time...
if (timer > BUTTON_DEBOUNCE) {
// If a quick press...
if (timer < 500UL) {
// Re-enable the watchdog timer, then loop until
// it fires off.
WDTCSR = _BV(WDE);
while (1);
} else {
// We want to shut everything down. Setting sensor_timer
// to the pin value will cause both the communications
// loop and the regular timeout loop in the main() to
// give up, which results in the device going to sleep.
sensor_timer = 0xFFFF;
}
}
// *** PORTB &= ~0x4;
}
// Interrupt 1 executes when the hall effect sensor fires
// QUESTION: unlike the pixel output interrupt, this one
// doesn't sei(). Why?
SIGNAL (SIG_INT1) {
#if NUM_LINES > 0
uint8_t cLine; // temp var used in scroll code
#endif
#ifdef DYNAMIC
uint8_t dCode; // temp var used in dynamic code
#endif
// make sure we don't get bitten by the watchdog
asm("wdr");
// *** PORTB |= 0x8;
// The first issue we need to deal with when the hall-effect
// sensor tells us it sees the magnet is to avoid doing any
// processing if we get an interrupt too soon after the previous
// interrupt.
// hall_debounce is incremented by TIMER0, which fires every 3ms
// or so. At the current setting of 4, this means that at least
// 15ms must elapse per trigger, which translates to about 4000
// rpm.
if (hall_debounce > HALL_DEBOUNCE_THRESH) {
// We know the number of ms since the last hall sensor trigger
// and there are 128 radial 'pixels' per sweep so divide to get
// the necessary ms between the pixel interrupts
// QUESTION: 128 or 256?
// Then we just make TIMER1 trigger at that rate!
// Reset the Timer Count Register for TIMER1 to 0, so it will
// begin counting up.
TCNT1 = 0;
// sensor_timer contains the number of TIMER0 interrupts since
// the last time we updated TIMER1. If it has a reasonable
// value, then we use it to reset the TIMER1 clock.
if ((sensor_timer < 0xFF) && (sensor_timer > 0x3)) {
// TIMER1 works differently from TIMER0. It's a 16-bit timer
// that apparently increments at the system clock rate.
//
// Because TIMER0 increments at 1/256 of the clock rate, and
// fires the interrupt only when it overflows, sensor_timer
// is incremented once ever 256*256 cycles.
//
// We want TIMER1 to fire off 256 times around the loop, so
// we can display 256 lines of pixels. We do this by putting
// sensor_timer into the high byte of TIMER1's comparator
// value, and the residual of TIMER0 (what it's counted up
// to since the last time sensor_timer was incremented) into
// the low byte, effectively a fractional value!
//
// Since TIMER0 is incrementing at 1/256 of the rate of TIMER1,
// this results in TIMER1 firing off 256 times per rotation,
// with excellent time resolution.
//
// I was quite touched by the elegance of how this works out;
// it may be able to handle the extreme RPMs of BrightSaber
// without modification...
// Set the TIMER1 comparator value. As a hack to Limor's hack,
// reduce the timing a little bit so that the display doesn't
// span the full 360 degrees, thus giving us a little more time
// around hall-effect interrupt time to do things. An attempt
// to get more speed, it doesn't seem to help - so back to the
// old way of doing things.
// OCR1A = ((sensor_timer << 8) | TCNT0) - (sensor_timer);
OCR1A = ((sensor_timer << 8) | TCNT0);
// Clear the residual of TIMER0
TCNT0 = 0;
// If we are in dynamic mode and want the rev counter, increment the rev counter
#ifdef DYNAMIC_REVCOUNT
// Increment the 4 byte rev counter - stored in reverse
// byte order
dynamicCounter[0]++;
dCode = 0;
while (dynamicCounter[dCode] > '9') {
dynamicCounter[dCode] = '0';
if (dCode != 3) {
dynamicCounter[++dCode]++; // Dont'cha just love compressed C syntax...?
}
}
#endif
// if we have only 2 lines, then we never scroll
// otherwise, we have to move between the lines.
// The code for no scrolling is NUM_LINES = 0
#if NUM_LINES > 0
// Check the line timer; if it has reached a particular value
// then increment line_shift. When that reaches 16, wrap it
// and increment cur_line. This system lets us be a little
// more flexible in our timing system, and permits long delays
// in the scrolling.
if (line_timer_l > 15) {
line_timer_l = line_timer_l - 16; // reset in a safe way that retains any residual
line_shift = (line_shift + 1) & 0x0f; // increment line_shift
if (line_shift == 0x00) {
// Move down 1 line in the line list, wrapping around
// Made the mistake of using % which isn't a good thing
// on a chip without mult/div...
cur_line++;
if (cur_line == NUM_LINES) {
cur_line = 0;
}
// Move the new first line into topLine. We must shift
// the line number 4 bits (x16) to index the correct line.
memcpy_P(topLine,lines+(cur_line << 4),16);
// If we are doing dynamic data, set it if there
// is such data in the line.
#ifdef DYNAMIC
dCode = pgm_read_byte(dInfo+cur_line);
if (dCode != 0) {
newDynamicPtr = topLine + (dCode & 0x0F);
dynamicType = dCode;
}
#endif
// Get the second line, which may wrap..
cLine = (cur_line + 1);
if (cLine == NUM_LINES) {
cLine = 0;
}
memcpy_P(botLine,lines+(cLine << 4),16);
// If we are doing dynamic data, set it if there
// is such data in the line.
#ifdef DYNAMIC
dCode = pgm_read_byte(dInfo+cLine);
if (dCode != 0) {
newDynamicPtr = botLine + (dCode & 0x0F);
dynamicType = dCode;
}
#endif
#ifdef SMOOTHSCROLL
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -