initial commit
This commit is contained in:
commit
b97c5933b5
5 changed files with 381 additions and 0 deletions
9
LICENSE
Normal file
9
LICENSE
Normal file
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Sascha Nitsch
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
The source code and plans for the Arduino based ["Play "Music" with stepper motors series"](https://contentnation.net/en/grumpydevelop/stepper_1) on Content Nation.
|
199
midi2stepper.cpp
Normal file
199
midi2stepper.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
/// \file midi2stepper.h
|
||||
/// \brief header file for Midi2Stepper class
|
||||
/// \author GrumpyDeveloper https://contentnation.net/en/grumpydevelop
|
||||
/// \license MIT
|
||||
|
||||
#include "midi2stepper.h"
|
||||
#include "pins_arduino.h"
|
||||
|
||||
Midi2Stepper m2s;
|
||||
|
||||
Midi2Stepper::Midi2Stepper() {
|
||||
m_enable = 0;
|
||||
m_counterA = 0;
|
||||
m_counterB = 0;
|
||||
m_counterC = 0;
|
||||
m_counterD = 0;
|
||||
m_counterE = 0;
|
||||
m_counterF = 0;
|
||||
m_counterG = 0;
|
||||
m_counterH = 0;
|
||||
m_cEndA = 0;
|
||||
m_cEndB = 0;
|
||||
m_cEndC = 0;
|
||||
m_cEndD = 0;
|
||||
m_cEndE = 0;
|
||||
m_cEndF = 0;
|
||||
m_cEndG = 0;
|
||||
m_cEndH = 0;
|
||||
}
|
||||
|
||||
void Midi2Stepper::begin() {
|
||||
// set stepper pins to 0 and enable output
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
digitalWrite(i+5, false);
|
||||
pinMode(i+5, OUTPUT);
|
||||
}
|
||||
// set /enable pin to 1 (off)
|
||||
digitalWrite(13, true);
|
||||
pinMode(13, OUTPUT);
|
||||
|
||||
cli(); // disable interrupts
|
||||
TCCR1A = 0; // Set the CTC mode
|
||||
TCCR1B = 0;
|
||||
TCNT1 = 0;
|
||||
OCR1A = 335; // 333 = (16*10^6) / (480000*1) - 1 (must be <65536), adjusted to actual measured frequency
|
||||
TCCR1B |= (1 << WGM12);
|
||||
sei(); // enable interrupt
|
||||
}
|
||||
|
||||
void Midi2Stepper::power(bool status) {
|
||||
cli();
|
||||
TCNT1 = 0;
|
||||
if (status) { // turn on
|
||||
// Set CS10 bit for 1 prescaler
|
||||
TCCR1B |= (1 << CS10);
|
||||
// enable timer compare interrupt
|
||||
TIMSK1 |= (1 << OCIE1A);
|
||||
} else {
|
||||
// Clear CS10 bit for 1 prescaler
|
||||
TCCR1B &= ~(1 << CS10);
|
||||
// enable timer compare interrupt
|
||||
TIMSK1 &= ~(1 << OCIE1A);
|
||||
digitalWrite(5, false);
|
||||
digitalWrite(6, false);
|
||||
digitalWrite(7, false);
|
||||
digitalWrite(8, false);
|
||||
digitalWrite(9, false);
|
||||
digitalWrite(10, false);
|
||||
digitalWrite(11, false);
|
||||
digitalWrite(12, false);
|
||||
}
|
||||
sei();
|
||||
digitalWrite(13, !status);
|
||||
}
|
||||
|
||||
ISR(TIMER1_COMPA_vect) { // timer1 interrupt 40kHz
|
||||
m2s.tick();
|
||||
}
|
||||
|
||||
void Midi2Stepper::tick() {
|
||||
bool bmod = false; // was port b modified
|
||||
bool dmod = false; // was port d modified
|
||||
// create local copies to speed things up
|
||||
uint8_t portb = PORTB;
|
||||
uint8_t portd = PORTD;
|
||||
uint8_t enable = m_enable;
|
||||
|
||||
// for each
|
||||
if (enable & 1) {
|
||||
++m_counterA;
|
||||
if (m_counterA > m_cEndA) {
|
||||
portd ^= (1 << PD5); // toggle D5
|
||||
dmod = true;
|
||||
m_counterA = 0;
|
||||
}
|
||||
}
|
||||
if (enable & 2) {
|
||||
++m_counterB;
|
||||
if (m_counterB > m_cEndB) {
|
||||
portd ^= (1 << PD6); // toggle D6
|
||||
dmod = true;
|
||||
m_counterB = 0;
|
||||
}
|
||||
}
|
||||
if (enable & 4) {
|
||||
++m_counterC;
|
||||
if (m_counterC > m_cEndC) {
|
||||
portd ^= (1 << PD7); // toggle D7
|
||||
dmod = true;
|
||||
m_counterC = 0;
|
||||
}
|
||||
}
|
||||
if (enable & 8) {
|
||||
++m_counterD;
|
||||
if (m_counterD > m_cEndD) {
|
||||
portb ^= (1 << PB0); // toggle D8
|
||||
bmod = true;
|
||||
m_counterD = 0;
|
||||
}
|
||||
}
|
||||
if (enable & 16) {
|
||||
++m_counterE;
|
||||
if (m_counterE > m_cEndE) {
|
||||
portb ^= (1 << PB1); // toggle D9
|
||||
bmod = true;
|
||||
m_counterE = 0;
|
||||
}
|
||||
}
|
||||
if (enable & 32) {
|
||||
++m_counterF;
|
||||
if (m_counterF > m_cEndF) {
|
||||
portb ^= (1 << PB2); // toggle D10
|
||||
bmod = true;
|
||||
m_counterF = 0;
|
||||
}
|
||||
}
|
||||
if (enable & 64) {
|
||||
++m_counterG;
|
||||
if (m_counterG > m_cEndG) {
|
||||
portb ^= (1 << PB3); // toggle D11
|
||||
bmod = true;
|
||||
m_counterG = 0;
|
||||
}
|
||||
}
|
||||
if (enable & 128) {
|
||||
++m_counterH;
|
||||
if (m_counterH > m_cEndH) {
|
||||
portb ^= (1 << PB4); // toggle D12
|
||||
bmod = true;
|
||||
m_counterH = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (bmod)
|
||||
PORTB = portb;
|
||||
if (dmod)
|
||||
PORTD = portd;
|
||||
}
|
||||
|
||||
void Midi2Stepper::setChannel(uint8_t channelNum, uint16_t freq) {
|
||||
uint16_t v = 0xFFFF;
|
||||
if (freq > 0) { // was a frequency set?
|
||||
v = (((uint16_t)24000) / freq) - 1;
|
||||
// channel is turned on later
|
||||
} else {
|
||||
m_enable &= ~(1 << channelNum); // turn off specified channel
|
||||
digitalWrite(5 + channelNum, false); // set output pin to 0
|
||||
}
|
||||
switch (channelNum) {
|
||||
case 0:
|
||||
m_cEndA = v;
|
||||
break;
|
||||
case 1:
|
||||
m_cEndB = v;
|
||||
break;
|
||||
case 2:
|
||||
m_cEndC = v;
|
||||
break;
|
||||
case 3:
|
||||
m_cEndD = v;
|
||||
break;
|
||||
case 4:
|
||||
m_cEndE = v;
|
||||
break;
|
||||
case 5:
|
||||
m_cEndF = v;
|
||||
break;
|
||||
case 6:
|
||||
m_cEndG = v;
|
||||
break;
|
||||
case 7:
|
||||
m_cEndH = v;
|
||||
break;
|
||||
}
|
||||
if (freq > 0) { // was a frequency set?
|
||||
m_enable |= 1 << channelNum; // turn on specified channel
|
||||
}
|
||||
|
||||
}
|
65
midi2stepper.h
Normal file
65
midi2stepper.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/// \file midi2stepper.h
|
||||
/// \brief header file for Midi2Stepper class
|
||||
/// \author GrumpyDeveloper https://contentnation.net/en/grumpydevelop
|
||||
/// \license MIT
|
||||
|
||||
#ifndef MIDI2STEPPER_H_
|
||||
#define MIDI2STEPPER_H_
|
||||
|
||||
// system includes
|
||||
#include "Arduino.h"
|
||||
|
||||
class Midi2Stepper {
|
||||
public:
|
||||
/**
|
||||
* \brief constructor
|
||||
*/
|
||||
Midi2Stepper();
|
||||
|
||||
/**
|
||||
* \brief start up internal structures
|
||||
*/
|
||||
void begin();
|
||||
|
||||
/**
|
||||
* \brief set power state and enable or disable pulse generation
|
||||
*
|
||||
* \param status new power status
|
||||
*/
|
||||
void power(bool status);
|
||||
|
||||
/**
|
||||
* \brief tick function called by interrupt at 48kHz
|
||||
*/
|
||||
void tick();
|
||||
|
||||
/**
|
||||
* \brief set channel A frequency and direction
|
||||
*
|
||||
* \param channelNum channel number
|
||||
* \param freq frequency in Hz
|
||||
*/
|
||||
void setChannel(uint8_t channelNum, uint16_t freq);
|
||||
|
||||
private:
|
||||
uint16_t m_counterA; ///< counter for toggling channels
|
||||
uint16_t m_counterB; ///< counter for toggling channels
|
||||
uint16_t m_counterC; ///< counter for toggling channels
|
||||
uint16_t m_counterD; ///< counter for toggling channels
|
||||
uint16_t m_counterE; ///< counter for toggling channels
|
||||
uint16_t m_counterF; ///< counter for toggling channels
|
||||
uint16_t m_counterG; ///< counter for toggling channels
|
||||
uint16_t m_counterH; ///< counter for toggling channels
|
||||
|
||||
volatile uint8_t m_enable; ///< which channels are enable, one bit per channel
|
||||
volatile uint16_t m_cEndA; ///< toggle output when m_counter reaches this
|
||||
volatile uint16_t m_cEndB; ///< toggle output when m_counter reaches this
|
||||
volatile uint16_t m_cEndC; ///< toggle output when m_counter reaches this
|
||||
volatile uint16_t m_cEndD; ///< toggle output when m_counter reaches this
|
||||
volatile uint16_t m_cEndE; ///< toggle output when m_counter reaches this
|
||||
volatile uint16_t m_cEndF; ///< toggle output when m_counter reaches this
|
||||
volatile uint16_t m_cEndG; ///< toggle output when m_counter reaches this
|
||||
volatile uint16_t m_cEndH; ///< toggle output when m_counter reaches this
|
||||
};
|
||||
|
||||
#endif // MIDI2STEPPER_H_
|
107
midi2stepper.ino
Normal file
107
midi2stepper.ino
Normal file
|
@ -0,0 +1,107 @@
|
|||
/// \file midi2stepper.ino
|
||||
/// \brief main routine for midi to stepper tool
|
||||
/// \author GrumpyDeveloper https://contentnation.net/en/grumpydevelop
|
||||
/// \license MIT
|
||||
|
||||
#include <EEPROM.h>
|
||||
#include "midi2stepper.h"
|
||||
|
||||
extern Midi2Stepper m2s;
|
||||
|
||||
int numChannels; ///< number of channels
|
||||
uint16_t channels[8]; ///< channel frequencies
|
||||
uint8_t msg[4]; ///< input messages over serial
|
||||
uint8_t nextChannel = 0; ///< next channel to test
|
||||
bool power; ///< current power status
|
||||
bool sendStatus = true; ///< send current output over serial
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
m2s.begin();
|
||||
numChannels = EEPROM.read(0); // read number of channels from eeprom
|
||||
if (numChannels < 1 || numChannels > 8) { // unreasonable value
|
||||
numChannels = 8;
|
||||
}
|
||||
memset(channels, 0, sizeof(uint16_t) * numChannels); // clear channel frequencies
|
||||
power = false; // power is off
|
||||
pinMode(LED_BUILTIN, OUTPUT); // set LED pin to output
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while (Serial.available()) {
|
||||
Serial.readBytes(msg, 3);
|
||||
uint8_t nibble = msg[0] >> 4;
|
||||
if (nibble == 0b1001) { // note on
|
||||
if (!power) { // not powered on yet?
|
||||
// enable steppers
|
||||
power = true;
|
||||
m2s.power(true);
|
||||
}
|
||||
// calculate frequency from midi note
|
||||
uint16_t freq = pow(2, (static_cast<double>(msg[1]) - 69.0)/12.0) * 440.0;
|
||||
// velocity byte 2 is ignored
|
||||
// loop through channels, starting with nextChannel to find the next free one
|
||||
uint8_t channel = nextChannel;
|
||||
bool good = false;
|
||||
do {
|
||||
if (channels[channel] == 0) {
|
||||
m2s.setChannel(channel, freq);
|
||||
channels[channel] = freq;
|
||||
nextChannel = (channel + 1) % (numChannels); // next channel = current channel + 1 modulo num channels
|
||||
good = true;
|
||||
break;
|
||||
}
|
||||
channel = (channel + 1) % numChannels;
|
||||
} while (channel != nextChannel); // if we end at our start channel, no free channels, ignore note
|
||||
if (!good && sendStatus) {
|
||||
Serial.print(F("\ntoo many channels\n"));
|
||||
}
|
||||
}
|
||||
if (nibble == 0b1000) { // note off
|
||||
uint16_t freq = pow(2, (static_cast<double>(msg[1]) - 69.0)/12.0) * 440.0;
|
||||
// velocity byte 2 is ignored
|
||||
// loop through channels to find an active one with that frequency
|
||||
for (uint8_t channel = 0; channel < numChannels; ++channel) {
|
||||
if (channels[channel] == freq) {
|
||||
m2s.setChannel(channel, 0);
|
||||
channels[channel] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nibble == 0b1011) { // control
|
||||
switch (msg[1]) {
|
||||
case 121: // reset
|
||||
case 123: // all notes off
|
||||
if (power) {
|
||||
power = false;
|
||||
m2s.power(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// hack to set number of channels via simple usb connection:
|
||||
// send <number of channel>\r\n
|
||||
if (msg[0] > '0' && msg[0] < '9') {
|
||||
numChannels = msg[0] - '0';
|
||||
Serial.print(F("channel count set to "));
|
||||
Serial.println(numChannels);
|
||||
// clear channel frequencies
|
||||
memset(channels, 0, sizeof(uint16_t) * numChannels);
|
||||
// save new value to EEPROM
|
||||
EEPROM.write(0, numChannels);
|
||||
}
|
||||
if (sendStatus) {
|
||||
// send current status over serial connection
|
||||
char buf[8]; ///< status message buffer
|
||||
buf[0] = power ? '+' : '-';
|
||||
buf[1] = ' ';
|
||||
Serial.write(buf, 2);
|
||||
for (uint8_t i = 0; i < numChannels; ++i) {
|
||||
uint8_t len = snprintf(buf, sizeof(buf) - 1, "%5i ", channels[i]);
|
||||
Serial.write(buf, len);
|
||||
}
|
||||
Serial.write('\r');
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue