Browse Source

add initial support for GPIO gate pulses

main^2
cinnaboot 3 years ago
parent
commit
ca4b657e4b
  1. 2
      Makefile
  2. 103
      src/main.cpp
  3. 21
      src/midi.cpp
  4. 9
      src/midi.h
  5. 52
      src/spi.cpp
  6. 19
      src/spi.h

2
Makefile

@ -1,7 +1,7 @@
CXX=g++ CXX=g++
CXXFLAGS= -std=c++11 -g -ggdb3 -Wall CXXFLAGS= -std=c++11 -g -ggdb3 -Wall
LDFLAGS=-lasound LDFLAGS=-lasound -lgpiod
OBJDIR = build OBJDIR = build
SRCDIR = src SRCDIR = src

103
src/main.cpp

@ -1,14 +1,24 @@
// TODO: update README with wiring instructions for SPI to DAC
// TODO: DAC voltage stepping
// if we use DAC input 4080 = 5V, then we can do steps of 1/12V on exactly
// the correct DAC value. Would then need to set Vref to:
// DAC: 5V = 4080 / 4095 * Vref, Vref ~= 5.0184
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <unistd.h> #include <unistd.h>
#include <linux/spi/spi.h> #include <linux/spi/spi.h>
#include "spi.h" #include "spi.h" // FIXME: should rename to gpio.h
#include "midi.h" #include "midi.h"
#define GPIO_CHIP_NAME "gpiochip1"
#define GPIO_LINE_NUM 82 // GPIO Pin #38 on 'le-potato'
#define SPI_SPEED 1000000 #define SPI_SPEED 1000000
#define SPI_BUFFER_LENGTH 2 #define SPI_BUFFER_LENGTH 2
#define SPI_CHANNEL 0 #define SPI_CHANNEL 0
@ -17,7 +27,13 @@
#define MIDI_NOTE_OFFSET 24 #define MIDI_NOTE_OFFSET 24
#define MIDI_NOTE_LENGTH 61 #define MIDI_NOTE_LENGTH 61
// NOTE: the max output from the MCP4921 DAC is 5.5V, but we have enough DAC
// resolution to use a voltage amplifier with a gain of 2 to extend the range
// to a full 10V. this gives us a range of 121 notes, most of the full MIDI
// range
#define USE_10V_MAX_CV true
#define MAX_DAC_INPUT 4095 // 2^12 - 1 #define MAX_DAC_INPUT 4095 // 2^12 - 1
#define TEST_DELAY 5 // seconds to sleep in the test loop
void void
@ -26,8 +42,8 @@ showUsage(const char* prog_name)
printf("Usage: %s [options]\n", prog_name); printf("Usage: %s [options]\n", prog_name);
printf(" options:\n"); printf(" options:\n");
printf(" -t: cycle test voltages on DAC\n"); printf(" -t: cycle test voltages on DAC\n");
printf(" -l: listen for midi events\n");
printf(" -k sequencer-port: attempt to connect to port \n"); printf(" -k sequencer-port: attempt to connect to port \n");
printf(" -f midi-file: play the first track, first channel of a MIDI file\n");
printf(" -h: show this help \n"); printf(" -h: show this help \n");
printf(" example:\n"); printf(" example:\n");
printf(" %s -k 20:0\n", prog_name); printf(" %s -k 20:0\n", prog_name);
@ -37,32 +53,92 @@ showUsage(const char* prog_name)
u32 u32
convertNoteToDACInput(u8 midi_note) convertNoteToDACInput(u8 midi_note)
{ {
#if USE_10V_MAX_CV
u32 dac = midi_note * 68 / 2;
#else
// NOTE: assuming // NOTE: assuming
// DAC: 5V = 4080 / 4095 * Vref, Vref ~= 5.0184 // DAC: 5V = 4080 / 4095 * Vref, Vref ~= 5.0184
// 1 half step/note = 1/12V = dac 68 // 1 half step/note = 1/12V = dac 68
// 1 octave = 1V = dac 816 // 1 octave = 1V = dac 816
u32 dac = midi_note * 68; u32 dac = midi_note * 68;
#endif
(dac > MAX_DAC_INPUT) ? dac = MAX_DAC_INPUT : dac; (dac > MAX_DAC_INPUT) ? dac = MAX_DAC_INPUT : dac;
return dac; return dac;
} }
void void
loopMIDI(alsa_sequencer* seq, spi_connection* spi) loopMIDI(alsa_sequencer* seq, spi_connection* spi, gpio_info* gpio)
{ {
// TODO: there's probably a more elegant way to handle this, but this was
// the first idea that came up, and seems to work
// NOTE: MIDI Note '0' is a vaild note, but 255 is not. So, we'll use 255
// as a placeholder to represent an unused PLAYING_NOTE
static const u32 MAX_NOTES = 6;
static u8 PLAYING_NOTES[MAX_NOTES] = {255,255,255,255,255,255};
while(1) while(1)
{ {
printf("\n--------------------\n"); printf("\n--------------------\n");
snd_seq_event_t *ev = nullptr; snd_seq_event_t *ev = nullptr;
snd_seq_event_input(seq->seq_handle, &ev); snd_seq_event_input(seq->seq_handle, &ev);
i32 note = midiProcess(ev);
if (note >= 0) { // FIXME: we should only be processing notes from the first channel
i32 dac_in = convertNoteToDACInput(note); alsa_note_event nev = midiProcess(ev);
bool note_tracked = false;
for (u32 i = 0; i < MAX_NOTES; i++) {
if (PLAYING_NOTES[i] == nev.note) {
note_tracked = true;
if (nev.note_on) { // do nothing
break;
} else { // remove note from tracked list
assert(gpioSetValue(gpio, 0)); // send set gate off
PLAYING_NOTES[i] = 255;
break;
}
}
}
// send note voltage to DAC, and add note to tracked list
if (!note_tracked && nev.note_on ) {
for (u32 i = 0; i < MAX_NOTES; i++) {
if (PLAYING_NOTES[i] == 255) {
PLAYING_NOTES[i] = nev.note;
assert(gpioSetValue(gpio, 1)); // send set gate on
break;
}
}
i32 dac_in = convertNoteToDACInput(nev.note);
// TODO: could check return value here for SPI error // TODO: could check return value here for SPI error
spiSendVoltage(spi, dac_in); spiSendVoltage(spi, dac_in);
} }
printf("PLAYING_NOTES: %d,%d,%d,%d,%d,%d\n",
PLAYING_NOTES[0],
PLAYING_NOTES[1],
PLAYING_NOTES[2],
PLAYING_NOTES[3],
PLAYING_NOTES[4],
PLAYING_NOTES[5]);
}
}
void
loopTestVoltages(spi_connection* spi)
{
u32 one_volt = 408;
for (u32 i = 0;; i++) {
#if 0
u32 volts = (i % 2 == 0) ? 0 : one_volt;
spiSendVoltage(spi, volts);
#else
spiSendVoltage(spi, 10 * one_volt);
#endif
sleep(TEST_DELAY);
} }
} }
@ -79,6 +155,11 @@ main(i32 argc, char* argv[])
u8 spi_buf[SPI_BUFFER_LENGTH] = {0}; u8 spi_buf[SPI_BUFFER_LENGTH] = {0};
spi_connection spi = spiInit(SPI_SPEED, MAX_DAC_INPUT, spi_buf, SPI_BUFFER_LENGTH, SPI_FLAGS, SPI_CHANNEL); spi_connection spi = spiInit(SPI_SPEED, MAX_DAC_INPUT, spi_buf, SPI_BUFFER_LENGTH, SPI_FLAGS, SPI_CHANNEL);
gpio_info gpio = {0};
if (!gpioInit(&gpio, GPIO_CHIP_NAME, GPIO_LINE_NUM))
return 1;
if (spi.fd < 0) { if (spi.fd < 0) {
printf("error opening SPI device\n"); printf("error opening SPI device\n");
return 1; return 1;
@ -87,10 +168,10 @@ main(i32 argc, char* argv[])
if (!midiOpen(&seq)) if (!midiOpen(&seq))
return 1; return 1;
while ((opt = getopt(argc, argv, "tk:f:h")) != -1) { while ((opt = getopt(argc, argv, "tlk:h")) != -1) {
switch (opt) { switch (opt) {
case 'f': case 'l':
printf("TODO: parse and play sequence from midi file\n"); loopMIDI(&seq, &spi, &gpio);
break; break;
case 'k': case 'k':
if (!midiConnect(&seq, optarg)) { if (!midiConnect(&seq, optarg)) {
@ -98,10 +179,10 @@ main(i32 argc, char* argv[])
exit(1); exit(1);
} }
loopMIDI(&seq, &spi); loopMIDI(&seq, &spi, &gpio);
break; break;
case 't': case 't':
printf("TODO: cycle test voltages to SPI DAC\n"); loopTestVoltages(&spi);
break; break;
case 'h': case 'h':
default: default:

21
src/midi.cpp

@ -63,26 +63,31 @@ midiConnect(alsa_sequencer* seq, const char* midi_device)
return false; return false;
} }
i32 alsa_note_event
midiProcess(const snd_seq_event_t* ev) midiProcess(const snd_seq_event_t* ev)
{ {
assert(ev != nullptr); assert(ev != nullptr);
i32 note = -1; alsa_note_event nev = {0};
if((ev->type == SND_SEQ_EVENT_NOTEON) || if((ev->type == SND_SEQ_EVENT_NOTEON) ||
(ev->type == SND_SEQ_EVENT_NOTEOFF)) (ev->type == SND_SEQ_EVENT_NOTEOFF))
{ {
const char *type = (ev->type==SND_SEQ_EVENT_NOTEON) ? "on " : "off"; const char *type = (ev->type==SND_SEQ_EVENT_NOTEON) ? "on " : "off";
printf("[%d] Note %s: %d, 0x%2x vel(%2x)\n", printf("[%d] Note %s: %d, 0x%2x vel(%2x), channel: %d\n",
ev->time.tick, type, ev->time.tick,
type,
ev->data.note.note, ev->data.note.note,
ev->data.note.note, ev->data.note.note,
ev->data.note.velocity); ev->data.note.velocity,
note = ev->data.note.note; ev->data.note.channel);
nev.note = ev->data.note.note;
nev.channel = ev->data.note.channel;
nev.note_on = (ev->type == SND_SEQ_EVENT_NOTEON) ? true : false;
} }
else if(ev->type == SND_SEQ_EVENT_CONTROLLER) else if(ev->type == SND_SEQ_EVENT_CONTROLLER)
{ {
printf("[%d] Control: %2x val(%2x)\n", printf("[%d] Control: 0x%2x val(%2x)\n",
ev->time.tick, ev->time.tick,
ev->data.control.param, ev->data.control.param,
ev->data.control.value); ev->data.control.value);
@ -92,7 +97,7 @@ midiProcess(const snd_seq_event_t* ev)
printf("[%d] Unknown: Unhandled Event Received\n", ev->time.tick); printf("[%d] Unknown: Unhandled Event Received\n", ev->time.tick);
} }
return note; return nev;
} }
i32 i32

9
src/midi.h

@ -14,11 +14,18 @@ struct alsa_sequencer
snd_seq_addr_t dest; snd_seq_addr_t dest;
}; };
struct alsa_note_event
{
u8 note;
u8 channel;
bool note_on;
};
bool midiOpen(alsa_sequencer* seq); bool midiOpen(alsa_sequencer* seq);
bool midiConnect(alsa_sequencer* seq, const char* midi_device); bool midiConnect(alsa_sequencer* seq, const char* midi_device);
i32 midiProcess(const snd_seq_event_t* ev); alsa_note_event midiProcess(const snd_seq_event_t* ev);
i32 midiClose(alsa_sequencer* seq); i32 midiClose(alsa_sequencer* seq);

52
src/spi.cpp

@ -1,6 +1,7 @@
#include <cassert> #include <cassert>
#include <cstdio> #include <cstdio>
#include <cstring>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <linux/types.h> #include <linux/types.h>
@ -9,6 +10,57 @@
#include "spi.h" #include "spi.h"
// GPIO
bool
gpioInit(gpio_info* gpio, const char* chip_name, u32 line_num)
{
strncpy(gpio->chip_name, chip_name, sizeof(gpio->chip_name));
gpio->line_num = line_num;
gpio->chip = gpiod_chip_open_by_name(gpio->chip_name);
if (!gpio->chip) {
printf("Error opening chip, %s\n", gpio->chip_name);
return false;
}
gpio->line = gpiod_chip_get_line(gpio->chip, line_num);
if (!gpio->line) {
printf("Error opening line, chip: %s, line: %d\n", gpio->chip_name, line_num);
gpiod_chip_close(gpio->chip);
return false;
}
i32 ret = gpiod_line_request_output(gpio->line, "Consumer", 0);
if (ret < 0) {
printf("Error setting line output, chip: %s, line: %d\n", gpio->chip_name, line_num);
gpiod_line_release(gpio->line);
gpiod_chip_close(gpio->chip);
return false;
}
return true;
}
bool
gpioSetValue(gpio_info* gpio, u32 value)
{
assert(gpio);
i32 ret = gpiod_line_set_value(gpio->line, value);
if (ret < 0) {
printf("Error setting line value, chip: %s, line: %d\n", gpio->chip_name, gpio->line_num);
return false;
}
return true;
}
// SPI
i32 open_device(u8 channel, u32 speed, u8 mode_flags); i32 open_device(u8 channel, u32 speed, u8 mode_flags);

19
src/spi.h

@ -3,10 +3,28 @@
#include <linux/spi/spi.h> #include <linux/spi/spi.h>
#include <linux/spi/spidev.h> #include <linux/spi/spidev.h>
#include <gpiod.h>
#include "types.h" #include "types.h"
// GPIO
struct gpio_info
{
char chip_name[16];
u32 line_num;
gpiod_chip* chip;
gpiod_line* line;
};
bool gpioInit(gpio_info* gpio, const char* chip_name, u32 line_num);
bool gpioSetValue(gpio_info* gpio, u32 value);
// SPI
struct spi_connection struct spi_connection
{ {
i32 fd; i32 fd;
@ -17,7 +35,6 @@ struct spi_connection
u8* buffer; u8* buffer;
}; };
spi_connection spiInit(u32 speed, u32 max_dac_input, u8* buf, u32 buf_len, u8 flags, u8 channel); spi_connection spiInit(u32 speed, u32 max_dac_input, u8* buf, u32 buf_len, u8 flags, u8 channel);
bool spiWrite(i32 fd, u32 speed, u8* buf, u32 buf_len); bool spiWrite(i32 fd, u32 speed, u8* buf, u32 buf_len);

Loading…
Cancel
Save