📄 main.c
字号:
pNum++; // If we have moved off the edge of the character, then // we need to move to the next character if (pNum == 16) { // If we will wrap around to the first character, turn off the pixel // timer, clear the display, and exit. Might speed things up a bit if (cNum == 15) { TCCR1B &= ~0x7; set_all(~0x00); return; } pNum = 0; // reset to first pixel cNum = (cNum+1) & 0x0F; // move, with wrap, to next character position // 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 // get the third line, which may wrap.. cLine++; if (cLine == NUM_LINES) { cLine = 0; } memcpy_P(scrollLine,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 = scrollLine + (dCode & 0x0F); dynamicType = dCode; } #endif #endif } } #else // we could do this just once when the app initializes, but // for now, let's do it here. Later we'll move it. cur_line = line_shift = 0; memcpy_P(topLine,lines,16); memcpy_P(botLine,lines+16,16); #ifdef DYNAMIC dCode = pgm_read_byte(dInfo); if (dCode != 0) { newDynamicPtr = topLine + (dCode & 0x0F); dynamicType = dCode; } dCode = pgm_read_byte(dInfo+1); if (dCode != 0) { newDynamicPtr = botLine + (dCode & 0x0F); dynamicType = dCode; } #endif #endif // Set the character and pixel numbers so they will overflow // on the next pixel interrupt, and cause the correct data to // be loaded. charNum = 31; // will wrap to 0, the first char. Not set to 15 as you might // expect, because when it hits 15 again, the pixel output // routine will shut down. But (31+1) mod 16 = (15+1) mod 16... pixelNum = 15; // will wrap to 0, the first pixel #ifdef USE_LOCAL_TIMER1 clean = 0; // flag that we changed things #endif // Start TIMER1 on its merry way... TCCR1B |= _BV(CS10); // increment at clock/1 TIMSK |= _BV(OCIE1A); // enable interrupt when it matches OCR1A
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -