alexis 5 months ago
parent 31480f2f22
commit 6814cb57dd

@ -6,6 +6,7 @@
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <avr/interrupt.h>
#include <avrstuff/serial.hpp>
#include <avrstuff/ringbuf.hpp>
@ -19,7 +20,11 @@ public:
bufserial(serial & sp)
: m_serial(&sp)
, m_rx_overflow(false)
, m_error(0)
{
}
void init()
{
m_serial->rxcie(true);
}
@ -29,15 +34,18 @@ public:
void rxc()
{
int c = m_serial->recv();
if (!m_rxbuf.push(c))
m_rx_overflow = true;
if (c < 0)
m_error = c;
else if (!m_rxbuf.push(c))
m_error = serial::BUF_OVERFLOW;
}
// Data Register Empty interrupt handler. Must be called from the
// relevant vector if transmit buffering is used.
void dre()
{
int c;
uint8_t c;
bool has_c = m_txbuf.pop(c);
if (has_c)
@ -48,11 +56,13 @@ public:
virtual int recv() override
{
int c;
if (m_rx_overflow)
uint8_t c;
if (m_error)
{
m_rx_overflow = false;
return serial::BUF_OVERFLOW;
int e = m_error;
m_error = 0;
return e;
}
else if (m_rxbuf.pop(c))
return c;
@ -62,15 +72,15 @@ public:
virtual void send(int b) override
{
m_txbuf.push_wait(b);
while (!m_txbuf.push(b));
m_serial->dreie(true);
}
private:
serial * m_serial;
ringbuf<int, Txsize> m_txbuf;
ringbuf<int, Rxsize> m_rxbuf;
bool m_rx_overflow;
int m_error;
ringbuf<uint8_t, Txsize> m_txbuf;
ringbuf<uint8_t, Rxsize> m_rxbuf;
};
#endif // !defined(BUFSERIAL_HPP)

@ -0,0 +1,21 @@
// intellectual property is bullshit bgdc
#ifndef MATH_MISC_HPP
#define MATH_MISC_HPP
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
// Return num/den rounded to the nearest integer. Undefined for negatives
// (TODO clean it up in the future)
template<typename T>
static inline T rounded_div(T num, T den)
{
T quo = num/den, rem = num%den;
if (rem >= den/2)
quo++;
return quo;
}
#endif // !defined(MATH_MISC_HPP)

@ -7,16 +7,32 @@
#include <stddef.h>
#include <inttypes.h>
// Single producer, single consumer ring buffer. Size is limited to 255 to
// permit volatile access to counters. Sizes one less than a power of two
// provide the most efficient implementation.
// Size-agnostic reference to a ringbuf
template<typename T>
class iringbuf
{
public:
virtual ~iringbuf() = default;
virtual bool push(T const & value) = 0;
virtual bool pop(T & value) = 0;
};
// Single producer, single consumer ring buffer. Size is limited to 256 to
// permit volatile access to counters. Sizes equal to a power of two provide
// the most efficient implementation.
//
// If used with more than one producer or consumer, access must be locked.
// Otherwise, threadsafe.
template<typename T, unsigned Size>
class ringbuf
class ringbuf: public iringbuf<T>
{
public:
static_assert(
Size <= UINT8_MAX,
"ringbuf size must not exceed UINT8_MAX"
);
ringbuf() : m_head(0), m_tail(0) {}
// Push into the buffer or return immediately.
@ -28,18 +44,12 @@ public:
return false;
else
{
m_buffer[m_tail] = value;
m_tail = next(m_tail);
m_buffer[m_head] = value;
m_head = (m_head + 1) % Size;
return true;
}
}
// Push into the buffer, waiting until ready.
void push_wait(T const & value)
{
while (!push(value));
}
// Pop out of the buffer or return immediately.
//
// Return: whether a value was popped
@ -49,40 +59,34 @@ public:
return false;
else
{
value = m_buffer[m_head];
m_head = next(m_head);
value = m_buffer[m_tail];
m_tail = (m_tail + 1) % Size;
return true;
}
}
// Pop out of the buffer, waiting until ready.
void pop_wait(T & value)
// Return number of elements in the buffer
uint8_t count() const
{
while (!pop(value));
return m_head - m_tail;
}
// Return whether empty
bool empty() const
{
return m_head == m_tail;
return count() == 0;
}
// Return whether full
bool full() const
{
return m_head == next(m_tail);
return count() == Size;
}
private:
uint8_t m_head;
uint8_t m_tail;
T m_buffer[Size + 1];
// Return the next index
static constexpr uint8_t next(uint8_t n)
{
return (n + 1) % (Size + 1);
}
uint8_t volatile m_head;
uint8_t volatile m_tail;
T m_buffer[Size];
};
#endif // !defined(RINGBUF_HPP)

