Week 19 - May 23rd 2012 - Project Development
Final Project Update
Over the past few weeks, I have completed several design cycles of my final project, reaching a stage where it meets all of the intial design criteria. Briefly, these were to:
- - Implement a device that connects via USB to a host computer and is recognised as a MIDI device
- - Receive MIDI messages and play them as audio
- - Alternatively, send MIDI messages reporting button presses back to the host computer
- - Display incoming and outgoing MIDI communications on an LCD screen
- - House the device in a suitable case
Following is documentation of each component, hopefully sufficient to allow recreation of any/all of the project. Where Eagle schematics are provided without layouts, these are still being finalised and validated.
USB module
The first component handles USB connectivity. It comprises two circuit boards; one simply holds the USB socket, the second holds the first ATTiny44 microcontroller.
The USB master microcontroller runs the V-USB software as discussed previously, allowing direct USB connectivity. MIDI data packets are received and sent across the bus (pins PA3 and PA4, serial in/out) to the synth and LCD boards.
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "usbdrv/usbdrv.h"
//lifted from NeilG
#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 20 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define led_port PORTA
#define led_direction DDRA
#define led_pin (1 << PA2)
#define serial_port PORTA
#define serial_direction DDRA
#define serial_pins PINA
#define serial_pin_in (1 << PA3)
#define serial_pin_out (1 << PA4)
#define mode_port PORTA
#define mode_direction DDRA
#define mode_pins PINA
#define mode_pin (1 << PA1)
//board IDs
#define synthID 0x01
#define lcdID 0x02
#define triggerID 0x03
// modes
#define synthMode 0x01
#define triggerMode 0x02
char mode;
uchar midiMsg[8];
char note1 = 0x3c;
char note2 = 0x3d;
char note3 = 0x3e;
char note4 = 0x3f;
char iomsg_current = 0x00;
char iomsg_transmit = 0x00;
// This descriptor is based on http://www.usb.org/developers/devclass_docs/midi10.pdf
//
// Appendix B. Example: Simple MIDI Adapter (Informative)
// B.1 Device Descriptor
//
static PROGMEM char deviceDescrMIDI[] = { /* USB device descriptor */
18, /* sizeof(usbDescriptorDevice): length of descriptor in bytes */
USBDESCR_DEVICE, /* descriptor type */
0x10, 0x01, /* USB version supported */
0, /* device class: defined at interface level */
0, /* subclass */
0, /* protocol */
8, /* max packet size */
USB_CFG_VENDOR_ID, /* 2 bytes */
USB_CFG_DEVICE_ID, /* 2 bytes */
USB_CFG_DEVICE_VERSION, /* 2 bytes */
1, /* manufacturer string index */
2, /* product string index */
0, /* serial number string index */
1, /* number of configurations */
};
// B.2 Configuration Descriptor
static PROGMEM char configDescrMIDI[] = { /* USB configuration descriptor */
9, /* sizeof(usbDescrConfig): length of descriptor in bytes */
USBDESCR_CONFIG, /* descriptor type */
101, 0, /* total length of data returned (including inlined descriptors) */
2, /* number of interfaces in this configuration */
1, /* index of this configuration */
0, /* configuration name string index */
0,
//USBATTR_BUSPOWER,
USB_CFG_MAX_BUS_POWER / 2, /* max USB current in 2mA units */
// B.3 AudioControl Interface Descriptors
// The AudioControl interface describes the device structure (audio function topology)
// and is used to manipulate the Audio Controls. This device has no audio function
// incorporated. However, the AudioControl interface is mandatory and therefore both
// the standard AC interface descriptor and the classspecific AC interface descriptor
// must be present. The class-specific AC interface descriptor only contains the header
// descriptor.
// B.3.1 Standard AC Interface Descriptor
// The AudioControl interface has no dedicated endpoints associated with it. It uses the
// default pipe (endpoint 0) for all communication purposes. Class-specific AudioControl
// Requests are sent using the default pipe. There is no Status Interrupt endpoint provided.
/* AC interface descriptor follows inline: */
9, /* sizeof(usbDescrInterface): length of descriptor in bytes */
USBDESCR_INTERFACE, /* descriptor type */
0, /* index of this interface */
0, /* alternate setting for this interface */
0, /* endpoints excl 0: number of endpoint descriptors to follow */
1, /* */
1, /* */
0, /* */
0, /* string index for interface */
// B.3.2 Class-specific AC Interface Descriptor
// The Class-specific AC interface descriptor is always headed by a Header descriptor
// that contains general information about the AudioControl interface. It contains all
// the pointers needed to describe the Audio Interface Collection, associated with the
// described audio function. Only the Header descriptor is present in this device
// because it does not contain any audio functionality as such.
/* AC Class-Specific descriptor */
9, /* sizeof(usbDescrCDC_HeaderFn): length of descriptor in bytes */
36, /* descriptor type */
1, /* header functional descriptor */
0x0, 0x01, /* bcdADC */
9, 0, /* wTotalLength */
1, /* */
1, /* */
// B.4 MIDIStreaming Interface Descriptors
// B.4.1 Standard MS Interface Descriptor
/* interface descriptor follows inline: */
9, /* length of descriptor in bytes */
USBDESCR_INTERFACE, /* descriptor type */
1, /* index of this interface */
0, /* alternate setting for this interface */
2, /* endpoints excl 0: number of endpoint descriptors to follow */
1, /* AUDIO */
3, /* MS */
0, /* unused */
0, /* string index for interface */
// B.4.2 Class-specific MS Interface Descriptor
/* MS Class-Specific descriptor */
7, /* length of descriptor in bytes */
36, /* descriptor type */
1, /* header functional descriptor */
0x0, 0x01, /* bcdADC */
65, 0, /* wTotalLength */
// B.4.3 MIDI IN Jack Descriptor
6, /* bLength */
36, /* descriptor type */
2, /* MIDI_IN_JACK desc subtype */
1, /* EMBEDDED bJackType */
1, /* bJackID */
0, /* iJack */
6, /* bLength */
36, /* descriptor type */
2, /* MIDI_IN_JACK desc subtype */
2, /* EXTERNAL bJackType */
2, /* bJackID */
0, /* iJack */
//B.4.4 MIDI OUT Jack Descriptor
9, /* length of descriptor in bytes */
36, /* descriptor type */
3, /* MIDI_OUT_JACK descriptor */
1, /* EMBEDDED bJackType */
3, /* bJackID */
1, /* No of input pins */
2, /* BaSourceID */
1, /* BaSourcePin */
0, /* iJack */
9, /* bLength of descriptor in bytes */
36, /* bDescriptorType */
3, /* MIDI_OUT_JACK bDescriptorSubtype */
2, /* EXTERNAL bJackType */
4, /* bJackID */
1, /* bNrInputPins */
1, /* baSourceID (0) */
1, /* baSourcePin (0) */
0, /* iJack */
// B.5 Bulk OUT Endpoint Descriptors
//B.5.1 Standard Bulk OUT Endpoint Descriptor
9, /* bLenght */
USBDESCR_ENDPOINT, /* bDescriptorType = endpoint */
0x1, /* bEndpointAddress OUT endpoint number 1 */
3, /* bmAttributes: 2:Bulk, 3:Interrupt endpoint */
8, 0, /* wMaxPacketSize */
10, /* bIntervall in ms */
0, /* bRefresh */
0, /* bSyncAddress */
// B.5.2 Class-specific MS Bulk OUT Endpoint Descriptor
5, /* bLength of descriptor in bytes */
37, /* bDescriptorType */
1, /* bDescriptorSubtype */
1, /* bNumEmbMIDIJack */
1, /* baAssocJackID (0) */
//B.6 Bulk IN Endpoint Descriptors
//B.6.1 Standard Bulk IN Endpoint Descriptor
9, /* bLenght */
USBDESCR_ENDPOINT, /* bDescriptorType = endpoint */
0x81, /* bEndpointAddress IN endpoint number 1 */
3, /* bmAttributes: 2: Bulk, 3: Interrupt endpoint */
8, 0, /* wMaxPacketSize */
10, /* bIntervall in ms */
0, /* bRefresh */
0, /* bSyncAddress */
// B.6.2 Class-specific MS Bulk IN Endpoint Descriptor
5, /* bLength of descriptor in bytes */
37, /* bDescriptorType */
1, /* bDescriptorSubtype */
1, /* bNumEmbMIDIJack (0) */
3, /* baAssocJackID (0) */
};
uchar usbFunctionDescriptor(usbRequest_t * rq)
{
if (rq->wValue.bytes[1] == USBDESCR_DEVICE) {
usbMsgPtr = (uchar *) deviceDescrMIDI;
return sizeof(deviceDescrMIDI);
}
else { /* must be config descriptor */
usbMsgPtr = (uchar *) configDescrMIDI;
return sizeof(configDescrMIDI);
}
}
//network protocols lifted from NeilG
void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) {
// read character into rxbyte on pins pin - assumes line driver (inverts bits)
*rxbyte = 0;
while (pin_test(*pins,pin)) ;// wait for start bit
half_bit_delay(); // delay to middle of first data bit
bit_delay();
// unrolled loop to read data bits
if pin_test(*pins,pin) *rxbyte |= (1 << 0);
else *rxbyte |= (0 << 0);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 1);
else *rxbyte |= (0 << 1);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 2);
else *rxbyte |= (0 << 2);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 3);
else *rxbyte |= (0 << 3);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 4);
else *rxbyte |= (0 << 4);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 5);
else *rxbyte |= (0 << 5);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 6);
else *rxbyte |= (0 << 6);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 7);
else *rxbyte |= (0 << 7);
// wait for stop bit
bit_delay();
half_bit_delay();
}
void put_char(volatile unsigned char *port, unsigned char pin, char txchar) {
// send character in txchar on port pin - assumes line driver (inverts bits)
clear(*port,pin); // start bit
bit_delay();
// unrolled loop to write data bits
if bit_test(txchar,0) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,1) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,2) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,3) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,4) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,5) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,6) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,7) set(*port,pin);
else clear(*port,pin);
bit_delay();
// stop bit
set(*port,pin);
bit_delay();
// char delay
bit_delay();
}
/* ------------------------------------------------------------------------- */
/* ----------------------------- USB interface ----------------------------- */
/* ------------------------------------------------------------------------- */
uchar usbFunctionSetup(uchar data[8])
{
return 0xff;
}
/*---------------------------------------------------------------------------*/
/* usbFunctionRead */
/*---------------------------------------------------------------------------*/
uchar usbFunctionRead(uchar * data, uchar len)
{
data[0] = 0;
data[1] = 0;
data[2] = 0;
data[3] = 0;
data[4] = 0;
data[5] = 0;
data[6] = 0;
return 7;
}
/*---------------------------------------------------------------------------*/
/* usbFunctionWrite */
/*---------------------------------------------------------------------------*/
uchar usbFunctionWrite(uchar * data, uchar len)
{
return 1;
}
/*---------------------------------------------------------------------------*/
/* usbFunctionWriteOut */
/* */
/* this Function is called if a MIDI Out message (from PC) arrives. */
/* See http://www.usb.org/developers/devclass_docs/midi10.pdf for a */
/* description of the data structure */
/* */
/*---------------------------------------------------------------------------*/
void usbFunctionWriteOut(uchar * data, uchar len)
{
uchar recmsg[len]; // prepare to read in received message
int i;
for (i=0; i 250 ms
wdt_reset();
_delay_ms(1);
}
usbDeviceConnect();
}
void buttonsPoll()
{
output(serial_direction, serial_pin_out);
put_char(&serial_port, serial_pin_out, triggerID);
put_char(&serial_port, serial_pin_out, 0x00);
put_char(&serial_port, serial_pin_out, 0x00);
put_char(&serial_port, serial_pin_out, 0x00);
input(serial_direction, serial_pin_out);
get_char(&serial_pins, serial_pin_in, &iomsg_transmit);
if(iomsg_transmit != iomsg_current) {
if(bit_test(iomsg_current, 7) != bit_test(iomsg_transmit, 7) { // button 1 change
if(bit_test(iomsg_current, 7) { // button 1 released
midiMsg[0] = 0x08;
midiMsg[1] = 0x80;
midiMsg[2] = note1;
midiMsg[3] = 0x00;
} else { // button 1 pressed
midiMsg[0] = 0x09;
midiMsg[1] = 0x90;
midiMsg[2] = note1;
midiMsg[3] = 0xff;
}
}
if(bit_test(iomsg_current, 6) != bit_test(iomsg_transmit, 6) { // button 2 change
if(bit_test(iomsg_current, 6) { // button 2 released
midiMsg[0] = 0x08;
midiMsg[1] = 0x80;
midiMsg[2] = note2;
midiMsg[3] = 0x00;
} else { // button 2 pressed
midiMsg[0] = 0x09;
midiMsg[1] = 0x90;
midiMsg[2] = note2;
midiMsg[3] = 0xff;
}
}
if(bit_test(iomsg_current, 5) != bit_test(iomsg_transmit, 5) { // button 3 change
if(bit_test(iomsg_current, 5) { // button 3 released
midiMsg[0] = 0x08;
midiMsg[1] = 0x80;
midiMsg[2] = note3;
midiMsg[3] = 0x00;
} else { // button 3 pressed
midiMsg[0] = 0x09;
midiMsg[1] = 0x90;
midiMsg[2] = note3;
midiMsg[3] = 0xff;
}
}
if(bit_test(iomsg_current, 4) != bit_test(iomsg_transmit, 4) { // button 4 change
if(bit_test(iomsg_current, 4) { // button 1 released
midiMsg[0] = 0x08;
midiMsg[1] = 0x80;
midiMsg[2] = note4;
midiMsg[3] = 0x00;
} else { // button 1 pressed
midiMsg[0] = 0x09;
midiMsg[1] = 0x90;
midiMsg[2] = note4;
midiMsg[3] = 0xff;
}
}
usbSetInterrupt(midiMsg, 4);
iomsg_current = iomsg_transmit;
output(serial_direction, serial_pin_out);
put_char(&serial_port, serial_pin_out, lcdID);
put_char(&serial_port, serial_pin_out, recmsg[1]);
put_char(&serial_port, serial_pin_out, recmsg[2]);
put_char(&serial_port, serial_pin_out, recmsg[3]);
input(serial_direction, serial_pin_out);
}
}
int main()
{
initStatusLED();
initModeSwitch();
initTXRX();
initUSB();
// Globally enable interrupts
sei();
if(pin_test(mode_pins, mode_pin)) {mode = synthMode;} else {mode = triggerMode;}
// Endless loop
for (;;) {
wdt_reset();
if (mode = synthMode) {usbPoll();}
if (mode = triggerMode) {buttonsPoll();}
}
return 0;
}
Synth module
The second ATTiny44 microcontroller runs a modified version of the 4bitsynth software, which generates audio from MIDI messages. Output is through an R-2R resistor ladder acting as a DAC. The board includes a LM386 audio amplifier IC to amplify the audio signal sufficiently to be heard through the internal speaker.
//Modified by Joel R from 4bitsynth source
/*
* This file is part of 4bitsynth.
*
* 4bitsynth 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 3 of the License, or
* (at your option) any later version.
*
* 4bitsynth 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 4bitsynth. If not, see http://www.gnu.org/licenses/.
*/
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "main.h"
char recmsg0;
char recmsg1;
char recmsg2;
char recmsg3;
#define synthID 1
//lifted from NeilG
#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 20 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define led_port PORTA
#define led_direction DDRA
#define led_pin (1 << PA6)
#define serial_port PORTA
#define serial_direction DDRA
#define serial_pins PINA
#define serial_pin_in (1 << PA4)
#define serial_pin_out (1 << PA5)
//network protocols lifted from NeilG
void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) {
// read character into rxbyte on pins pin - assumes line driver (inverts bits)
*rxbyte = 0;
while (pin_test(*pins,pin)) ;// wait for start bit
half_bit_delay(); // delay to middle of first data bit
bit_delay();
// unrolled loop to read data bits
if pin_test(*pins,pin) *rxbyte |= (1 << 0);
else *rxbyte |= (0 << 0);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 1);
else *rxbyte |= (0 << 1);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 2);
else *rxbyte |= (0 << 2);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 3);
else *rxbyte |= (0 << 3);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 4);
else *rxbyte |= (0 << 4);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 5);
else *rxbyte |= (0 << 5);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 6);
else *rxbyte |= (0 << 6);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 7);
else *rxbyte |= (0 << 7);
// wait for stop bit
bit_delay();
half_bit_delay();
}
//void init_interrupts() {
//Turn on USART reception and | RX Interrupt
// UCSR0B = (1 << RXEN0) | (1 << RXCIE0);
//8-bit, 1 stop, Asynch.
// UCSR0C = (0 << UMSEL00) | (0 << UPM00) | (0 << USBS0) | (3 << UCSZ00);
/* Set the baud rate to 31250 for MIDI */
// UBRR0L = 0x27; // For 20MHz Clock
//UBRR0L = 0x13; // FOr 10MHz Clock
/* Enable USART Receive interrupt */
// enable_USART_interrupts();
//}
void init_io() {
//b0 - b3 of PORT A is output
// DDRA = 0x0F;
//b4 0 v7 of PORT D is input (MIDI Channel selection)
// DDRD &= 0b00001111;
//enable internal pull-up resistors for MIDI Channel selection bits
// PORTD |= 0b11110000;
output(led_direction, led_pin);
set(serial_port, serial_pin_out);
input(serial_direction, serial_pin_out);
}
void init_timers() {
//8-bit timer 0 for decay, sweep, vibrato effects?
//Enable Overflow interrupts for Timer 0
TIMSK0 = 0b00000001;
//Normal counter operation
TCCR0A = 0b00000000;
//Divide by 1024 prescalar
TCCR0B = 0b00000101;
//Start terminal count at zero
TCNT0 = 0x00;
//16-bit timer 1 for main frequency generation
TIMSK1 = 0b00000010; // Enable A and B compare interrupts
TCCR1A = 0b00000001;
// Prescaler 1, Fast PWM
TCCR1B = 0b00010001;
//Start count at zero now
TCNT1H = 0;
TCNT1L = 0;
// CLKPR = (1 << CLKPCE);
// CLKPR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);
}
int main(void) {
/* Disable interrupts at first */
cli();
/* Setup I/O Pins */
init_io();
/* Setup Timers */
init_timers();
/* Enable USART and Pin Interrupts */
// init_interrupts();
// PORTA |= 0x0F;
OCR1A = 500;
/*Pitch bend needs to start at 64 (middle value) or pitch will start out
two half steps too low (0 = max bend downward)
*/
current_midi_pb_l = current_midi_pb_h = 64;
num_bytes = 0;
num_ccs = 0;
num_pbs = 0;
byte_ready = 0;
midi_channel = 0;
master_volume = 127;
amplitude = 0;
fine_pitch_bend = 0;
note_on_gate = 0;
sweep_enabled = 1;
sweep_direction = SWEEP_UP;
sweep_amount = 0;
sweep_loop_enabled = 1;
fake_16_timer = 0;
/* Finally, enable global interrupts */
sei();
/*Main Loop*/
while (1) {
// check_channel_set();
// check_byte_received();
// if(pin_test(led_port, led_pin)) clear(led_port, led_pin);
// else set(led_port, led_pin);
// _delay_ms(200);
get_char(&serial_pins, serial_pin_in, &recmsg0);
get_char(&serial_pins, serial_pin_in, &recmsg1);
get_char(&serial_pins, serial_pin_in, &recmsg2);
get_char(&serial_pins, serial_pin_in, &recmsg3);
/* int i;
int j;
for (i = 8; i > 0; i--) {
for (j = 0; j < i; j++) {
set(led_port, led_pin);
_delay_ms(80);
clear(led_port, led_pin);
_delay_ms(20);
}
if (bit_test(recmsg[0], (i-1))) set(led_port, led_pin);
_delay_ms(400);
clear(led_port, led_pin);
_delay_ms(100);
}*/
if (recmsg0 == synthID) {
byte_received = recmsg1;
// byte_ready = 1;
check_byte_received();
byte_received = recmsg2;
// byte_ready = 1;
check_byte_received();
byte_received = recmsg3;
// byte_ready = 1;
check_byte_received();
}
}
return 0;
}
/* USART Received byte interrupt (get MIDI byte)*/
//ISR(USART_RX_vect) {
// byte_received = UDR0;
// byte_ready = 1;
//}
ISR(TIM1_COMPA_vect)
{
if(note_on_gate == 1) {
fivebit_counter ++;
//Reset counter if 'overflowed'
if(fivebit_counter> 31) fivebit_counter = 0;
//Each amplitude b0-3 is XNORed with b4 to create output
unsigned char strobe = (fivebit_counter & 0b00010000) >> 4;
if(strobe == 1) {
amplitude = ~(fivebit_counter ^ 0b00001111);
} else {
amplitude = ~(fivebit_counter ^ 0b00000000);
}
amplitude &= 0b11101111;
amplitude |= (strobe << 4);
PORTA = amplitude;
}
}
ISR(TIM0_OVF_vect) {
/* Sweep */
if((sweep_enabled == 1) && (sweep_amount> 0) && (note_on_gate == 1)) {
fake_16_timer ++;
if(fake_16_timer >= 2) {
unsigned int sweep_mod = sweep_amount * note_table[playing_midi_note] / (8 * playing_midi_note);
//Sweep down mode
if(sweep_direction == SWEEP_DOWN) {
if(frequency < MAXIMUM_FREQ) {
frequency += sweep_mod;
}
else
{
if(sweep_loop_enabled == 1) {
frequency = note_table[playing_midi_note];
}
else {
amplitude = 0;
}
}
}
//Sweep up mode
else
{
if(frequency> MINIMUM_FREQ+sweep_mod) {
frequency -= sweep_mod;
}
else
{
if(sweep_loop_enabled == 1) {
frequency = note_table[playing_midi_note];
}
else {
amplitude = 0;
}
}
}
update_frequency(frequency);
fake_16_timer = 0;
}
}
}
void check_byte_received() {
//Is there a byte waiting in the buffer?
// if (byte_ready == 1) {
//Is this a stupid byte like Clock or Active sensing?
if (byte_received < 0xF0) {
//Is this a status byte? ...
if (byte_received >= 0x80) {
unsigned char temp_midi_channel = byte_received & 0x0F;
//Is this for one of our channels?
if (temp_midi_channel == midi_channel) {
current_midi_channel = temp_midi_channel;
//What kind of status byte is this?
unsigned char status_type = (byte_received & 0xF0);
switch (status_type) {
case (MIDI_STATUS_TYPE_NOTEON):
current_midi_status = MIDI_STATUS_NOTEON;
break;
case (MIDI_STATUS_TYPE_NOTEOFF):
current_midi_status = MIDI_STATUS_NOTEOFF;
break;
case (MIDI_STATUS_TYPE_CC):
current_midi_status = MIDI_STATUS_CC;
break;
case (MIDI_STATUS_TYPE_PB):
current_midi_status = MIDI_STATUS_PB;
break;
default:
current_midi_status = MIDI_STATUS_NONE;
}
} else {
current_midi_status = 0;
}
}
// ... or is it a data byte?
else {
switch (current_midi_status) {
case (MIDI_STATUS_NOTEON):
//Is this a velocity byte?
if (num_bytes > 0) {
//If the velocity sent was 0, then this is really a NOTE-OFF
if (byte_received > 0) {
//current_midi_velocity = byte_received;
playing_midi_note = current_midi_note;
note_on();
} else {
if (current_midi_note == playing_midi_note) {
//current_midi_velocity = 0;
note_off();
}
}
num_bytes = 0;
}
//Or is this a note data byte?
else {
if((byte_received > 17) & (byte_received < 66)) {
current_midi_note = byte_received;
num_bytes++;
}
}
//Clear the byte so we don't process it twice
clear_byte_received();
break;
case (MIDI_STATUS_NOTEOFF):
//Is this a velocity byte?
if (num_bytes > 0) {
//Who cares? We aren't implementing aftertouch
num_bytes = 0;
} else {
/* This is a note byte. Let's see if it's the same as the currently
* playing note. Only then will we note_off()
*/
if (byte_received == playing_midi_note) {
note_off();
}
num_bytes++;
}
//Clear the byte so we don't process it twice
clear_byte_received();
break;
case (MIDI_STATUS_CC):
//Did we already get a CC Status byte?
if (num_ccs > 0) {
current_midi_ccdata = byte_received;
process_cc();
}
//Or is this a new CC status byte?
else {
current_midi_cc = byte_received;
num_ccs++;
}
break;
case (MIDI_STATUS_PB):
//How many PB related bytes have we gotten?
switch (num_pbs) {
case (0):
//First byte is 7 LSB
//Don't care about it for now
//current_midi_pb_l = byte_received;
num_pbs++;
break;
case (1):
//Second byte has 7 MSB
current_midi_pb_h = byte_received;
//Combine to get 14 bytes 0 - 13
//current_midi_pb = ((current_midi_pb_h << 7)|(current_midi_pb_l << 0));
bend_pitch();
break;
}
break;
}
}
}
// byte_ready = 0;
// }
}
/*
void enable_USART_interrupts() {
UCSR0A = (1 << RXCIE0);
}
void disable_USART_interrupts() {
UCSR0A = (0 << RXCIE0);
}
*/
void note_on() {
num_bytes = 0;
//Reset main timer1
TCNT1 = 0;
//Set timer count corresponding to midi note and thus musical note
frequency = note_table[current_midi_note];
update_frequency(frequency);
note_on_gate = 1;
set(led_port, led_pin);
}
void note_off() {
num_bytes = 0;
note_on_gate = 0;
clear(led_port, led_pin);
}
void process_cc() {
num_ccs = 0;
switch (current_midi_cc) {
case MASTER_VOLUME_CC:
master_volume = current_midi_ccdata;
break;
case FINE_PITCH_CC:
fine_pitch_bend = ((note_table[playing_midi_note - 1]
- note_table[playing_midi_note]) * current_midi_ccdata) / 192;
update_frequency(note_table[playing_midi_note]);
break;
case SWEEP_ENABLED_CC:
if (current_midi_ccdata > 64)
sweep_enabled = 1;
else
sweep_enabled = 0;
break;
case SWEEP_DIRECTION_CC:
if (current_midi_ccdata > 64)
sweep_direction = SWEEP_UP;
else
sweep_direction = SWEEP_DOWN;
break;
case SWEEP_AMOUNT_CC:
sweep_amount = current_midi_ccdata;
OCR1B = sweep_amount * 1024;
break;
case SWEEP_LOOP_ENABLED_CC:
if (current_midi_ccdata > 64)
sweep_loop_enabled = 1;
else
sweep_loop_enabled = 0;
break;
}
}
void update_frequency(unsigned int new_frequency) {
OCR1A = new_frequency + fine_pitch_bend;
}
void bend_pitch() {
num_pbs = 0;
if (current_midi_pb_h > 63) {
distance
= ((note_table[playing_midi_note]
- note_table[playing_midi_note + 2])
* (current_midi_pb_h - 63)) / 64;
update_frequency(note_table[playing_midi_note] - distance);
} else if ((current_midi_pb_h < 63) && (playing_midi_note > 1)) {
distance = ((note_table[playing_midi_note - 2]
- note_table[playing_midi_note]) * (64 - current_midi_pb_h))
/ 64;
update_frequency(note_table[playing_midi_note] + distance);
}
}
void clear_byte_received() {
byte_received = 0;
}
/*
void check_channel_set() {
midi_channel = 0;
//Get 4-bit (0-16) MIDI CHannel from PORTD b4-b7)
midi_channel |= (~PIND & 0xF0) >> 4;
}
*/
LCD module
The third ATTiny44 microcontroller drives the LCD screen as per the examples linked previously. Within the code there is a function to decode the MIDI instruction and generate a suitable message string.
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include "main.h"
//lifted from NeilG
#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 20 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define lcd_port PORTA
#define lcd_direction DDRA
#define lcd_pins PINA
#define lcd_SCE (1 << PA0)
#define lcd_RST (1 << PA1)
#define lcd_DC (1 << PA2)
#define lcd_SDIN (1 << PA3)
#define lcd_SCLK (1 << PA4)
#define led_port PORTA
#define led_direction DDRA
#define led_pin (1 << PA5)
#define serial_port PORTA
#define serial_direction DDRA
#define serial_pins PINA
#define serial_pin_in (1 << PA6)
#define serial_pin_out (1 << PA7)
#define LCD_C 0
#define LCD_D 1
#define LCD_X 84
#define LCD_Y 48
#define lcdID 2
char recmsg0;
char recmsg1;
char recmsg2;
char recmsg3;
const uint8_t PROGMEM ascii_table[] =
{
0x00, 0x00, 0x00, 0x00, 0x00, // 20
0x00, 0x00, 0x5f, 0x00, 0x00, // 21 !
0x00, 0x07, 0x00, 0x07, 0x00, // 22 "
0x14, 0x7f, 0x14, 0x7f, 0x14, // 23 #
0x24, 0x2a, 0x7f, 0x2a, 0x12, // 24 $
0x23, 0x13, 0x08, 0x64, 0x62, // 25 %
0x36, 0x49, 0x55, 0x22, 0x50, // 26 &
0x00, 0x05, 0x03, 0x00, 0x00, // 27 '
0x00, 0x1c, 0x22, 0x41, 0x00, // 28 (
0x00, 0x41, 0x22, 0x1c, 0x00, // 29 )
0x14, 0x08, 0x3e, 0x08, 0x14, // 2a *
0x08, 0x08, 0x3e, 0x08, 0x08, // 2b +
0x00, 0x50, 0x30, 0x00, 0x00, // 2c ,
0x08, 0x08, 0x08, 0x08, 0x08, // 2d -
0x00, 0x60, 0x60, 0x00, 0x00, // 2e .
0x20, 0x10, 0x08, 0x04, 0x02, // 2f /
0x3e, 0x51, 0x49, 0x45, 0x3e, // 30 0
0x00, 0x42, 0x7f, 0x40, 0x00, // 31 1
0x42, 0x61, 0x51, 0x49, 0x46, // 32 2
0x21, 0x41, 0x45, 0x4b, 0x31, // 33 3
0x18, 0x14, 0x12, 0x7f, 0x10, // 34 4
0x27, 0x45, 0x45, 0x45, 0x39, // 35 5
0x3c, 0x4a, 0x49, 0x49, 0x30, // 36 6
0x01, 0x71, 0x09, 0x05, 0x03, // 37 7
0x36, 0x49, 0x49, 0x49, 0x36, // 38 8
0x06, 0x49, 0x49, 0x29, 0x1e, // 39 9
0x00, 0x36, 0x36, 0x00, 0x00, // 3a :
0x00, 0x56, 0x36, 0x00, 0x00, // 3b ;
0x08, 0x14, 0x22, 0x41, 0x00, // 3c <
0x14, 0x14, 0x14, 0x14, 0x14, // 3d =
0x00, 0x41, 0x22, 0x14, 0x08, // 3e >
0x02, 0x01, 0x51, 0x09, 0x06, // 3f ?
0x32, 0x49, 0x79, 0x41, 0x3e, // 40 @
0x7e, 0x11, 0x11, 0x11, 0x7e, // 41 A
0x7f, 0x49, 0x49, 0x49, 0x36, // 42 B
0x3e, 0x41, 0x41, 0x41, 0x22, // 43 C
0x7f, 0x41, 0x41, 0x22, 0x1c, // 44 D
0x7f, 0x49, 0x49, 0x49, 0x41, // 45 E
0x7f, 0x09, 0x09, 0x09, 0x01, // 46 F
0x3e, 0x41, 0x49, 0x49, 0x7a, // 47 G
0x7f, 0x08, 0x08, 0x08, 0x7f, // 48 H
0x00, 0x41, 0x7f, 0x41, 0x00, // 49 I
0x20, 0x40, 0x41, 0x3f, 0x01, // 4a J
0x7f, 0x08, 0x14, 0x22, 0x41, // 4b K
0x7f, 0x40, 0x40, 0x40, 0x40, // 4c L
0x7f, 0x02, 0x0c, 0x02, 0x7f, // 4d M
0x7f, 0x04, 0x08, 0x10, 0x7f, // 4e N
0x3e, 0x41, 0x41, 0x41, 0x3e, // 4f O
0x7f, 0x09, 0x09, 0x09, 0x06, // 50 P
0x3e, 0x41, 0x51, 0x21, 0x5e, // 51 Q
0x7f, 0x09, 0x19, 0x29, 0x46, // 52 R
0x46, 0x49, 0x49, 0x49, 0x31, // 53 S
0x01, 0x01, 0x7f, 0x01, 0x01, // 54 T
0x3f, 0x40, 0x40, 0x40, 0x3f, // 55 U
0x1f, 0x20, 0x40, 0x20, 0x1f, // 56 V
0x3f, 0x40, 0x38, 0x40, 0x3f, // 57 W
0x63, 0x14, 0x08, 0x14, 0x63, // 58 X
0x07, 0x08, 0x70, 0x08, 0x07, // 59 Y
0x61, 0x51, 0x49, 0x45, 0x43, // 5a Z
0x00, 0x7f, 0x41, 0x41, 0x00, // 5b [
0x02, 0x04, 0x08, 0x10, 0x20, // 5c ¥
0x00, 0x41, 0x41, 0x7f, 0x00, // 5d ]
0x04, 0x02, 0x01, 0x02, 0x04, // 5e ^
0x40, 0x40, 0x40, 0x40, 0x40, // 5f _
0x00, 0x01, 0x02, 0x04, 0x00, // 60 `
0x20, 0x54, 0x54, 0x54, 0x78, // 61 a
0x7f, 0x48, 0x44, 0x44, 0x38, // 62 b
0x38, 0x44, 0x44, 0x44, 0x20, // 63 c
0x38, 0x44, 0x44, 0x48, 0x7f, // 64 d
0x38, 0x54, 0x54, 0x54, 0x18, // 65 e
0x08, 0x7e, 0x09, 0x01, 0x02, // 66 f
0x0c, 0x52, 0x52, 0x52, 0x3e, // 67 g
0x7f, 0x08, 0x04, 0x04, 0x78, // 68 h
0x00, 0x44, 0x7d, 0x40, 0x00, // 69 i
0x20, 0x40, 0x44, 0x3d, 0x00, // 6a j
0x7f, 0x10, 0x28, 0x44, 0x00, // 6b k
0x00, 0x41, 0x7f, 0x40, 0x00, // 6c l
0x7c, 0x04, 0x18, 0x04, 0x78, // 6d m
0x7c, 0x08, 0x04, 0x04, 0x78, // 6e n
0x38, 0x44, 0x44, 0x44, 0x38, // 6f o
0x7c, 0x14, 0x14, 0x14, 0x08, // 70 p
0x08, 0x14, 0x14, 0x18, 0x7c, // 71 q
0x7c, 0x08, 0x04, 0x04, 0x08, // 72 r
0x48, 0x54, 0x54, 0x54, 0x20, // 73 s
0x04, 0x3f, 0x44, 0x40, 0x20, // 74 t
0x3c, 0x40, 0x40, 0x20, 0x7c, // 75 u
0x1c, 0x20, 0x40, 0x20, 0x1c, // 76 v
0x3c, 0x40, 0x30, 0x40, 0x3c, // 77 w
0x44, 0x28, 0x10, 0x28, 0x44, // 78 x
0x0c, 0x50, 0x50, 0x50, 0x3c, // 79 y
0x44, 0x64, 0x54, 0x4c, 0x44, // 7a z
0x00, 0x08, 0x36, 0x41, 0x00, // 7b {
0x00, 0x00, 0x7f, 0x00, 0x00, // 7c |
0x00, 0x41, 0x36, 0x08, 0x00, // 7d }
};
//network protocols lifted from NeilG
void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) {
// read character into rxbyte on pins pin - assumes line driver (inverts bits)
*rxbyte = 0;
while (pin_test(*pins,pin)) ;// wait for start bit
half_bit_delay(); // delay to middle of first data bit
bit_delay();
// unrolled loop to read data bits
if pin_test(*pins,pin) *rxbyte |= (1 << 0);
else *rxbyte |= (0 << 0);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 1);
else *rxbyte |= (0 << 1);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 2);
else *rxbyte |= (0 << 2);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 3);
else *rxbyte |= (0 << 3);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 4);
else *rxbyte |= (0 << 4);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 5);
else *rxbyte |= (0 << 5);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 6);
else *rxbyte |= (0 << 6);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 7);
else *rxbyte |= (0 << 7);
// wait for stop bit
bit_delay();
half_bit_delay();
}
void lcd_write(uint8_t dc, uint8_t data)
{
if (dc) set(lcd_port, lcd_DC);
else clear(lcd_port, lcd_DC);
clear(lcd_port, lcd_SCE);
int i;
for (i = 0; i < 8; i++)
{
if (data&(1<<7)) set(lcd_port, lcd_SDIN);
else clear(lcd_port, lcd_SDIN);
clear(lcd_port, lcd_SCLK);
set(lcd_port, lcd_SCLK);
data <<= 1;
}
set(lcd_port, lcd_SCE);
clear(lcd_port, lcd_SDIN);
}
void lcd_char(char c)
{
lcd_write(LCD_D, 0x00);
int i;
for (i = 0; i < 5; i++)
lcd_write(LCD_D, pgm_read_byte(ascii_table+(c-0x20)*5+i));
lcd_write(LCD_D, 0x00);
}
void lcd_str(const char* str)
{
while (pgm_read_byte(str))
{
lcd_char(pgm_read_byte(str));
str++;
}
}
void lcd_clear(void)
{
int i;
for (i = 0; i < LCD_X*LCD_Y/8; i++)
lcd_write(LCD_D, 0x00);
}
void lcd_gotoXY(int x, int y) // x 0 - 84, y 0 - 5
{
lcd_write(0, 0x80 | x); // column
lcd_write(0, 0x40 | y); // row
}
void setup_lcd() {
clear(lcd_port, lcd_RST);
set(lcd_port, lcd_RST);
lcd_write(LCD_C,0x21); //switch to extended commands
lcd_write(LCD_C,0xBB); //set value of Vop (controls contrast) = 0x80 | 0x60 (arbitrary)
lcd_write(LCD_C,0x04); //set temperature coefficient
lcd_write(LCD_C,0x14); //set bias mode to 1:48.
lcd_write(LCD_C,0x20); //switch back to regular commands
lcd_write(LCD_C,0x0C); //enable normal display (dark on light), horizontal addressing
}
void setup_io() {
output(lcd_direction, lcd_SCE);
output(lcd_direction, lcd_RST);
output(lcd_direction, lcd_DC);
output(lcd_direction, lcd_SDIN);
output(lcd_direction, lcd_SCLK);
output(led_direction, led_pin);
set(serial_port, serial_pin_out);
input(serial_direction, serial_pin_out);
}
int main() {
setup_io();
setup_lcd();
lcd_clear();
lcd_str(PSTR("FabSynth 0.1"));
_delay_ms(2000); // this is to allow for v-usb board to fully boot
while (1)
{
get_char(&serial_pins, serial_pin_in, &recmsg0);
get_char(&serial_pins, serial_pin_in, &recmsg1);
get_char(&serial_pins, serial_pin_in, &recmsg2);
get_char(&serial_pins, serial_pin_in, &recmsg3);
if (recmsg0 == lcdID) {
lcd_clear();
lcd_gotoXY(0,0);
lcd_str(PSTR("note played"));
switch (recmsg2 % 12) {
case (0): lcd_str(PSTR("C")); break;
case (1): lcd_str(PSTR("C#")); break;
case (2): lcd_str(PSTR("D")); break;
case (3): lcd_str(PSTR("D#")); break;
case (4): lcd_str(PSTR("E")); break;
case (5): lcd_str(PSTR("F")); break;
case (6): lcd_str(PSTR("F#")); break;
case (7): lcd_str(PSTR("G")); break;
case (8): lcd_str(PSTR("G#")); break;
case (9): lcd_str(PSTR("A")); break;
case (10): lcd_str(PSTR("A#")); break;
case (11): lcd_str(PSTR("B")); break;
}
switch (recmsg2 / 12) {
case (0): lcd_str(PSTR("-2")); break;
case (1): lcd_str(PSTR("-1")); break;
case (2): lcd_str(PSTR("0")); break;
case (3): lcd_str(PSTR("1")); break;
case (4): lcd_str(PSTR("2")); break;
case (5): lcd_str(PSTR("3")); break;
case (6): lcd_str(PSTR("4")); break;
case (7): lcd_str(PSTR("5")); break;
case (8): lcd_str(PSTR("6")); break;
case (9): lcd_str(PSTR("7")); break;
case (10): lcd_str(PSTR("8")); break;
}
switch (recmsg3 > 0) {
case (0): lcd_str(PSTR(" off")); clear(led_port, led_pin); break;
case (1): lcd_str(PSTR(" on")); set(led_port, led_pin); break;
}
}
}
return 0;
}
Trigger module
The fourth microcontroller, when it receives the ready instruction from the USB master microontroller, reads and transmits the states of the four push buttons. The master module then determines whether there have been any state changes, and generates a MIDI message if necessary. The buttons are wired with a common ground, and internal pullups activated on the relevant microcontroller input pins.
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include "main.h"
//lifted from NeilG
#define output(directions,pin) (directions |= pin) // set port direction for output
#define input(directions,pin) (directions &= (~pin)) // set port direction for input
#define set(port,pin) (port |= pin) // set port pin
#define clear(port,pin) (port &= (~pin)) // clear port pin
#define pin_test(pins,pin) (pins & pin) // test for port pin
#define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set
#define bit_delay_time 20 // bit delay for 9600 with overhead
#define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay
#define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay
#define button_port PORTA
#define button_direction DDRA
#define button_pins PINA
#define button1_pin (1 << PA0)
#define button2_pin (1 << PA1)
#define button3_pin (1 << PA2)
#define button4_pin (1 << PA3)
#define led_port PORTA
#define led_direction DDRA
#define led_pin (1 << PA5)
#define serial_port PORTA
#define serial_direction DDRA
#define serial_pins PINA
#define serial_pin_in (1 << PA6)
#define serial_pin_out (1 << PA7)
#define triggerID 2
char recmsg0;
char recmsg1;
char recmsg2;
char recmsg3;
char sendchr;
//network protocols lifted from NeilG
void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) {
// read character into rxbyte on pins pin - assumes line driver (inverts bits)
*rxbyte = 0;
while (pin_test(*pins,pin)) ;// wait for start bit
half_bit_delay(); // delay to middle of first data bit
bit_delay();
// unrolled loop to read data bits
if pin_test(*pins,pin) *rxbyte |= (1 << 0);
else *rxbyte |= (0 << 0);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 1);
else *rxbyte |= (0 << 1);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 2);
else *rxbyte |= (0 << 2);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 3);
else *rxbyte |= (0 << 3);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 4);
else *rxbyte |= (0 << 4);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 5);
else *rxbyte |= (0 << 5);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 6);
else *rxbyte |= (0 << 6);
bit_delay();
if pin_test(*pins,pin) *rxbyte |= (1 << 7);
else *rxbyte |= (0 << 7);
// wait for stop bit
bit_delay();
half_bit_delay();
}
void put_char(volatile unsigned char *port, unsigned char pin, char txchar) {
// send character in txchar on port pin - assumes line driver (inverts bits)
clear(*port,pin); // start bit
bit_delay();
// unrolled loop to write data bits
if bit_test(txchar,0) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,1) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,2) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,3) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,4) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,5) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,6) set(*port,pin);
else clear(*port,pin);
bit_delay();
if bit_test(txchar,7) set(*port,pin);
else clear(*port,pin);
bit_delay();
// stop bit
set(*port,pin);
bit_delay();
// char delay
bit_delay();
}
void setup_io() {
set(button_port, button1_pin);
set(button_port, button2_pin);
set(button_port, button3_pin);
set(button_port, button4_pin);
input(button_direction, button1_pin);
input(button_direction, button2_pin);
input(button_direction, button3_pin);
input(button_direction, button4_pin);
output(led_direction, led_pin);
set(serial_port, serial_pin_out);
input(serial_direction, serial_pin_out);
}
int main() {
setup_io();
while (1)
{
get_char(&serial_pins, serial_pin_in, &recmsg0);
get_char(&serial_pins, serial_pin_in, &recmsg1);
get_char(&serial_pins, serial_pin_in, &recmsg2);
get_char(&serial_pins, serial_pin_in, &recmsg3);
if (recmsg0 == triggerID) {
sendchr = button_pins && 0xF0;
output(serial_direction, serial_pin_out);
putchar(&serial_pins, serial_pin_out, &sendchar);
input(serial_direction, serial_pin_out);
}
return 0;
}
}
Casing
The case was modelled in Sketchup then lasercut from 3mm plywood. The clip joints hold the case together tightly and have withstood several attachment/detachment cycles.
Ongoing work
Shortcomings of the project (set aside for future developments) focus on two areas:
- - Commercial MIDI hardware is designed such that lag is kept to an absolute minimum, in order to allow for expressive, responsive instruments. This has not been a focus of my project, as it would greatly increase the complexity both of the components required and of the implementation.
- - Commercial MIDI synthesisers also tend to have the ability to generate more than a square wave; I have not initially concentrated on making a fully featured synth, rather I have tried to create a proof of concept that will be of help to people undertaking similar projects in the future.
In addition to these, I aim to complete the layouts for the remaining circuit boards to take the final modules out of the prototyping environment.
Conclusions
Overall I am very happy with how this project turned out. I found the spiral development approach very helpful, working towards gradual increments which forced me to think through the individual components of the project before starting. At times I also reached the limits of the capabilities of the microcontroller and my ability to efficiently program it, giving useful insight into the scope of what can be achieved with these components.
<<< Week 18
Module Index >>>