A counter program

MiniPOV4 and previous versions

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
pneumatic
 
Posts: 186
Joined: Sun Jul 26, 2009 3:59 pm

A counter program

Post by pneumatic »

I wanted to see if I could make my MiniPOV3 more interactive, so I implemented a simple counter program below. I requires that you use a sensor to trigger each sweep, which I outlined in another post.

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);
}


sparr
 
Posts: 196
Joined: Tue Nov 04, 2008 5:21 pm

Re: A counter program

Post by sparr »

Search for 'PROGMEM' for a way to keep the character array in program space instead of on the stack. I put an 8x5 64-character font in PROGMEM with a thousand bytes of space to spare for a message.

http://forums.adafruit.com/viewtopic.php?f=11&t=8030

Locked
Please be positive and constructive with your questions and comments.

Return to “MiniPOV”