@ -43,7 +43,7 @@ public:
}
// Transmit a string.
void send(char const * s)
void send_str(char const * s)
{
for (size_t i = 0; s[i]; i++)
send((int)(uint8_t) s[i]);

@ -7,6 +7,7 @@
#include <stddef.h>
#include <inttypes.h>
#include <avrstuff/serial.hpp>
#include <avrstuff/math_misc.hpp>
// Serial interface class for AVR-Dx USART. Does not provide initialization,
// the user should do this first. If using a character size of 9 bits, 9BITH
@ -74,44 +75,45 @@ public:
Usart()->CTRLA &= ~USART_DREIE_bm;
}
// Set the baud rate. This function is templated so it can emit
// compile-time warnings if the baud rate cannot be set adequately.
template<uint32_t Baud>
void set_baud()
// Set the baud rate.
//
// Return: true if the baud rate could be set
constexpr bool set_baud(uint32_t baud)
{
constexpr uint16_t breg_normal =
64.0 * F_CPU / (16.0 * Baud) + 0.5;
constexpr uint16_t breg_2x =
64.0 * F_CPU / (8.0 * Baud) + 0.5;
uint16_t breg_normal =
rounded_div(64uLL * F_CPU, 16uLL * baud);
uint16_t breg_2x =
rounded_div(64uLL * F_CPU, 8uLL * baud);
constexpr double baud_normal =
64.0 * F_CPU / (16.0 * breg_normal);
constexpr double baud_2x =
64.0 * F_CPU / (8.0 * breg_2x);
int32_t baud_normal =
rounded_div(64LL * F_CPU, 16LL * breg_normal);
int32_t baud_2x =
rounded_div(64LL * F_CPU, 16LL * breg_2x);
constexpr double error_normal = baud_normal / Baud;
constexpr double error_2x = baud_2x / Baud;
int32_t error_normal = baud_normal - baud;
int32_t error_2x = baud_2x - baud;
constexpr bool valid_normal =
error_normal >= 0.99 && error_normal <= 1.01;
constexpr bool valid_2x =
error_2x >= 0.99 && error_2x <= 1.01;
static_assert(valid_normal || valid_2x,
"Baud rate out of range, cannot achieve tolerance");
bool valid_normal = error_normal > -(int32_t)baud/100 &&
error_normal < (int32_t)baud/100;
bool valid_2x = error_2x > -(int32_t)baud/100 &&
error_2x < (int32_t)baud/100;
if (valid_normal)
{
Usart()->BAUD = breg_normal;
Usart()->CTRLB = (Usart()->CTRLB & ~USART_RXMODE_gm)
| USART_RXMODE_NORMAL_gc;
return true;
}
else
else if (valid_2x)
{
Usart()->BAUD = breg_2x;
Usart()->CTRLB = (Usart()->CTRLB & ~USART_RXMODE_gm)
| USART_RXMODE_CLK2X_gc;
return true;
}
else
return false;
}
// Stop the USART. Interrupts and the transceiver are disabled. Other

@ -0,0 +1,46 @@
// intellectual property is bullshit bgdc
#ifndef SPI_HPP
#define SPI_HPP
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
// Generic SPI interface class. Generally assumes a non interrupt driven style
// interface.
class spi
{
public:
virtual ~spi() = default;
// Transfer one byte, returning the data received.
virtual uint8_t transfer(uint8_t data) = 0;
// Transfer a packet. Does not manipulate chip selects.
//
// data_out: data to send
// data_in: buffer to receive response. May equal data_out; may be null
// length: number of bytes to send
//
// Implementer: override only if you have a faster approach.
virtual void transfer_block(
uint8_t const * data_out,
uint8_t * data_in,
size_t length
)
{
if (data_in)
{
for (size_t i = 0; i < length; i++)
data_in[i] = transfer(data_out[i]);
}
else
{
for (size_t i = 0; i < length; i++)
transfer(data_out[i]);
}
}
};
#endif // !defined(SPI_HPP)

