Some notes:
- I appear to be running up against the memory limits of the ATTiny2313. When I had a set of numbers that were 6 bits wide, the 9 would get corrupted (probably a stack/heap collision), and if I added much to code, it wouldn't run at all. It might be possible to trade data (stack/heap) space for text (code) space by making each number a hard-coded routine, but I didn't want to do that.
- I used a tilt-ball to trigger it as I sweep my hand back and forth, but a reed switch passing a magnet and a rotating arm should work just as well. Play with the delay the code.
- The numbers are coded in BCD, since doing standard itoa() was causing the stack to expand too much and run into the heap.
- Currently I have counter as a uint16_t, which gives a 4 digit number. If you use a uint8_t or a uint32_t, then you can have 2 or 8 digit numbers, respectively.
- I'm not terribly proud of this code, since it's the worst kind of spaghetti code, but it does the job and fits in memory.
Code: Select all
/* Trigger a increasing counter as the sensor line goes low
* Written by Mitch Patenaude ([email protected])
*
* Derived from the code by Lady Ada as part of the MiniPOV3 kit
*
* Released under the GPL
*/
#include <avr/io.h> // this contains all the IO port definitions
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <util/delay.h>
#define ENABLE_SENSOR
// #undef ENABLE_SENSOR
// The trigger must be on for this many display cycles before the message
// starts displaying. This is to prevent transients from triggering it.
#define TRIGGER_DELAY 10
// Number of cycles after trigger to wait before displaying the message
// (to give time for the hand to accelerate up to a constant speed)
#define DISPLAY_DELAY 50
#define TIMER1_PRESCALE_1 1
#define TIMER1_PRESCALE_8 2
#define TIMER1_PRESCALE_64 3
#define TIMER1_PRESCALE_256 4
#define TIMER1_PRESCALE_1024 5
// We use these macros because binary constants arent always supported. ugh.
#define HEX__(n) 0x##n##UL
#define B8__(x) ((x&0x0000000FLU)?1:0) \
+((x&0x000000F0LU)?2:0) \
+((x&0x00000F00LU)?4:0) \
+((x&0x0000F000LU)?8:0) \
+((x&0x000F0000LU)?16:0) \
+((x&0x00F00000LU)?32:0) \
+((x&0x0F000000LU)?64:0) \
+((x&0xF0000000LU)?128:0)
#define B8(d) ((unsigned char)B8__(HEX__(d)))
const static int image[] = {
// 0
B8(01111110),
B8(10000101),
B8(10011001),
B8(10100001),
B8(01111110),
// 1
B8(10000000),
B8(10000010),
B8(11111111),
B8(10000000),
B8(10000000),
// 2
B8(10000100),
B8(11000010),
B8(10100001),
B8(10010010),
B8(10001100),
// 3
B8(01000010),
B8(10000001),
B8(10001001),
B8(10001001),
B8(01110110),
// 4
B8(00011000),
B8(00010100),
B8(00010010),
B8(11111111),
B8(00010000),
// 5
B8(01000111),
B8(10001001),
B8(10001001),
B8(10001001),
B8(01110001),
// 6
B8(01111110),
B8(10001001),
B8(10001001),
B8(10001001),
B8(01110010),
// 7
B8(00000001),
B8(11000001),
B8(00110001),
B8(00001101),
B8(00000011),
// 8
B8(01110110),
B8(10001001),
B8(10001001),
B8(10001001),
B8(01110110),
// 9
B8(01001110),
B8(10010001),
B8(10010001),
B8(10010001),
B8(01111110),
};
#ifdef ENABLE_SENSOR
// Keep this in 8 bits and we need less shuffling when manipulating
# define CHARSIZE 5
# define TRIGGERED (!(PIND & 0x4))
uint8_t trigstate = 0;
uint16_t counter = 0;
# define MAX_PLACE (2*(sizeof(counter)/sizeof(uint8_t)))
uint8_t place = MAX_PLACE;
uint8_t digit = 0;
uint8_t nonzeroseen = 0;
#else
# define NUM_ELEM(x) (sizeof (x) / sizeof (*(x)))
int imagesize = NUM_ELEM(image);
#endif
// this function is called when timer1 compare matches OCR1A
uint8_t j = 0;
SIGNAL( SIG_TIMER1_COMPA ) {
#ifdef ENABLE_SENSOR
// States
// 0: wating to trigger
// 1: Waiting to display
// 2: running sweep
switch (trigstate) {
case 0:
if (!TRIGGERED) {
j=0;
break;
}
if (j < TRIGGER_DELAY) {
j++;
break;
}
trigstate=1;
j = 0;
// intentional fallthrough
case 1:
if ( j < DISPLAY_DELAY ) {
j++;
break;
}
// start with the highest digit.
place = MAX_PLACE;
trigstate++;
// hack... we want to calculate the first digit.
// since j is already large, we don't reset it.
// intentional fallthrough
case 2:
if (j < CHARSIZE) {
// Don't display leading zeros, so we suppress output until a non-zero
// digit is seen.
if (nonzeroseen) {
PORTB = image[j+digit*CHARSIZE];
}
j++;
break;
}
// Since we fell through, either we've just entered the display section
// or we've finished displaying a digit, so either way we need to load
// up the next digit.
j = 0;
// this turns off the display, also serves as a spacer between numbers
PORTB = 0;
// If we're not done displaying the digits, load the next one up.
if ( place > 0 ) {
place -= 1;
digit = (counter >> (4*place)) & 0xf;
if (digit != 0) {
nonzeroseen = 1;
}
break;
}
// Falling through here means all digiits have been displayed, and we
// need to reset for next pass
trigstate = 0;
nonzeroseen = 0;
// Really crude BCD increment implementation:
counter += 1;
place = 0;
while (place < MAX_PLACE) {
if (((counter >> (4U*place)) & 0xfU) == 0xaU) {
counter += (0x6U << (4U*place));
}
place += 1;
}
} // end switch(trigstate)
#else
if (j >= imagesize)
j = 0;
PORTB = image[j];
j++;
#endif
}
int main(void) {
DDRB = 0xFF; // set all 8 pins on port B to outputs
#ifdef ENABLE_SENSOR
DDRD = 0xFB; // set the sensor pin as input
PORTD = 0x04;
#endif
/*
the frequency of the interrupt overflow is determined by the
prescaler and overflow value.
freq = clock_frequency / ( 2 * prescaler * overflow_val)
where prescaler can be 1, 8, 64, 256, or 1024
clock_freq is 8MHz
and overflow_val is 16bit
the overflow value is placed in OCR1A, the prescale is set in TCCR1B
so for example:
A good POV frequency is around 400Hz
desired freq = 400Hz
clock freq = 8MHz
8MHz / (400Hz * 2) = 10000
since 10000 is less than 655536 (largest 16 bit number)
OCR1A = 10000 and the prescale is 1
*/
TCCR1B = (1 << WGM12) | TIMER1_PRESCALE_1;
OCR1A = (uint16_t)10000;
TIMSK |= 1 << OCIE1A; // Output Compare Interrupt Enable (timer 1, OCR1A)
sei(); // Set Enable Interrupts
while (1);
}