initial commit

master
Sascha Nitsch 2022-01-05 19:17:52 +01:00
commit b97c5933b5
5 changed files with 381 additions and 0 deletions

9
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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');
}
}
}