📄 main.c
字号:
/******************************************CharPOV V1.02 firmwareCharPOV 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******************************************//* Modified from SpokePOV 1.01 by RJW to display messages from a character *//* set instead of simple bitmaps. The character set (ascii 32-127) is *//* stored in the first 3 banks of the external EEPROM *//* *//* Currently does not use the rotation offset or backside leds *//* 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>/* We now store the message to be scrolled out on the SpokePOV in the flash *//* memory. This requires using the PROGMEM routines. For more info about *//* them, see: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=38003 */#include <avr/pgmspace.h>#include "main.h"#include "eeprom.h"/* The number of lines in our message *//* The maximum we can display right now is 15 to prevent 8bit overflow */#define NUM_LINES 16/* The text of the message. Since this is stored in FLASH, it requires *//* a special method of accessing it. */const char lines[] PROGMEM = " Episode IV " " A New Hope " " " " It is a " " period of " " civil war. " " " " Rebel " " spaceships, " " striking " " from a " " hidden base " " have won " " their first " " victory.. " " " ;/* The following two 16-character arrays define the message *//* the SpokePOV is currently displaying. They get updated from lines */char topLine[16];char botLine[16];// QUESTION: What clock speed is the ATMEL set to run at in the SpokePOV kits?// QUESTION: How does one change that?// 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 // not used in this app (yet)#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 fleds[4]; // pixel array for the front LEDs onlyvolatile 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 line_timer_h = 0x80; // counters used to step lines of text throughvolatile uint8_t line_timer_l = 0x00; // the display; split into two bytes for conveniencevolatile uint8_t cur_line = 0xff; // the current 'top line'// TIMER0 interrupt handler. This runs about every 8ms// 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++; // increment the line timers line_timer_l++; // increment the low byte if (line_timer_l == 0) { // if we wrapped around, then line_timer_h++; // increment the high byte as well } // *** PORTB &= ~0x1;}// As we sweep around the circle, we display 256 radial pixel// lines, once per TIMER1 interrupt. This is broken down into// 16 16-pixel wide characters, and we have two characters// stacked vertically. To save time, we keep track of the// character number, pixel number (in the character), and// pointers into the eeprom for each of the two chars being// displayed.volatile uint16_t topChar = 0; // top character being displayed (address in EEPROM of data)volatile uint16_t botChar = 0; // bottom character being displayedvolatile uint8_t charNum = 0; // character numbervolatile uint8_t pixelNum = 0; // pixel numbervolatile uint8_t clean = 0; // have these values been changed outside TIMER1?// 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 tChar; // local copies of the values uint16_t bChar; uint8_t cNum; uint8_t pNum; uint8_t cCode; // character code to display // 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; // Copy the volatile variables into their local equivalents tChar = topChar; bChar = botChar; cNum = charNum; pNum = pixelNum; // 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; // The first thing we do is increment our character position; this // is done here to avoid code duplication. This means that the // Hall Effect interrupt routine must set them up so they "wrap" // into the first valid values. // Move to the next pixel in the character pNum++; // If we have moved off the edge of the character, then // we need to move to the next character if (pNum == 16) { pNum = 0; // reset to first pixel cNum = (cNum+1) % 16; // 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 ); // tChar = 1024UL; // debug, should be an @ // Ditto for bChar... cCode = botLine[cNum]-32; bChar = ((cCode >> 1) << 6) | ( (cCode & 0x01) << 1 ); // bChar = tChar + 2; // debug, should display A } 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; } // 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,2); // the top 2 bytes spieeprom_read(bChar,fleds,2); // and the bottom 2 clock_leds(); // and send them to the LEDs // 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 cli(); if (clean) { topChar = tChar; botChar = bChar; charNum = cNum; pixelNum = pNum; } 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(); // *** 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); // *** 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) { uint8_t cLine; // *** 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.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -