📄 main.c
字号:
sensor_timer.bytes.high_byte = 0xFF; }}// Interrupt 1 executes when the hall effect sensor fires// QUESTION: unlike the pixel output interrupt, this one// doesn't sei(). Why?SIGNAL (SIG_INT1) { // make sure we don't get bitten by the watchdog asm("wdr"); // 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.bytes.high_byte == 0x00) && (sensor_timer.bytes.low_byte > 0x03)) { // 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); OCR1AH = sensor_timer.bytes.low_byte; OCR1AL = TCNT0; // Clear the residual of TIMER0 TCNT0 = 0; // if we are shifting the shiftReg, get that done if (shiftDir != 0x00) { if (shiftDir == 0x01) { // rotate towards center - left shift. Basically, shift // the 32 bit quantity, and make allowances for the highbit if ( (shiftReg.bytes.high_byte & 0x80) == 0) { // shift ok, there is no carry shiftReg.longWord = shiftReg.longWord << 1; } else { // carry, so we set low bit shiftReg.longWord = shiftReg.longWord << 1; shiftReg.bytes.low_byte = shiftReg.bytes.low_byte | 0x01; } } else { // rotate towards perimeter... opposite of above if ( (shiftReg.bytes.low_byte & 0x01) == 0) { // shift ok, there is no carry shiftReg.longWord = shiftReg.longWord >> 1; } else { // carry, so we set high bit shiftReg.longWord = shiftReg.longWord >> 1; shiftReg.bytes.high_byte = shiftReg.bytes.high_byte | 0x80; } } } // Check the line timer; if it has reached a particular value // then decrement curTime. If that hits zero, then increment // the curElementPtr and load in the new data if (line_timer >= SCROLLSPEED) { line_timer = line_timer - SCROLLSPEED; // reset in a safe way that retains any residual curTime--; if (curTime == 0x00) { curElementPtr++; if (curElementPtr >= NUM_ELEMENTS) { curElementPtr = 0; } curElement = pgm_read_byte(elementList+curElementPtr); curTime = pgm_read_byte(elementTime+curElementPtr); // Reset the revolution counter, this is used by some of the effects // that do animation. curRev = 0xFF; // Clear the shift direction shiftDir = 0x00; // If new element is a shift, we need to initialize it if ( (curElement & 0xF0) == 0x10) { // hi bit of nibble tells us shift direction // to avoid conditional, and to differentiate from the 0x00 // no shift setting, we use values 0x09 and 0x01 for shift direction // codes shiftDir = (curElement & 0x08) | 0x01; // low two bits tell us what pattern to use if ( (curElement & 0x01) == 0x00 ) { if ( (curElement & 0x02) == 0x00) { // 0x00 case - 2 banded, 50% on shiftReg.longWord = 0xFFFF0000; } else { // 0x01 case shiftReg.longWord = 0xFFFFFF00; } } else { if ( (curElement & 0x02) == 0x00) { // 0x10 case shiftReg.longWord = 0xFF000000; } else { // 0x11 case shiftReg.longWord = 0xFF00FF00; } } } } } // Figure out the eeprom start position. This will only actually // be valid for modes 0-3, but since it isn't used if we are not // in these modes, there's no harm, and no sense wasting space on // a conditional! #ifdef CLOCKWISE eepromPtr.bytes.high_byte = curElement << 2; eepromPtr.bytes.low_byte = 0x00; #else eepromPtr.bytes.high_byte = (curElement << 2) | 0x03; eepromPtr.bytes.low_byte = 0xFC; #endif // Increment the revolution counter curRev++; // and clear the count curPixel = 0x00; // Start TIMER1 on its merry way... TCCR1B |= _BV(CS10); // increment at clock/1 TIMSK |= _BV(OCIE1A); // enable interrupt when it matches OCR1A } else { // Since we don't have a valid setting for the rotation // speed, set a couple of LEDs to let the human know we // aren't dead yet, and turn off the timer. set_all(~0x0F); TCCR1B &= ~_BV(CS10); // no incrementing = no interrupting // reset the line timers so that when we get a valid spinup, // they will start clocking the lines across the display line_timer = SCROLLSPEED; // delay figure, will trigger wrap } // Whether we're displaying or not, we reset sensor_timer so we can // time the next revolution. sensor_timer.word = 0; } // Finally, reset hall_debounce so we won't execute the timer reset code // until the Hall Effect sensor hasn't bothered us for a reasonable while. hall_debounce = 0; // *** PORTB &= ~0x8;}// Initialize the IO pins on the ATMEL.void ioinit(void) { // Set the data direction for the PORTD and PORTB pins; see the // circuit diagram for more information on this. DDRD = 0x73; // input on PD2 (button), PD3 (sensor), all other output DDRB = 0xDF; // input on MOSI/DI (for SPI), all others output // Deselect EEPROM. Not being an EE, I'm not going to worry about // how the ATMEL talks to the EEPROM. It's black magic. PORTB = _BV(SPIEE_CS); // Just above, we set PD2 and PD3 to input. If we now set those // bits to 1, they set into pullup mode (again, not EE, claim // ignorance), which is essential for them to work. We also set // the SENSORPOWER bit to 1, which sends out little dribbles of // electrons to the hall effect sensor (see circuit diagram) // // Finally, we write 0's to the FRONT and BACK pins, which control // which bank of 30 LEDs we are talking to. Having both of these // on at the same time probably causes horrible things to happen. PORTD = (_BV(BUTTON) | _BV(SENSOR) | _BV(SENSORPOWER)) & ~_BV(FRONT) & ~_BV(BACK); // Rather than poll to see when the hall effect sensor and // button are pressed, we configure an interrupt handler. If you // look at the circuit diagram, you'll see that PD3 and PD2, which // are wired to SENSOR IN and BUTTON IN, do double-duty as INT1 // and INT0. They are both SUPPOSEDLY set to interrupt on the // falling edge of a pulse from the devices. (Page 63) // POSSIBLE BUG: ISC0{1,0} seems to be being set to 00, not 10 // as ISC1{1,0} is being set to. So ISC0 will trigger when // the button interrupt line goes low. Either this is a bug, // or the original comment was not correct (likely, IMHO) MCUCR = _BV(ISC11) & ~_BV(ISC01) & ~_BV(ISC00) & ~_BV(ISC10); // Activate the interrupts by setting the General Interrupt Mask // Register (Page 63) GIMSK = _BV(INT1) | _BV(INT0); // The ATMEL has built-in timers that can trigger an interrupt. // SpokePOV uses them to update the LEDs 256 times per rotation. // Timer 0 is set to update at a rate system-clock / 256 and // interrupt when it overflows (8 bit). This means that it // triggers every 65536 cycles. TCCR0A = 0; // normal, overflow (count up to 256 == num pixels) TCCR0B = _BV(CS02); // clk/256 TIMSK |= _BV(TOIE0); // turn on overflow interrupt // Timer 1 (T1) is the pixel timer, which is used to update the // LEDs 256 times per rotation. It's set up as a normal timer // as well. See Page 108&71; it is apparently being set into CTC // mode 4. This means that the counter is compared to a 16-bit value // and interrupts when it reaches this value. // // Adjusting this value is how the SpokePOV compensates for // changes in the rotation speed of the device. // // Note that at this point, the timer is initialized, but not // activated. TCCR1A = 0; TCCR1B = _BV(WGM12); // Clear the debounce values, which I haven't sussed out yet. hall_debounce = 0; sensor_timer.word = 0; // set the element variables so they'll wrap around. curElementPtr = NUM_ELEMENTS-1; curTime = 0x01; line_timer = SCROLLSPEED; // delay figure, will trigger wrap }// Set all the LEDs on a side to have the same// repeating 8-bit value (ie: 0x00 = all on, 0xFF = all off)// Added by RJW to permit a more comprehensive reset displayvoid set_all(uint8_t blockValue) { fleds[0] = fleds[1] = fleds[2] = fleds[3] = blockValue; clock_scroll(0); }int main(void) { uint8_t cmd; // the reason we reset // MCUSR is the MCU Status Register (page 40). It tells us // why we reset, and a reset is the only way to get here. cmd = MCUSR; // The first order of business is to tell the chip that // we've got things under control. MCUSR = 0; // Turn on watchdog timer immediately, this protects against // a 'stuck' system by resetting it. // WDTCSR is the Watchdog Timer Control Register (page 45). // We set it so that it'll generate a watchdog interrupt // every second. The idea is that if things mess up, // the watchdog will kickstart us. WDTCSR = _BV(WDE) | _BV(WDP2) | _BV(WDP1); // 1 second // Initialize the various pins of the ATMEL, and set up // the interrupts. ioinit(); // Show that we are active. set_all(~0x03); // enable the interrupts. I think this is not needed // since it'll immediately be done by the loop, below. sei(); // Loop until we timeout, at which point the ATMEL is // put to sleep. If the communications routine timed // out, or the user pressed the button for >500ms, // then sensor_timer will be 0xFFFF and we'll immediately // sleep. // Change to for (;;) to see if it makes any difference for (;;) { // Reset the watchdog Timer. // // QUESTION: What's with toggling the PD0 output line here? // it doesn't seem to be connected to anything according to // the circuit diagram... // *** PORTD |= 0x1; asm("wdr"); // *** PORTD &= ~0x1; // If the sensor_timer (incremented by TIMER0) maxes out // (in about 3 minutes), then sleep everything. if (sensor_timer.bytes.high_byte == 0xFF) { // Avoid pesky interruptions cli(); // Turn off all LEDs - I guess LED 0 is one of the "invisible ones" set_all(0xFF); // Turn off power to the Hall Effect sensor. SENSOR_PORT &= ~_BV(SENSORPOWER); // Deselect EEPROM SPIEE_CS_PORT |= _BV(SPIEE_CS); // pull CS high to deselect // Turn off Watchdog (must be restarted when we get the wakeup) // Wakeup will be via the button interrupt. WDTCSR |= _BV(WDCE) | _BV(WDE); WDTCSR = 0; MCUCR |= _BV(SM1) | _BV(SM0) | _BV(SE); // Re-enable interrupts so we can get the wakeup! sei(); // Go into sleep mode asm("sleep"); } } // *** PORTD |= 0x2;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -