⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 main.c

📁 旋转16个LED灯控制程序
💻 C
📖 第 1 页 / 共 2 页
字号:
/******************************************SpokePOV V1.0 firmwareSpokePOV firmware is distributed under CC license. For more info on CC go to www.creativecommons.orgFor more info on SpokePOV go to www.ladyada.net/make/spokepovCreative Commons DeedAttribution-NonCommercial-ShareAlike 2.5You are free:    * to copy, distribute, display, and perform the work    * to make derivative worksUnder the following conditions:Attribution.   You must attribute the work in the manner specified by the    author or licensor.Noncommercial.    You may not use this work for commercial purposes.Share Alike.    If you alter, transform, or build upon this work, you may    distribute the resulting work only under a license identical to this one.    * For any reuse or distribution, you must make clear to others       the license terms of this work.    * Any of these conditions can be waived if you get permission       from the copyright holder.Your fair use and other rights are in no way affected by the above.A more detailed version of this license is available at:http://creativecommons.org/licenses/by-nc-sa/2.5/legalcode******************************************//* ANNOTATED by RJW - trebor@animeigo.com - to further my understanding		*//* of the code and environment before proceeding to make mods.  All page	*//* numbers refer to the ATMEL ATTiny2313 documentation located at			*//* http://www.atmel.com/dyn/resources/prod_documents/doc2543.pdf			*//* Any comments implying any ignorance were made by RJW!  The esteemed		*//* original author is by definition omniscient, and, it is feared,			*//* omnipotent as well...													*/#include <avr/io.h>#include <avr/interrupt.h>//#include <avr/signal.h>#include "main.h"#include "eeprom.h"// QUESTION: What clock speed is the ATMEL set to run at in the SpokePOV kits?// QUESTION: How does one change that?// If defined, ANIMATE will cause the SpokePOV firmware// to sequence through the 1K buffers in the eeprom, if// a >1K eeprom is actually installed.#define ANIMATE 1uint8_t animation_time = 6;					// change image every animation_time rotationsvolatile uint16_t anim_timer = 0;			// current rotation countvolatile uint16_t anim_eeprom_offset = 0;	// offset into EEPROM to display// How many ~3ms delays (-1) must elapse before we consider// a subsequent hall-effect sensor interrupt to be valid?#define HALL_DEBOUNCE_THRESH 4				// ~15ms// How many msecs the button must be held down before it is// considered to be an actual button press.#define BUTTON_DEBOUNCE  100// How many pixels in a full rotation of the SpokePOV#define NUM_PIXELS 256     					// max is 255 (since it is 8 bit value)// How long after the SpokePOV stops rotating will the display continue?#define STANDBY_TIMEOUT 5       			// in seconds// How long until the SpokePOV goes into sleep mode.// NOTE: not actually used, it's hard-coded instead!#define POWEROFF_TIMEOUT 2*60   			// in seconds// Internal EEPROM storage locations#define EEPROM_ROTATION_OFFSET 0x00			// rotation offset to compensate for magnet position#define EEPROM_MIRROR 0x01					// flag telling whether oppposite side LEDs are mirrored#define EEPROM_ANIMATION 0x02				// animation activation flaguint8_t mirror;								// RAM copy of the mirror flaguint8_t fleds[4], bleds[4];					// pixel arrays for the front and back LEDsvolatile uint8_t hall_debounce;				// count (via TIMER0) since last *used* Hall Effect detectionvolatile uint16_t sensor_timer;				// count (via TIMER0) since last actual Hall Effect detectionvolatile uint8_t stopcomputertx = 0;		// flag used to terminate talking to the PC.// TIMER0 interrupt handler.  This runs about every 3ms// AFAICT.  It increments the hall_debounce and sensor_timer// values until they pin.// QUESTION: what's with the setting and clearing of PORTB0?// According to the wiring diagram, it isn't connected to// anything.  Is this vestigial code from when you were using// Pin Change Interrupts?  Or is it debugger code so you// can monitor the pins and tell when something happens.SIGNAL (SIG_TIMER0_OVF) {  PORTB |= 0x1;  if (hall_debounce != 0xFF)    hall_debounce++;    if (sensor_timer != 0xFFFF)    sensor_timer++;  PORTB &= ~0x1;}// The current EEPROM address gets incremented by 4 (the number of// bytes in a pixel line) each time the pixel timer fires.  This// steps us through the bitmap we need to display.volatile uint16_t curr_eeprom_addr = 0;// This routine gets called every time the pixel timer runs down;// in other words, once per "scan line", 256 times per revolution// of the SpokePOV.  Its purpose is to update the LEDs.SIGNAL (SIG_TIMER1_COMPA) {  uint16_t eepromaddr;				// local copy of the eprom address   // When an interrupt routine is called, interrupts are disabled.  // but it's important to let other interrupts interrupt us, so  // they need to be re-enabled.	  sei();  PORTB |= 0x2;  // We need a local copy of the EEPROM address because we  // muck with it a bit.    eepromaddr = curr_eeprom_addr;  // If it has been less than STANDBY_TIMEOUT seconds since the last time we  // got a Hall Effect sensor update, then proceed as normal and  // update the LEDs    // QUESTION: what is F_CPU?    if (sensor_timer < ((F_CPU/NUM_PIXELS)/256 * STANDBY_TIMEOUT)) {            PORTA |= 0x1;        // Since cur_eeprom_addr can go past a 1K boundary depending on    // the rotation offset, we need to modulo it to a 0-1023 value.        eepromaddr %= NUM_PIXELS * 4;        // Then the bytes we want to plonk into the LEDs will be this    // address, plus the animation offset (0,1024,2048 or 3072).        // This routine is in eeprom.c.  As far as I can tell, it's doing    // a really cute trick, causing the EEPROM to send the desired    // pixel bytes to both the ATMEL and the pixel shift registers    // at the same time.        spieeprom_read_into_leds(eepromaddr + anim_eeprom_offset, FRONT);        // If we are mirroring the image onto the backside LEDs, then we    // have to output the lines of pixels in reverse order        if (mirror) {      spieeprom_read_into_leds(anim_eeprom_offset + (1024UL-eepromaddr), BACK);    } else {	  // Otherwise, currently, turn off the backside LEDs	        LATCH_SELECT_PORT |= _BV(BACK);      NOP; NOP; NOP; NOP;      LATCH_SELECT_PORT &= ~_BV(BACK);        }          // Now we increment the curr_eeprom_addr variable.  However, we    // only do it IF it hasn't already 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 it while    // we are updating it.        cli();        if (eepromaddr == (curr_eeprom_addr%(NUM_PIXELS*4))) {      curr_eeprom_addr = eepromaddr+4;            }    sei();        PORTA &= ~0x1;      } 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?        cli();    TCCR1B &= ~0x7;    sei();    // Turn off all but one LED        set_led(2, FRONT);    set_led(2, BACK);    PORTA &= ~0x2;  }  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) {    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) {    stopcomputertx = 1;#ifdef ANIMATE  // increment the animation timer, and move  // to the next image every animation_time rotations    if (anim_timer != animation_time) {    anim_timer++;  } else {    anim_timer = 0;    anim_eeprom_offset += 1024;  }#endif    // 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            OCR1A = (sensor_timer << 8) | TCNT0;            // Clear the residual of TIMER0            TCNT0 = 0;            // Set the initial address to display in the EEPROM, adjusting      // for the rotation offset.            curr_eeprom_addr = 		(internal_eeprom_read(EEPROM_ROTATION_OFFSET) % NUM_PIXELS) * 4;			  // Read the mirror flag from EEPROM	  	  mirror = internal_eeprom_read(EEPROM_MIRROR);      // QUESTION: why are these read every rotation vs. being      // stored in ram?            // 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_led(2, FRONT);      set_led(2, BACK);            TCCR1B &= ~_BV(CS10);		// no incrementing = no interrupting    }           // Whether we're displaying or not, we reset sensor_timer so we can    // time the next revolution.        sensor_timer = 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.  

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -