main.c
来自「旋转16个LED灯控制程序」· C语言 代码 · 共 993 行 · 第 1/2 页
C
993 行
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 = 0; }// Delay for a specified number of milliseconds using some// assembly code. Will this be dependant on the clock speed?void delay_ms(unsigned char ms){ unsigned short delay_count = F_CPU / 4000; unsigned short cnt; asm volatile ("\n" "L_dl1%=:\n\t" "mov %A0, %A2\n\t" "mov %B0, %B2\n" "L_dl2%=:\n\t" "sbiw %A0, 1\n\t" "brne L_dl2%=\n\t" "wdr\n\t" "dec %1\n\t" "brne L_dl1%=\n\t":"=&w" (cnt) :"r"(ms), "r"((unsigned short) (delay_count)) );}// All of the following routines have been modified so they// only deal with the front leds, for speed, and to save// code space!// Sends the 4-byte LED pixel data block out// over the serial link. Front LEDs onlyvoid clock_leds(void) { // QUESTION: this code sends 4 bytes over the link to // update the LEDs. But spieeprom_read_into_leds() in // eeprom.c sends 5. Why the difference? // Also, to get the character set to display properly, // I had to send out the bytes in a slightly shuffled // order. Not sure WHY this is so, but it works... spi_transfer(fleds[2]); spi_transfer(fleds[3]); spi_transfer(fleds[0]); spi_transfer(fleds[1]); LATCH_SELECT_PORT |= _BV(FRONT); NOP; NOP; NOP; NOP; LATCH_SELECT_PORT &= ~_BV(FRONT);}// Turn on a single LED, turning off all the other LEDsvoid set_led(uint8_t led) { fleds[0] = fleds[1] = fleds[2] = fleds[3] = 0xFF; fleds[led/8] = ~_BV(led%8); clock_leds();}// 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_leds();}// Test the LEDs on power-on. Runs through them// quickly, then displays alternating LEDs, and// finally puts them all on. This test sequence// is slightly modified from the original, and// makes it easier to see problems with the LEDs.void test_leds(void) { uint8_t i; // Quick run through the LEDs for(i=0; i< 33; i++) { set_led(i); delay_ms(10); } // Set groups of 8 LEDs to the same value. // Note that the LED state is the opposite // of what you might expect: // // 0 bits = on, 1 bits = off! // Light every other LED set_all(0xAA); delay_ms(50); // Now light the other LEDs set_all(0x55); set_all(0x55); delay_ms(50); // Now light all LEDs set_all(0x00); delay_ms(255); delay_ms(255); // likely the 1-second reset timer will go off before // this ends. But no biggy, since if it does, it'll // reset the LEDs.. } int main(void) { uint8_t buff[16]; // a buffer of 16 bytes read/written to EEPROM uint8_t i, n; // loop variables uint8_t cmd; // the reason we reset uint16_t addr; // address to read/write in EEPROM // 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(); // We saved the reason for the reset of the chip. If // it's a power-on, then we run a test pattern through // the LEDs. Note that because we've set a 1-second // watchdog timer (in ioinit), if this test sequence // takes more than a second, the chip will reset. But // since we'll know it isn't a power-on, the test // sequence won't run... if ((cmd & _BV(PORF)) != 0) test_leds(); // display the reason for the reset on the LEDs. set_led(cmd+2); // enable the interrupts. I think this is not needed // since it'll immediately be done by the loop, below. sei(); // Now loop processing commands that come in over the // dongle. This is how the EEPROMs are programmed. while (1) { // Enable interrupts again sei(); cmd = tx_computer_byte(0); // This break is the only way out of // this loop. If it executes (probably because of // a timeout in tx_computer_byte()), then there is // no way back other than resetting the chip by // a long button-push. if (cmd == 0) break; else // Disable interrupts during command processing. cli(); // Sensor_timer does double-duty here, as a timeout counter for // the communications link. But this is OK since we never do // comm and display at the same time... // POSSIBLE BUG: it isn't cleared before the original cmd // byte read, and since interrupts are off, it won't be // incremented by the interrupt routine! sensor_timer = 0; // overloaded to reset communications timeout switch (cmd) { // Read either a single byte, or 16 bytes, from either // the external or internal EEPROM. If the high bit of // the address is 1, then it's an internal read. case COMP_CMD_RDEEPROM: case COMP_CMD_RDEEPROM16: if (cmd == COMP_CMD_RDEEPROM16) n = 16; else n = 1; // read 2 bytes to get the address addr = tx_computer_byte(0); addr <<= 8; addr |= tx_computer_byte(0); // internal or external? if ((addr & 0x8000) != 0) { // there are only 256 bytes of internal eeprom tx_computer_byte(internal_eeprom_read(addr & 0xFF)); } else { // read and transfer 1 or 16 bytes spieeprom_read(addr, buff, n); for (i=0; i<n; i++) { tx_computer_byte(buff[i]); } } // Tell the PC we're copacetic tx_computer_byte(COMP_SUCCESS); break; // Write either a single byte or a block of 16. Same // idea as above... case COMP_CMD_WREEPROM: case COMP_CMD_WREEPROM16: if (cmd == COMP_CMD_WREEPROM16) n = 16; else n = 1; addr = tx_computer_byte(0); addr <<= 8; addr |= tx_computer_byte(0); // Give a visual indication that we're loading data set_led((addr/16)%32); // Receive the bytes for (i=0; i<n; i++) { buff[i] = tx_computer_byte(0); } // Tell the PC we got the data... tx_computer_byte(COMP_SUCCESS); // ...and actually do the write if ((addr & 0x8000) != 0) { internal_eeprom_write(addr & 0xFF, buff[0]); } else { spieeprom_write(addr, buff, n); } break; } } // 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. while (1) { // 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 == 0xFFFF) { // Avoid pesky interruptions cli(); // Turn off all LEDs - I guess LED 0 is one of the "invisible ones" set_led(0); // 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;}// Talk to the PC. First a byte is sent, then a byte// is received. This is a simple form of handshaking.//// The assembly wdr instruction resets the watchdog// timer so it won't cause everything to abort.//// POSSIBLE BUG: this function quits when sensor_timer// maxes out. But sensor_timer is incremented in an// interrupt routine, and AFAICT interrupts are turned// off in the communications loop (except for the// cmd read, which is possibly the only time it's// needed). But in addition, it isn't cleared before// that read, so the timeout may occur a lot faster// that expected.//// POSSIBLE BUG: if it does time out, then the returned// value v is undefined.//// I won't worry at this point about figuring out how// this works!uint8_t tx_computer_byte(uint8_t b) { uint8_t v; DDRA = 0x0; // outputs DDRB = 0x5F; // input on MOSI/DI (for SPI), all others output USICR = _BV(USIWM0) | _BV(USICS1); USIDR = b; // transfer 0x0 USISR = _BV(USIOIF); // ready while (! (USISR & _BV(USIOIF)) ) { asm("wdr"); if (sensor_timer == 0xFFFF) { stopcomputertx = 1; } if (stopcomputertx) { break; } } v = USIDR; DDRB = 0xDF; USICR = 0; return v;}// Move data over the serial link (to the 1K/4K EEPROM or the// LED shift registers, or both!) This is black magic to me!uint8_t spi_transfer(uint8_t c) { USIDR = c; USISR = _BV(USIOIF); while (! (USISR & _BV(USIOIF))) { USICR = _BV(USIWM0) | _BV(USICS1) | _BV(USICLK) | _BV(USITC); //NOP; // slow down so that the eeprom isnt overwhelmed } return USIDR;}// Read the internal EEPROMuint8_t internal_eeprom_read(uint8_t addr) { loop_until_bit_is_clear(EECR, EEWE); // Wait for last write to finish EEAR = addr; // Store address to read EECR |= _BV(EERE); // Trigger the EEPROM read - this takes only one // cycle, so the result can immediately be returned! return EEDR; }// Write the internal EEPROMvoid internal_eeprom_write(uint8_t addr, uint8_t data) { loop_until_bit_is_clear(EECR, EEWE); // Wait for last write to finish EEAR = addr; // Store address to write EEDR = data; // and the data // The following register bitflips are time-critical and must // happen within 4 cycles of each other. For this reason, // interrupts cannot be allowed here. // Note: Page 19 refers to EEWE as EEPE and EEMWE as EEMPE. // POSSIBLE BUG: EEPM{0,1} bits in EECR define what programming // mode the EEPROM is in. They do not have a defined initial // value if the EEPROM is programming when a reset comes in. // This is a tiny time window, but should they be explicitly // cleared? cli(); // Turn off interrupts EECR |= _BV(EEMWE); // Master enable, unlocks ability to write to EEPROM EECR |= _BV(EEWE); // Program enable, actually does it. sei(); // Turn on interrupts again}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?