@ -0,0 +1,154 @@
// intellectual property is bullshit bgdc
#ifndef SPI_AVRDX_HPP
#define SPI_AVRDX_HPP
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <avrstuff/spi.hpp>
#include <avr/io.h>
template<int NSpi>
class spi_avrdx: public spi
{
public:
enum class clockdiv: uint8_t
{
div2 = SPI_PRESC_DIV4_gc | SPI_CLK2X_bm,
div4 = SPI_PRESC_DIV4_gc,
div8 = SPI_PRESC_DIV16_gc | SPI_CLK2X_bm,
div16 = SPI_PRESC_DIV16_gc,
div32 = SPI_PRESC_DIV64_gc | SPI_CLK2X_bm,
div64 = SPI_PRESC_DIV64_gc,
div128 = SPI_PRESC_DIV128_gc,
};
virtual ~spi_avrdx() = default;
virtual uint8_t transfer(uint8_t data) override
{
transfer_block_u8(&data, &data, 1);
return data;
}
void transfer_block_u8(
uint8_t const * data_out,
uint8_t * data_in,
uint8_t length
)
{
Spi()->INTFLAGS = 0xFF;
if (!data_in)
{
// This loop can maintain a 93% transmit duty cycle
// at div/2
for (uint8_t i = 0; i < length; i++)
{
while (!(Spi()->INTFLAGS & SPI_DREIF_bm));
Spi()->DATA = data_out[i];
}
}
else
{
// This loop can maintain a 58% transmit duty cycle
// at div/2. It's not a fully saturated link, but it's
// better than 50% (so higher throughput than running
// one divider lower). If there is any further
// optimization to be had, it's in the middle loop.
uint8_t i_out = 0, i_in = 0, discard = 2;
while (i_out < length && discard)
{
uint8_t flags = Spi()->INTFLAGS;
if (flags & (SPI_RXCIF_bm | SPI_BUFOVF_bm | SPI_TXCIF_bm))
{
(void) Spi()->DATA;
discard--;
}
if (flags & SPI_DREIF_bm)
Spi()->DATA = data_out[i_out++];
}
while (i_out < length)
{
uint8_t flags = Spi()->INTFLAGS;
if (flags & (SPI_RXCIF_bm | SPI_BUFOVF_bm | SPI_TXCIF_bm))
{
uint8_t data = Spi()->DATA;
data_in[i_in++] = data;
}
if (flags & SPI_DREIF_bm)
Spi()->DATA = data_out[i_out++];
}
while (i_in < length)
{
uint8_t flags = Spi()->INTFLAGS;
if (flags & (SPI_RXCIF_bm | SPI_BUFOVF_bm | SPI_TXCIF_bm))
{
uint8_t data = Spi()->DATA;
if (discard)
discard--;
else
data_in[i_in++] = data;
}
}
}
}
virtual void transfer_block(
uint8_t const * data_out,
uint8_t * data_in,
size_t length
) override
{
while (length > 255)
{
transfer_block_u8(data_out, data_in, 255);
data_out += 255;
length -= 255;
if (data_in)
data_in += 255;
}
if (length)
transfer_block_u8(data_out, data_in, length);
}
void init(clockdiv fsck, bool msb_first, uint8_t mode)
{
Spi()->CTRLB = SPI_BUFEN_bm | SPI_BUFWR_bm | SPI_SSD_bm
| (mode & 0x3);
Spi()->INTCTRL = 0;
Spi()->CTRLA = 0
| (msb_first ? 0 : SPI_DORD_bm)
| SPI_MASTER_bm
| (uint8_t) fsck
| SPI_ENABLE_bm
;
// Pump the FIFO so the discards in the transfer loop match up
transfer_block_u8((uint8_t const *) "\0", NULL, 3);
}
SPI_t * Spi() const {
return
#ifdef SPI0
NSpi == 0 ? &SPI0 :
#endif
#ifdef SPI1
NSpi == 1 ? &SPI1 :
#endif
NULL;
};
};
#endif // !defined(SPI_AVRDX_HPP)

