📄 spokepov.lss
字号:
// 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 character and pixel numbers so they will overflow
// on the next pixel interrupt, and cause the correct data to
// be loaded.
charNum = 15; // will wrap to 0, the first char
pixelNum = 15; // will wrap to 0, the first pixel
clean = 0; // flag that we changed things
// 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);
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.
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;
ae: 20 ed ldi r18, 0xD0 ; 208
b0: 37 e0 ldi r19, 0x07 ; 7
000000b2 <L_dl140>:
unsigned short cnt;
asm volatile ("\n"
b2: e2 2f mov r30, r18
b4: f3 2f mov r31, r19
000000b6 <L_dl240>:
b6: 31 97 sbiw r30, 0x01 ; 1
b8: f1 f7 brne .-4 ; 0xb6 <L_dl240>
ba: a8 95 wdr
bc: 8a 95 dec r24
be: c9 f7 brne .-14 ; 0xb2 <L_dl140>
c0: 08 95 ret
000000c2 <__vector_1>:
c2: 1f 92 push r1
c4: 0f 92 push r0
c6: 0f b6 in r0, 0x3f ; 63
c8: 0f 92 push r0
ca: 11 24 eor r1, r1
cc: 2f 93 push r18
ce: 3f 93 push r19
d0: 4f 93 push r20
d2: 5f 93 push r21
d4: 6f 93 push r22
d6: 7f 93 push r23
d8: 8f 93 push r24
da: 9f 93 push r25
dc: af 93 push r26
de: bf 93 push r27
e0: cf 93 push r28
e2: df 93 push r29
e4: ef 93 push r30
e6: ff 93 push r31
e8: c0 e0 ldi r28, 0x00 ; 0
ea: d0 e0 ldi r29, 0x00 ; 0
ec: 82 99 sbic 0x10, 2 ; 16
ee: 05 c0 rjmp .+10 ; 0xfa <__stack+0x1b>
f0: 21 96 adiw r28, 0x01 ; 1
f2: 81 e0 ldi r24, 0x01 ; 1
f4: dc df rcall .-72 ; 0xae <delay_ms>
f6: 82 9b sbis 0x10, 2 ; 16
f8: fb cf rjmp .-10 ; 0xf0 <__stack+0x11>
fa: c5 36 cpi r28, 0x65 ; 101
fc: d1 05 cpc r29, r1
fe: 60 f0 brcs .+24 ; 0x118 <__stack+0x39>
100: c4 5f subi r28, 0xF4 ; 244
102: d1 40 sbci r29, 0x01 ; 1
104: 18 f4 brcc .+6 ; 0x10c <__stack+0x2d>
106: 88 e0 ldi r24, 0x08 ; 8
108: 81 bd out 0x21, r24 ; 33
10a: ff cf rjmp .-2 ; 0x10a <__stack+0x2b>
10c: 8f ef ldi r24, 0xFF ; 255
10e: 9f ef ldi r25, 0xFF ; 255
110: 90 93 8c 00 sts 0x008C, r25
114: 80 93 8b 00 sts 0x008B, r24
118: ff 91 pop r31
11a: ef 91 pop r30
11c: df 91 pop r29
11e: cf 91 pop r28
120: bf 91 pop r27
122: af 91 pop r26
124: 9f 91 pop r25
126: 8f 91 pop r24
128: 7f 91 pop r23
12a: 6f 91 pop r22
12c: 5f 91 pop r21
12e: 4f 91 pop r20
130: 3f 91 pop r19
132: 2f 91 pop r18
134: 0f 90 pop r0
136: 0f be out 0x3f, r0 ; 63
138: 0f 90 pop r0
13a: 1f 90 pop r1
13c: 18 95 reti
0000013e <ioinit>:
13e: 83 e7 ldi r24, 0x73 ; 115
140: 81 bb out 0x11, r24 ; 17
142: 8f ed ldi r24, 0xDF ; 223
144: 87 bb out 0x17, r24 ; 23
146: 80 e1 ldi r24, 0x10 ; 16
148: 88 bb out 0x18, r24 ; 24
14a: 8c e4 ldi r24, 0x4C ; 76
14c: 82 bb out 0x12, r24 ; 18
14e: 98 e0 ldi r25, 0x08 ; 8
150: 95 bf out 0x35, r25 ; 53
152: 80 ec ldi r24, 0xC0 ; 192
154: 8b bf out 0x3b, r24 ; 59
156: 10 be out 0x30, r1 ; 48
158: 84 e0 ldi r24, 0x04 ; 4
15a: 83 bf out 0x33, r24 ; 51
15c: 89 b7 in r24, 0x39 ; 57
15e: 82 60 ori r24, 0x02 ; 2
160: 89 bf out 0x39, r24 ; 57
162: 1f bc out 0x2f, r1 ; 47
164: 9e bd out 0x2e, r25 ; 46
166: 10 92 8a 00 sts 0x008A, r1
16a: 10 92 8c 00 sts 0x008C, r1
16e: 10 92 8b 00 sts 0x008B, r1
172: 08 95 ret
00000174 <spi_transfer>:
"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 only
void 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 LEDs
void 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 display
void 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 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();
// 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();
// 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!
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -