rendered paste body#include <avr/interrupt.h>
#include <avr/io.h>
#include <inttypes.h>
#include <string.h>
#include "atomic.h"
#include "timer0.h"
#include "midi.h"
#include "../../vst/vinylcontroller/src/midi_protocol.h"
#ifndef FALSE
# define FALSE 0
#endif
#ifndef TRUE
# define TRUE (!FALSE)
#endif
#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
# define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef hi8
# define hi8(x) ((uint8_t)((x) >> 8))
#endif
#ifndef lo8
# define lo8(x) ((uint8_t)(x))
#endif
#ifndef F_CPU
# error F_CPU must be defined to the clock frequency in Hz
#endif
#define MIDI_DEFAULT_CHANNEL 15
#define MIDI_BAUDRATE 31250ul
#define MIDI_UBRR ((F_CPU / (16ul * MIDI_BAUDRATE)) - 1ul)
#if (F_CPU / (16ul * (MIDI_UBRR + 1ul))) != MIDI_BAUDRATE
# error MIDI UBRR error!
#endif
#define TT_33RPM_INTERVAL (F_CPU * 60 * 3 / 100 / TIMER0_PRESCALE)
#define TT_45RPM_INTERVAL (F_CPU * 60 / 45 / TIMER0_PRESCALE)
#define TT_33RPM_TICK_INTERVAL (TT_33RPM_INTERVAL / 100) // 2250 @ 8MHz
#define TT_45RPM_TICK_INTERVAL (TT_45RPM_INTERVAL / 100)
#define HEAD_SERVO_PORT PORTD
#define HEAD_SERVO_PIN 7
#define HEAD_SERVO_DDR _SFR_IO8(_SFR_IO_ADDR(HEAD_SERVO_PORT) - 1)
#define HEAD_POSITION_RAISED 0
#define HEAD_POSITION_LOWERED 1
// FIXME FIXME FIXME check if stackpointer overruns
/******************** TYPES ********************/
typedef struct _MidiBuffer
{
uint8_t size;
uint8_t pos;
uint8_t data[4];
} MidiBuffer;
/******************** VARS ********************/
static struct
{
struct
{
uint8_t rpm;
} turntable;
struct
{
uint16_t speed; /* in µM/rev */
uint16_t leadInSpeed; /* in µM/rev */
uint16_t leadInDuration; /* 100 for one revolution */
uint16_t markSpeed; /* in µM/rev */
uint16_t markDuration; /* 100 for one revolution */
uint16_t leadOutSpeed; /* in µM/rev */
uint16_t leadOutDuration; /* 100 for one revolution */
} pitch;
struct
{
uint8_t position; /* HEAD_POSITION_XXX */
uint8_t raiseCurve[16];
uint8_t lowerCurve[16];
} head;
} m_params =
{
{ .rpm = 33 },
{ .speed = 0,
.leadInSpeed = 1000, .leadInDuration = 500,
.markSpeed = 500, .markDuration = 100,
.leadOutSpeed = 5000, .leadOutDuration = 100 },
{ .position = HEAD_POSITION_RAISED,
.raiseCurve = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
.lowerCurve = { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff } }
};
static struct
{
uint8_t channel;
MidiBuffer out;
MidiBuffer in;
uint8_t lastController;
uint16_t nrpn;
uint8_t dataMSB;
} m_midi =
{
MIDI_DEFAULT_CHANNEL,
{0, 0},
{0, 0},
0x00,
MIDI_NRPN_RESET,
0x00
};
static struct
{
uint32_t leadInEndTicks;
uint32_t markEndTicks;
uint32_t leadOutEndTicks;
} m_pitch =
{
0, 0
};
static struct
{
uint8_t servoPulseLength;
uint8_t servoCurve;
uint8_t servoCurvePos;
} m_head =
{
128,
0,
0
};
/******************** PROTOTYPES ********************/
void PITCH_LeadIn(void);
void PITCH_Mark(void);
void PITCH_LeadOut(void);
void HEAD_ChangePos(uint8_t newPos);
/******************** FUNCTIONS ********************/
/*** PARAMETERS ***/
void
PARAM_Init(void)
{
}
void
PARAM_Set(uint16_t nrpn, uint16_t value)
{
switch (nrpn)
{
case MIDI_NRPN_TT_RPM:
switch (value)
{
case 33:
TIMER0_Start(TT_33RPM_TICK_INTERVAL);
break;
case 45:
TIMER0_Start(TT_45RPM_TICK_INTERVAL);
break;
default:
value = 0;
TIMER0_Stop();
break;
}
m_params.turntable.rpm = value;
break;
case MIDI_NRPN_PITCH_SPEED:
m_params.pitch.speed = value;
break;
case MIDI_NRPN_PITCH_LEADIN_SPEED:
m_params.pitch.leadInSpeed = value;
break;
case MIDI_NRPN_PITCH_LEADIN_DURATION:
m_params.pitch.leadInDuration = value;
break;
case MIDI_NRPN_PITCH_MARK_SPEED:
m_params.pitch.markSpeed = value;
break;
case MIDI_NRPN_PITCH_MARK_DURATION:
m_params.pitch.markDuration = value;
break;
case MIDI_NRPN_PITCH_LEADOUT_SPEED:
m_params.pitch.leadOutSpeed = value;
break;
case MIDI_NRPN_PITCH_LEADOUT_DURATION:
m_params.pitch.leadOutDuration = value;
break;
case MIDI_NRPN_HEAD_POSITION:
// m_params.head.position = (value < 0x2000) ? HEAD_POSITION_RAISED : HEAD_POSITION_LOWERED;
HEAD_ChangePos((value < 0x2000) ? HEAD_POSITION_RAISED : HEAD_POSITION_LOWERED);
break;
case MIDI_NRPN_HEAD_SERVO_RAISE_CURVE:
m_params.head.raiseCurve[value >> 8] = lo8(value);
break;
case MIDI_NRPN_HEAD_SERVO_LOWER_CURVE:
m_params.head.lowerCurve[value >> 8] = lo8(value);
break;
}
}
#if 0
uint16_t
PARAM_Get(uint16_t nrpn)
{
switch (nrpn)
{
case MIDI_NRPN_TT_RPM:
return m_params.turntable.rpm;
break;
case MIDI_NRPN_PITCH_SPEED:
return m_params.pitch.speed;
break;
case MIDI_NRPN_PITCH_MARK_SPEED:
return m_params.pitch.markSpeed;
break;
case MIDI_NRPN_PITCH_MARK_DURATION:
return m_params.pitch.markDuration;
break;
case MIDI_NRPN_PITCH_LEADIN_SPEED:
return m_params.pitch.leadInSpeed;
break;
case MIDI_NRPN_PITCH_LEADIN_DURATION:
return m_params.pitch.leadInDuration;
break;
case MIDI_NRPN_HEAD_POSITION:
switch (m_params.head.position)
{
case HEAD_POSITION_RAISED:
return 0x0000;
break;
case HEAD_POSITION_LOWERED:
return 0x3fff;
break;
}
break;
}
return 0;
}
#endif
/*** MIDI ***/
void
MIDI_Init(void)
{
/* initialize USART */
UBRRH = hi8(MIDI_UBRR);
UBRRL = lo8(MIDI_UBRR);
UCSRA = 0x00;
UCSRB = _BV(RXCIE) | _BV(UDRIE) | _BV(RXEN) | _BV(TXEN);
UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0); /* asynchronous, no parity, 1 stop bit, 8 data bits */
}
void
MIDI_Send(const uint8_t *data, uint8_t size)
{
register uint8_t s;
while (size > 0)
{
while (m_midi.out.pos < m_midi.out.size);
s = MIN(size, sizeof (m_midi.out.data) + 1);
if (s > 1)
{
memcpy(m_midi.out.data, data + 1, s - 1);
m_midi.out.size = s - 1;
m_midi.out.pos = 0;
}
/* send first byte */
loop_until_bit_is_set(UCSRA, UDRE);
UDR = data[0];
if (s > 1)
UCSRB |= _BV(UDRIE); /* enable UDRE interrupt */
data += s;
size -= s;
}
}
ISR(USART_UDRE_vect)
{
PORTB ^= _BV(0);
if (m_midi.out.pos < m_midi.out.size)
UDR = m_midi.out.data[m_midi.out.pos++];
else /* disable UDRE interrupt */
UCSRB &= ~_BV(UDRIE);
}
void
MIDI_HandleData2(void)
{
register uint8_t tmp = SREG;
cli();
#if 0 /* This is done in MIDI_HandleData */
if (m_midi.in.pos <= 0) /* no data received so far */
{
SREG = tmp;
return;
}
#endif
if (m_midi.in.size <= 0) /* new message received, check status byte and set size of message */
{
register uint8_t data = m_midi.in.data[0];
switch ((data & MIDI_MESSAGE_MASK) >> MIDI_MESSAGE_SHIFT)
{
case MIDI_MESSAGE_NOTEON:
case MIDI_MESSAGE_CONTROLCHANGE:
if (((data & MIDI_CHANNEL_MASK) >> MIDI_CHANNEL_SHIFT) != m_midi.channel)
break;
m_midi.in.size = 3;
break;
}
if (m_midi.in.size <= 0) /* unknown message */
{
m_midi.in.pos = 0; /* ignore message */
SREG = tmp;
return;
}
}
if (m_midi.in.pos < m_midi.in.size) /* message not completely received */
{
SREG = tmp;
}
else
{
static uint8_t message[4];
/* copy message */
memcpy(message, m_midi.in.data, m_midi.in.size);
m_midi.in.pos = 0;
/* reenable interrupts */
SREG = tmp;
//MIDI_Send(message, m_midi.in.size);
/* handle message */
switch ((message[0] & MIDI_MESSAGE_MASK) >> MIDI_MESSAGE_SHIFT)
{
case MIDI_MESSAGE_NOTEON:
switch (message[1])
{
case MIDI_NOTE_PITCH_LEADIN:
PITCH_LeadIn();
break;
case MIDI_NOTE_PITCH_MARK:
PITCH_Mark();
break;
case MIDI_NOTE_PITCH_LEADOUT:
PITCH_LeadOut();
break;
}
break;
case MIDI_MESSAGE_CONTROLCHANGE:
switch (message[1])
{
case MIDI_CONTROLLER_NRPN_MSB:
//PORTB ^= _BV(2);
PORTB &= ~_BV(2);
m_midi.nrpn &= ~(0x007f << 7);
m_midi.nrpn |= message[2] << 7;
break;
case MIDI_CONTROLLER_NRPN_LSB:
if (m_midi.lastController != MIDI_CONTROLLER_NRPN_MSB)
{
m_midi.nrpn = MIDI_NRPN_RESET;
break;
}
//PORTB ^= _BV(2);
PORTB |= _BV(2);
m_midi.nrpn &= ~0x007f;
m_midi.nrpn |= message[2];
break;
case MIDI_CONTROLLER_DATA_MSB:
if (m_midi.lastController != MIDI_CONTROLLER_NRPN_LSB)
{
m_midi.nrpn = MIDI_NRPN_RESET;
break;
}
//PORTB ^= _BV(2);
PORTB &= ~_BV(2);
m_midi.dataMSB = message[2];
break;
case MIDI_CONTROLLER_DATA_LSB:
if (m_midi.lastController != MIDI_CONTROLLER_DATA_MSB)
{
m_midi.nrpn = MIDI_NRPN_RESET;
break;
}
//PORTB ^= _BV(2);
PORTB |= _BV(2);
PARAM_Set(m_midi.nrpn, (m_midi.dataMSB << 7) | message[2]);
break;
break;
}
m_midi.lastController = message[1];
break;
}
}
}
void __attribute__((naked))
MIDI_HandleData(void)
{
__asm__(
" lds __tmp_reg__, %0" "\n\t"
" cpse __tmp_reg__, __zero_reg__" "\n\t"
" rjmp MIDI_HandleData2" "\n\t"
" ret" "\n\t"
: /* no output */
: "m"(m_midi.in.pos)
);
}
ISR(USART_RX_vect)
{
register uint8_t data;
PORTB ^= _BV(0);
/* check for FE, DOR or PE and ignore the current message if any */
if (UCSRA & (_BV(FE) | _BV(DOR) | _BV(PE)))
{
m_midi.in.pos = 0;
data = UDR;
return;
}
data = UDR;
//UDR = data;
if (data & MIDI_STATUS_FLAG)
{
m_midi.in.size = 0;
m_midi.in.data[0] = data;
m_midi.in.pos = 1;
}
else if (m_midi.in.pos > 0)
{
if (m_midi.in.pos < sizeof (m_midi.in.data))
m_midi.in.data[m_midi.in.pos++] = data;
}
}
/*** TURNTABLE ***/
void
TURNTABLE_Init(void)
{
/* start timer */
TIMER0_Start(TT_33RPM_TICK_INTERVAL);
}
/*** PITCH ***/
void
PITCH_Init(void)
{
TCCR1A = _BV(COM1A1) | _BV(WGM11) | _BV(WGM10); /* Clear OC1A on compare, set at TOP, 10bit fast-pwm mode */
TCCR1B = _BV(WGM12) | _BV(CS10); /* 10bit fast-pwm mode, no prescaling */
OCR1A = 0;
DDRD |= _BV(DDD5); /* set OC1A pin as output */
}
void
PITCH_UpdateSpeed(void)
{
uint16_t speed, ticks;
PORTB |= _BV(6) | _BV(5) | _BV(4);
ticks = atomic_get_uint32(TIMER0_ticks);
if (m_pitch.leadInEndTicks > ticks)
{
PORTB &= ~_BV(6);
speed = m_params.pitch.leadInSpeed;
}
else if (m_pitch.markEndTicks > ticks)
{
PORTB &= ~_BV(6);
speed = m_params.pitch.markSpeed;
}
else if (m_pitch.leadOutEndTicks > ticks)
{
PORTB &= ~(_BV(5) | _BV(6));
speed = m_params.pitch.leadOutSpeed;
}
else
{
PORTB &= ~_BV(4);
speed = m_params.pitch.speed;
}
OCR1A = MIN(0x03ff, speed);
}
void
PITCH_LeadIn()
{
m_pitch.leadInEndTicks = atomic_get_uint32(TIMER0_ticks) +
m_params.pitch.leadInDuration;
}
void
PITCH_Mark()
{
m_pitch.markEndTicks = atomic_get_uint32(TIMER0_ticks) +
m_params.pitch.markDuration;
}
void
PITCH_LeadOut()
{
m_pitch.leadOutEndTicks = atomic_get_uint32(TIMER0_ticks) +
m_params.pitch.leadOutDuration;
}
/*** HEAD ***/
void
HEAD_Init(void)
{
HEAD_SERVO_PORT &= ~_BV(HEAD_SERVO_PIN);
HEAD_SERVO_DDR |= _BV(HEAD_SERVO_PIN);
/* start TIMER2 with prescale /32, overflow is every 1.024 ms @ 8MHz then */
TCNT2 = 0;
TIFR = _BV(TOV2);
TIMSK |= _BV(TOIE2);
TCCR2 = _BV(CS21) | _BV(CS20); /* prescale /32 */
}
void
HEAD_ServoPulse(uint8_t pos)
{
atomic_set_uint8(m_head.servoPulseLength, pos);
}
ISR(TIMER2_OVF_vect)
{
static uint8_t delay = 2 + 10; /* delay 10 * 1.024ms between pulses */
if (--delay > 2)
return;
else if (delay == 2)
{
/* raise pin */
HEAD_SERVO_PORT |= _BV(HEAD_SERVO_PIN);
}
else
{
if (delay == 1)
{
/* pin is raised for 1.024ms */
if (m_head.servoPulseLength > 5)
{
TCNT2 -= m_head.servoPulseLength;
}
else
{
--delay;
}
}
if (delay == 0)
{
/* pulse finished */
HEAD_SERVO_PORT &= ~_BV(HEAD_SERVO_PIN);
delay = 2 + 10;
if (m_head.servoCurvePos < (sizeof (m_params.head.raiseCurve) / sizeof (m_params.head.raiseCurve[0])))
{
register uint8_t pos = m_head.servoCurvePos;
if (m_head.servoCurve == 1)
pos += sizeof (m_params.head.raiseCurve) / sizeof (m_params.head.raiseCurve[0]);
m_head.servoPulseLength = m_params.head.raiseCurve[pos];
m_head.servoCurvePos++;
}
}
}
}
void
HEAD_ChangePos(uint8_t newPos)
{
register uint8_t curve, tmp;
if (m_params.head.position == newPos)
return;
m_params.head.position = newPos;
if (newPos == HEAD_POSITION_LOWERED)
{
PORTB &= ~_BV(3);
//HEAD_ServoPulse(0xff);
curve = 1;
}
else
{
PORTB |= _BV(3);
//HEAD_ServoPulse(0x00);
curve = 0;
}
tmp = SREG;
cli();
m_head.servoCurve = curve;
m_head.servoCurvePos = 0;
SREG = tmp;
}
/*** MAIN ***/
void
UNUSED_Init(void)
{
TIMSK = 0x00;
/* TODO: Set all floating pins as output! */
}
void
main(void)
{
uint32_t next;
/* init */
UNUSED_Init();
PARAM_Init();
MIDI_Init();
TURNTABLE_Init();
PITCH_Init();
HEAD_Init();
sei();
DDRB = 0xff;
PORTB = 0xff;
next = atomic_get_uint32(TIMER0_ticks) + 100;
for(;;)
{
MIDI_HandleData();
PITCH_UpdateSpeed();
if (atomic_get_uint32(TIMER0_ticks) >= next)
{
next += 100;
PORTB ^= _BV(7);
}
}
}