@ -35,6 +35,14 @@ void board_pins_init()
PIN_DRIVE_TO(PIN_SD_nCS, 1);
PIN_DRIVE_TO(PIN_nVS, 1);
// Drive other outputs to decent defaults
PIN_DRIVE_TO(PIN_INTMIDI_TX, 1);
PIN_DRIVE_TO(PIN_USBMIDI_TX, 1);
PIN_DRIVE_TO(PIN_MIDI_TX, 1);
PIN_DRIVE_TO(PIN_COPI, 0);
PIN_DRIVE_TO(PIN_SCK, 0);
PINCTRL_FOR(PIN_CIPO) = PORT_PULLUPEN_bm;
// Set the alternates and inversions (we invert usbmidi.rx to allow
// detection of cable disconnects - because the opto is ON by default,
// depowering the USB region will generate a break)
@ -48,6 +56,7 @@ void board_pins_init()
// Set up the clocks
DEBUGPINS_START_INIT_CLOCK();
_PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA,
CLKCTRL_RUNSTDBY_bm |
CLKCTRL_CSUTHF_4K_gc | CLKCTRL_FRQRANGE_24M_gc
| CLKCTRL_ENABLE_bm);

@ -13,7 +13,12 @@ for arg in "$@"; do
meson configure -D"$arg" "$BDIR"
done
meson compile -C "$BDIR"
# TODO this is a shit way to do this
if [[ "$FLASH" -ne 0 ]]; then
meson compile -C "$BDIR" flash
else
meson compile -C "$BDIR"
fi
echo
echo "########################################################################"

@ -1,11 +1,17 @@
#include "board.hpp"
#include "midix.hpp"
#include <avrstuff/serial_avrdx.hpp>
#include <avrstuff/bufserial.hpp>
#include <avrstuff/spi_avrdx.hpp>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <assert.h>
#include <string.h>
static serial_avrdx<2, 8> usbmidi_usart;
static bufserial<63, 63> usbmidi_buf(usbmidi_usart);
static bufserial<64, 64> usbmidi_buf(usbmidi_usart);
static ringbuf<struct midi_message, 4> midi_q;
static midix midi(usbmidi_usart, midi_q);
// Plan: main pulls from an event queue. Events are generated from the MIDI
// receive vectors as well as from other sources (e.g. timers to implement
@ -18,15 +24,61 @@ int main(void)
{
board_pins_init();
usbmidi_usart.set_baud<31250>();
usbmidi_usart.set_baud(31250);
usbmidi_usart.start(true, true, 'N', false);
usbmidi_buf.init();
sei();
spi_avrdx<0> hspi;
hspi.init(spi_avrdx<0>::clockdiv::div2, true, 0);
for (;;)
{
int c = usbmidi_buf.recv();
if (c >= 0)
usbmidi_buf.send(c);
char info[64];
struct midi_message m;
if (!midi_q.pop(m))
continue;
uint8_t cmd = MIDI_CMD(m.command);
uint8_t chan = MIDI_CMD_CHAN(m.command);
switch (cmd)
{
case MIDI_ICMD_NOP: sprintf_P(info, PSTR("I NOP ")); break;
case MIDI_ICMD_SYSEX: sprintf_P(info, PSTR("I SYSEX ")); break;
case MIDI_ICMD_SYSEX_OVERFLOW: sprintf_P(info, PSTR("I SYSEX_OVERFLOW ")); break;
case MIDI_CMD_NOTE_OFF: sprintf_P(info, PSTR("NOTE_OFF [%X]"), chan); break;
case MIDI_CMD_NOTE_ON: sprintf_P(info, PSTR("NOTE_ON [%X]"), chan); break;
case MIDI_CMD_AFTERTOUCH: sprintf_P(info, PSTR("AFTERTOUCH [%X]"), chan); break;
case MIDI_CMD_CC: sprintf_P(info, PSTR("CC [%X]"), chan); break;
case MIDI_CMD_PATCH: sprintf_P(info, PSTR("PATCH [%X]"), chan); break;
case MIDI_CMD_PRESSURE: sprintf_P(info, PSTR("PRESSURE [%X]"), chan); break;
case MIDI_CMD_PITCHBEND: sprintf_P(info, PSTR("PITCHBEND [%X]"), chan); break;
case MIDI_CMD_START_SYSEX: sprintf_P(info, PSTR("START_SYSEX ")); break;
case MIDI_CMD_TUNE_REQ: sprintf_P(info, PSTR("TUNE_REQ ")); break;
case MIDI_CMD_END_SYSEX: sprintf_P(info, PSTR("END_SYSEX ")); break;
case MIDI_CMD_SYSRESET: sprintf_P(info, PSTR("SYSRESET ")); break;
default: sprintf_P(info, PSTR("WTF [%02x]"), m.command); break;
}
usbmidi_buf.send_str(info);
if (cmd == MIDI_ICMD_SYSEX)
{
usbmidi_buf.send_str((char const *) m.content);
hspi.transfer_block(m.content, m.content, strlen((char const *) m.content));
hspi.transfer_block(m.content, NULL, strlen((char const *) m.content));
m.source->sysex_release_buffer(m.content);
}
else
{
for (int i = 0; i < midi_num_args(cmd); i++)
{
sprintf_P(info, PSTR(" %02x"), m.arguments[i]);
usbmidi_buf.send_str(info);
}
}
usbmidi_buf.send_str("\r\n");
}
}
@ -43,5 +95,5 @@ ISR(USART2_DRE_vect)
ISR(USART2_RXC_vect)
{
usbmidi_buf.rxc();
midi.rxc();
}

