Tuesday, March 31, 2015

Repurposing the Crazy Clock as a phonograph strobe

So, I don't know what made me sit down this evening and do this, but it occurred to me that the n/n+1 fractional arithmetic timer stuff I had done for the Crazy Clock could be put to another purpose - generating 60 Hz for a phonograph strobe.

The Crazy Clock hardware is pretty well suited to the job - any 1-3 volt power source can be turned into 3.3 volts by the built-in boost converter, and I use LEDs in the test harness, so I know it can successfully drive them.

Getting 120 Hz (since we want the light to blink at 60 Hz, we want to toggle it at 120 Hz) from the 32.768 kHz crystal is simply another exercise in the n/n+1 fractional division machinery that already drives the Crazy Clock firmware. And by toggling them both out of phase, we can ignore the polarity of the LED when we hook it up.

Setting the timer prescaler to 8 yields 4.096 kHz. Divide that by 120 and you get 34 + 2/15. 34 * 13 + 35 * 2 = 512, and 512 divides into 32768 evenly. Some of the light pulses will be 244 µS longer than others, but the 60 Hz average should still be within 10 ppm otherwise, which is the tolerance of the crystal.

Here's a 240 fps slow motion video of the strobe:


/*

 Phonograph strobe generator for Arduino
 Copyright 2015 Nicholas W. Sayer

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along
 with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * This is intended to run on an ATTiny45. Connect a 32.768 kHz crystal and fuse it
 * for the low frequency crystal oscillator, no watchdog or brown-out detector.
 *
 * Connect PB0 and PB1 to an LED. Either orientation will work. The two pins will
 * alternate polarity. When this firmware is loaded into a crazy clock, it will
 * just work - the flyback diodes will serve no purpose and the two series resistors
 * wil be correct for an LED.
 *
 */

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>

// 32,768 divided by (8 * 120) yields a divisor of 34 2/15
#define CLOCK_CYCLES (15)
// Don't forget to decrement the OCR0A value - it's 0 based and inclusive
#define CLOCK_BASIC_CYCLE (34 - 1)
// a "long" cycle is CLOCK_BASIC_CYCLE + 1
#define CLOCK_NUM_LONG_CYCLES (2)

// LED pins. So that we don't have to remember which is which, we'll always make one
// the opposite of the other.
#define P0 0
#define P1 1
#define P_UNUSED 2

ISR(TIMER0_COMPA_vect) {
  // Do nothing - just wake up
}

void main() {
  ADCSRA = 0; // DIE, ADC!!! DIE!!!
  ACSR = _BV(ACD); // Turn off analog comparator - but was it ever on anyway?
  power_adc_disable();
  power_usi_disable();
  power_timer1_disable();
  TCCR0A = _BV(WGM01); // mode 2 - CTC
  TCCR0B = _BV(CS01); // prescale = 8
  TIMSK = _BV(OCIE0A); // OCR0A interrupt only.
  
  set_sleep_mode(SLEEP_MODE_IDLE);

  DDRB = _BV(P0) | _BV(P1) | _BV(P_UNUSED); // all our pins are output.
  PORTB = 0; // Initialize all pins low.

  // Don't forget to turn the interrupts on.
  sei();

  unsigned char lastTick = P0;
  unsigned char cycle_pos = 0xfe;
  while(1) {

// This will alternate the ticks
#define TICK_PIN (lastTick == P0?P1:P0)

    // Toggle the two pins back and forth.
    PORTB |= _BV(TICK_PIN);
    lastTick = TICK_PIN;
    PORTB &= ~ _BV(TICK_PIN);

    if (++cycle_pos == CLOCK_NUM_LONG_CYCLES)
      OCR0A = CLOCK_BASIC_CYCLE;
    if (cycle_pos >= CLOCK_CYCLES) {
      OCR0A = CLOCK_BASIC_CYCLE + 1;
      cycle_pos = 0;
    }
    sleep_mode();
  }

}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.