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 + -
显示快捷键?