@ -1,4 +1,4 @@
project('poly1', 'cpp', default_options: ['optimization=2', 'cpp_std=c++17'])
project('poly1', 'cpp', default_options: ['optimization=s', 'cpp_std=c++17'])
atpack_zip = 'Microchip.AVR-Dx_DFP.2.1.152.atpack'
dependencies = []
@ -8,6 +8,7 @@ local_headers = ['.', 'etl/include']
sources = [
'main.cpp',
'board.cpp',
'midix.cpp',
]
_includes = local_headers
@ -17,7 +18,7 @@ _incl_dirs = include_directories(_includes)
if host_machine.cpu_family() != 'avr'
test_midi = executable(
'test_midi',
['test_midi.cpp'],
['test_midi.cpp', 'midix.cpp'],
include_directories: _incl_dirs,
)
subdir_done()
@ -26,12 +27,16 @@ endif
# debug build
if get_option('buildtype') == 'debug'
add_project_arguments(['-DDEBUG=1', '-ggdb3'], language: ['c', 'cpp'])
add_project_link_arguments(['-ggdb3'], language: ['c', 'cpp'])
else
add_project_arguments(['-ggdb3'], language: ['c', 'cpp'])
add_project_link_arguments(['-ggdb3'], language: ['c', 'cpp'])
endif
objcopy = find_program('objcopy')
avrdude = find_program('avrdude', required: false, disabler: true)
objdump = find_program('objdump')
size = find_program('size')
pymcuprog = find_program('pymcuprog', required: false, disabler: true)
atpack_path = meson.build_root() / 'atpack'
sysroot = atpack_path / 'gcc/dev' / host_machine.cpu()
@ -44,6 +49,12 @@ add_project_link_arguments(['-mmcu=' + host_machine.cpu()], language: ['c', 'cpp
add_project_arguments(['-DF_CPU=' + get_option('cpu_freq').to_string()], language: ['c', 'cpp'])
add_project_arguments(['-I' + atpack_path / 'include'], language: ['c', 'cpp'])
add_project_arguments(['-DETL_NO_STL=1'], language: ['c', 'cpp'])
opt_opts = ['-mrelax', '-flto']
add_project_arguments(opt_opts, language: ['c', 'cpp'])
add_project_link_arguments(opt_opts, language: ['c', 'cpp'])
atpack = custom_target(
'atpack',
command: [ 'unzip', '@INPUT@', '-d', '@OUTPUT@' ],
@ -68,24 +79,46 @@ main_hex = custom_target(
],
input: main,
output: meson.project_name() + '.hex',
build_by_default: true
build_by_default: true,
)
main_disas = custom_target(
meson.project_name() + '.disas',
command: [
objdump,
'-SC', '@INPUT@'
],
input: main,
output: meson.project_name() + '.disas',
build_by_default: true,
capture: true,
)
main_size = custom_target(
'size',
build_always_stale: true,
build_by_default: true,
command: [
size, '@INPUT@'
],
input: main,
console: true,
output: 'size',
)
if avrdude.found()
# creates an empty file named 'flash' to get around meson's inability
# to have a target create no outputs.
#
# TODO i don't think this is necessary...
if pymcuprog.found()
flash = custom_target(
'flash',
build_always_stale: true,
command: [
avrdude, '-p', host_machine.cpu(),
'-c', get_option('programmer'),
'-U', 'flash:w:@INPUT@:i'
pymcuprog, 'write', '-d', host_machine.cpu(),
'-t', 'uart', '-u', get_option('programmer'),
'-f', '@INPUT@',
'--erase',
'--verify'
],
input: main_hex,
capture: true,
console: true,
output: 'flash'
)
endif

@ -12,8 +12,11 @@ c = arch + '-gcc'
cpp = arch + '-g++'
strip = arch + '-strip'
objcopy = arch + '-objcopy'
objdump = arch + '-objdump'
size = arch + '-size'
[built-in options]
c_args = []
cpp_args = []
c_link_args = []
c_args = ['-mrelax']
cpp_args = ['-mrelax']
c_link_args = ['-mrelax']
cpp_link_args = ['-mrelax']

@ -9,6 +9,6 @@ option(
option(
'programmer',
type: 'array',
value: ['usbtiny'], #['arduino', '-P', '/dev/ttyACM0'],
value: ['/dev/tty.usbserial-1110'],
description: 'AVR Programmer to flash the MCU with.'
)

@ -0,0 +1,80 @@
// intellectual property is bullshit bgdc
#include "midix.hpp"
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
void midix::rxc()
{
int c = m_serport.recv();
if (c < 0)
{
m_message.command = MIDI_ICMD_NOP;
m_message_pos = 0;
if (m_receiving_sysex)
{
m_receiving_sysex = false;
m_sysex_buf_avail = true;
}
}
else if (m_receiving_sysex)
{
if (c == MIDI_CMD_END_SYSEX)
{
if (m_message_pos < MIDIX_SYSEX_LEN)
m_sysex_buf[m_message_pos] = 0;
else
m_sysex_buf[MIDIX_SYSEX_LEN - 1] = 0;
send();
}
else if (m_message_pos < MIDIX_SYSEX_LEN)
m_sysex_buf[m_message_pos++] = c;
}
else if (c == MIDI_CMD_START_SYSEX)
{
if (m_sysex_buf_avail)
{
m_message.command = MIDI_ICMD_SYSEX;
m_message.content = m_sysex_buf;
m_message_pos = 0;
m_receiving_sysex = true;
m_sysex_buf_avail = false;
}
else
{
m_message.command = MIDI_ICMD_SYSEX_OVERFLOW;
send();
}
}
else if (c >= 0x80)
{
m_message.command = (uint8_t) c;
m_message_pos = 1;
}
else
{
if (m_message_pos <= sizeof(m_message.arguments))
{
m_message.arguments[m_message_pos - 1] = c;
m_message_pos++;
}
if (m_message.command &&
m_message_pos == midi_num_args(m_message.command) + 1)
{
send();
}
}
}
void midix::sysex_release_buffer(uint8_t * buf)
{
assert(!m_sysex_buf_avail);
assert(buf == &m_sysex_buf[0]);
buf[0] = 0;
m_sysex_buf_avail = true;
}

@ -8,16 +8,21 @@
#include <inttypes.h>
#include <assert.h>
#include <etl/queue.h>
#include <etl/queue_spsc_locked.h>
#include <avrstuff/serial.hpp>
#include <avrstuff/ringbuf.hpp>
#include "midi.hpp"
#define MIDIX_SYSEX_LEN 64
// MIDI transceiver
typedef iringbuf<struct midi_message> midi_iqueue;
// MIDI transceiver. MIDI messages are received from the RXC ISR and placed
// into a queue. Outgoing MIDI messages are transmitted from an internal
// queue.
class midix {
public:
midix(serial & serport, etl::iqueue<struct midi_message> & mq)
midix(serial & serport, midi_iqueue & mq)
: m_serport(serport)
, m_sysex_buf_avail(true)
, m_message_pos(0)
@ -29,79 +34,17 @@ public:
}
// Receive Complete interrupt. Call within the serial port's RXC ISR.
void rxc()
{
int c = m_serport.recv();
if (c < 0)
{
m_message.command = MIDI_ICMD_NOP;
m_message_pos = 0;
if (m_receiving_sysex)
{
m_receiving_sysex = false;
m_sysex_buf_avail = true;
}
}
else if (m_receiving_sysex)
{
if (c == MIDI_CMD_END_SYSEX)
{
if (m_message_pos < MIDIX_SYSEX_LEN)
m_sysex_buf[m_message_pos] = 0;
else
m_sysex_buf[MIDIX_SYSEX_LEN - 1] = 0;
send();
}
else if (m_message_pos < MIDIX_SYSEX_LEN)
m_sysex_buf[m_message_pos++] = c;
}
else if (c == MIDI_CMD_START_SYSEX)
{
if (m_sysex_buf_avail)
{
m_message.command = MIDI_ICMD_SYSEX;
m_message.content = m_sysex_buf;
m_message_pos = 0;
m_receiving_sysex = true;
m_sysex_buf_avail = false;
}
else
{
m_message.command = MIDI_ICMD_SYSEX_OVERFLOW;
send();
}
}
else if (c >= 0x80)
{
m_message.command = (uint8_t) c;
m_message_pos = 1;
}
else
{
if (m_message_pos <= sizeof(m_message.arguments))
{
m_message.arguments[m_message_pos - 1] = c;
m_message_pos++;
}
void rxc();
if (m_message.command &&
m_message_pos == midi_num_args(m_message.command) + 1)
{
send();
}
}
}
// Release the sysex buffer
void sysex_release_buffer(uint8_t * buf)
{
assert(!m_sysex_buf_avail);
assert(buf == &m_sysex_buf[0]);
buf[0] = 0;
m_sysex_buf_avail = true;
}
// Release the sysex buffer. When a message is enqueued containing
// sysex content, it contains a reference to this midix's internal
// sysex buffer. This can only be used once, so it must be released
// when no longer needed. If another sysex is received while the buffer
// is held, it is rejected and becomes a MIDI_ICMD_SYSEX_OVERFLOW.
//
// buf: buffer to release. Must be the original buffer given in
// midi_message.content
void sysex_release_buffer(uint8_t * buf);
private:
// Send the current message and initialize it for the next one
@ -120,7 +63,7 @@ private:
struct midi_message m_message;
size_t m_message_pos;
bool m_receiving_sysex;
etl::iqueue<struct midi_message> & m_queue;
midi_iqueue & m_queue;
};
#endif // !defined(MIDIX_HPP)

@ -6,7 +6,7 @@
#include <assert.h>
#include <vector>
#include <etl/queue.h>
#include <etl/queue_spsc_locked.h>
#include <avrstuff/serial.hpp>
#include "midix.hpp"
@ -88,7 +88,7 @@ int main(int argc, char ** argv)
}
serial_test serport;
etl::queue<struct midi_message, 2048> queue;
ringbuf<struct midi_message, 128> queue;
midix m(serport, queue);
for (int i = 0; i < argc; i++)
@ -103,7 +103,7 @@ int main(int argc, char ** argv)
if (!queue.empty())
{
struct midi_message msg;
queue.pop_into(msg);
queue.pop(msg);
print_midi(&msg);
if (msg.command == MIDI_ICMD_SYSEX)
m.sysex_release_buffer(msg.content);

Loading…
Cancel
Save