#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <inttypes.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
/**************************************\
* Laser projector.
* 32x16 display. 16x16 active pixels.
* Use 8MHz clock.
*
* Ports:
* B0: out: Laser
* B1: out: Motor
* B3: in: Fork reader (active low) (B3=INT2)
* D7: out: Servo control (D7=OC2)
\*************************************/
//#include "video/x.h"
//#include "test_pattern.h"
#include "video_alf.h"
/* Calibration */
const char offsetx[16] = {4,10,13,13, 12,12,14,11, 8,8,9,9, 13,9,9,3};
const uint8_t suboffsetx[16] = {8,9,8,15, 6,3,5,2, 1,9,7,5, 13,6,8,8};
/* Variables */
const uint8_t frame_div = 4; /* Number of times to show each frame */
volatile uint8_t frame_div_cnt; /* Counter for frame slowdown */
volatile uint16_t pixel_pos; /* Rotor position in pixels (0 to 511) */
volatile uint16_t rot_ticks; /* Number of counter1 ticks per rotation */
volatile uint16_t frame; /* Current video frame */
volatile uint16_t frame_inc; /* Frames forward per rotation */
volatile uint8_t the_end; /* Has the movie reached the end? */
volatile uint8_t pixelclock; /* Pixel clock (counter0 ticks per pixel) */
volatile uint8_t pixelclock_frac; /* 64*(pixelclock fraction) */
volatile uint8_t pixelclock_frac_running; /* Incremented by pixelclock_frac once per pixel */
void ioinit(void)
{
/* Set port direction (0=in, 1=out) */
DDRA = 0x00;
DDRB = 0x03;
DDRC = 0x00;
DDRD = 0x80;
/* Set internal pull-up (0=off, 1=on) */
PORTA = 0xff;
PORTB = 0xf8;
PORTC = 0xff;
PORTD = 0x7f;
/* 8-bit counter (counter0) - Pixel clock */
#define TCCR0_DIV_OFF ( 0)
#define TCCR0_DIV_1 ( BV(CS00))
#define TCCR0_DIV_8 ( BV(CS01) )
#define TCCR0_DIV_64 ( BV(CS01)|BV(CS00))
#define TCCR0_DIV_256 (BV(CS02) )
#define TCCR0_DIV_1024 (BV(CS02) |BV(CS00))
outp(BV(WGM01)|TCCR0_DIV_8, TCCR0); /* Reset counter on compare, set clock div */
/* 8-bit counter (counter2) - PWM generator */
outp(BV(WGM21)|BV(WGM20)|BV(COM21)|7, TCCR2);
OCR2=10; /* Range: 3-18 */
/* 16-bit counter (counter1) - Used to measure rotation time */
#define TCCR1B_DIV_OFF ( 0)
#define TCCR1B_DIV_1 ( BV(CS10))
#define TCCR1B_DIV_8 ( BV(CS11) )
#define TCCR1B_DIV_64 ( BV(CS11)|BV(CS10))
#define TCCR1B_DIV_256 (BV(CS12) )
#define TCCR1B_DIV_1024 (BV(CS12) |BV(CS10))
outp(0, TCCR1A);
outp(0, TCCR1B);
/* External interrupt INT2 - "Reading fork", triggers once per revolution */
cbi(MCUCSR, ISC2); /* Trigger on falling edge */
sbi(GICR, INT2); /* Enable */
/* Enable interrupts */
sei();
}
/* 8-bit counter (counter0) - Pixel clock */
SIGNAL(SIG_OUTPUT_COMPARE0)
{
int8_t x,y,i;
pixel_pos++;
x = pixel_pos & 0x1f;
y = pixel_pos >> 5;
/* Sub-x-offset */
i = 0;
if (x == 0)
i = (pixelclock * (uint16_t)suboffsetx[y]) >> 4;
if (x == 30)
{
pixel_pos++;
i = pixelclock - ((pixelclock * (uint16_t)suboffsetx[y]) >> 4);
}
/* Pixel clock fractions / Sub-x-offset */
pixelclock_frac_running += pixelclock_frac;
if (pixelclock_frac_running >= 0x40)
{
pixelclock_frac_running -= 0x40;
OCR0 = pixelclock + i + 1;
}
else
OCR0 = pixelclock + i;
/* Output pixel */
x = x - offsetx[(int)y];
if ((x >= 0) && (x < 16))
{
if (pgm_read_byte_near(video + (frame<<5) + (y<<1) + (x>>3)) & BV(7-(x & 0x07)))
sbi(PORTB,0);
else
cbi(PORTB,0);
}
else
cbi(PORTB,0);
/* Turn off counter0 (this one), so it won't disturb INT2 interrupt */
if (pixel_pos > 498)
{
timer_enable_int(0);
cbi(PORTB,0);
}
}
/* External interrupt INT2 */
SIGNAL(SIG_INTERRUPT2)
{
const uint16_t hysteresis = 4;
static uint16_t i;
pixel_pos = 0;
/* Read rotation time from 16-bit counter */
outp(TCCR1B_DIV_OFF, TCCR1B); /* Stop counting */
if (TCNT1 > rot_ticks + hysteresis) rot_ticks = TCNT1 - hysteresis;
if (TCNT1 < rot_ticks - hysteresis) rot_ticks = TCNT1 + hysteresis;
i = rot_ticks >> 6; /* rot_ticks * 64 / 512 / 8 */
if (i > 126) i = 126;
pixelclock = i; /* Typical: 61 */
OCR0 = pixelclock;
pixelclock_frac = rot_ticks & 0x3f;
pixelclock_frac_running = 0;
TCNT1 = 0;
outp(TCCR1B_DIV_64, TCCR1B); /* Start counting again */
/* Re-enable 8-bit counter */
timer_enable_int(BV(OCIE0)); /* Output Compare Interrupt Enable */
TCNT0 = 0; /* Reset counter */
TIFR = 0xff; /* Clear pending counter interrupts. FIXME: Clears all interrupts! */
/* Video */
if (++frame_div_cnt >= frame_div)
{
frame_div_cnt = 0;
frame += frame_inc;
if (frame >= video_frames)
{
frame = video_frames-1;
the_end = 1;
}
}
}
int main(void)
{
uint8_t i;
uint32_t j;
frame = 0;
frame_inc = 0;
ioinit();
/* Startup */
OCR2 = 3; /* Raise screen */
sbi(PORTB, 1); /* Turn on motor */
/* Play video */
for (j=0; j < 32000000; j++);
for (i=0; i<2; i++)
{
frame = 0;
frame_div_cnt = 0;
the_end = 0;
for (j=0; j < 32000000; j++);
frame_inc = 1;
while (!the_end);
frame_inc = 0;
for (j=0; j < 32000000; j++);
}
/* Shutdown */
cli(); /* Disable interrupts */
cbi(PORTB, 0); /* Turn off laser */
cbi(PORTB, 1); /* Turn off motor */
for (j=0; j < 128000000; j++);
OCR2 = 10; /* Lower screen */
for(;;);
return 0;
}