Buonasera a tutti.
Sto lavorando su uno Step sequencer midi e sto implementando il codice che attacco a seguire per poter implementare un "tap tempo" (premendo più volte un pulsante viene calcolato un numero di bpm che uso per distanziare gli step).
Il codice è un found, ho eliminato parti che non mi servivano (uscita midi e segnale di clock in tensione in uscita).
La mia domanda è la seguente :
Il tutto funziona e si implementa con il codice dello Step sequencer ma presenta una piccola anomalia.
Lui legge almeno 4 pressioni di un tasto prima di fare il calcolo e aggiornare la variabile "bpm" solo che se le pressioni sono molto rapide il calcolo viene eseguito molto velocemente, se sono lente il calcolo è come se venisse eseguito più lentamente.(il tutto comunque fa aggiornare "bpm" con un tempo tutto suo.
La mia necessità sarebbe : se premo 4 volte il pulsante lo sketch dovrebbe aggiornare "bpm" in synch con il tempo che ho battuto (esempio: 4 tap, lui mi fa una media dei millisecondi e mi aggiorna bpm a 80) ma il tempo che ci mette ad aggiornare la variabile vorrei che fosse uguale ai bpm che mi ha rilevato.
Quindi: TAP / x Millis TAP / x Millis TAP / x Millis TAP /x Millis..... Ed entro il valore di bpm mi dovrebbe restituire "Bpm equivale ad un valore" (cosa che ora fa ma con tempi tutti suoi, costringendomi ad eseguire un resynch sullo Step sequencer).
#include <TimerOne.h>
/*
* For all features: if you do not define the pin, the feature will be disabled!
*/
/*
* FEATURE: TAP BPM INPUT
*/
#define TAP_PIN 32
#define TAP_PIN_POLARITY RISING
#define MINIMUM_TAPS 4
#define EXIT_MARGIN 150 // If no tap after 150% of last tap interval -> measure and set
/*
* FEATURE: DIMMER BPM INPUT
*/
#define DIMMER_INPUT_PIN 35
#define DIMMER_CHANGE_MARGIN 10 // Big value to make sure this doesn't interfere. Tweak as needed.
#define BLINK_OUTPUT_PIN 31
#define BLINK_PIN_POLARITY 0 // 0 = POSITIVE, 255 - NEGATIVE
#define BLINK_TIME 4 // How long to keep LED lit in CLOCK counts (so range is [0,24])
/*
* FEATURE: EEPROM BPM storage
*/
#define EEPROM_ADDRESS 0 // Where to save BPM
#ifdef EEPROM_ADDRESS
#include <EEPROM.h>
#endif
/*
* GENERAL PARAMETERS
*/
#define CLOCKS_PER_BEAT 24
#define MINIMUM_BPM 100 // Used for debouncing
#define MAXIMUM_BPM 4000 // Used for debouncing
long intervalMicroSeconds;
int bpm; // BPM in tenths of a BPM!!
boolean initialized = false;
long minimumTapInterval = 60L * 1000 * 1000 * 10 / MAXIMUM_BPM;
long maximumTapInterval = 60L * 1000 * 1000 * 10 / MINIMUM_BPM;
volatile long firstTapTime = 0;
volatile long lastTapTime = 0;
volatile long timesTapped = 0;
volatile int blinkCount = 0;
int lastDimmerValue = 0;
boolean playing = false;
long lastStartStopTime = 0;
void setup(){
//Serial.begin(9600);
// Set MIDI baud rate:
// Set pin modes
#ifdef BLINK_OUTPUT_PIN
pinMode(BLINK_OUTPUT_PIN, OUTPUT);
#endif
#ifdef DIMMER_INPUT_PIN
pinMode(DIMMER_INPUT_PIN, INPUT);
#endif
#ifdef EEPROM_ADDRESS
// Get the saved BPM value from 2 stored bytes: MSB LSB
bpm = EEPROM.read(EEPROM_ADDRESS) << 8;
bpm += EEPROM.read(EEPROM_ADDRESS + 1);
if (bpm < MINIMUM_BPM || bpm > MAXIMUM_BPM) {
bpm = 1200;
}
#endif
#ifdef TAP_PIN
// Interrupt for catching tap events
attachInterrupt(digitalPinToInterrupt(TAP_PIN), tapInput, TAP_PIN_POLARITY);
#endif
#ifdef DIMMER_INPUT_PIN
// Initialize dimmer value
lastDimmerValue = analogRead(DIMMER_INPUT_PIN);
#endif
}
void tapInput() {
long now = micros();
if (now - lastTapTime < minimumTapInterval) {
return; // Debounce
}
if (timesTapped == 0) {
firstTapTime = now;
}
timesTapped++;
lastTapTime = now;
Serial.println("Tap!");
}
void startOrStop() {
if (!playing) {
Serial.println("Start playing");
} else {
Serial.println("Stop playing");
}
playing = !playing;
}
void sendClockPulse() {
// Write the timing clock byte
blinkCount = (blinkCount + 1) % CLOCKS_PER_BEAT;
if (blinkCount == 0) {
// Turn led on
#ifdef BLINK_OUTPUT_PIN
analogWrite(BLINK_OUTPUT_PIN, 255 - BLINK_PIN_POLARITY);
#endif
} else {
#ifdef BLINK_OUTPUT_PIN
if (blinkCount == BLINK_TIME) {
// Turn led on
analogWrite(BLINK_OUTPUT_PIN, 0 + BLINK_PIN_POLARITY);
}
#endif
}
}
void updateBpm(long now) {
// Update the timer
long interval = calculateIntervalMicroSecs(bpm);
Timer1.setPeriod(interval);
#ifdef EEPROM_ADDRESS
// Save the BPM in 2 bytes, MSB LSB
EEPROM.write(EEPROM_ADDRESS, bpm / 256);
#endif
Serial.print("Set BPM to: ");
Serial.print(bpm / 10);
}
long calculateIntervalMicroSecs(int bpm) {
// Take care about overflows!
return 60L * 1000 * 1000 * 10 / bpm / CLOCKS_PER_BEAT;
}
void loop()
{
long now = micros();
#ifdef TAP_PIN
/*
* Handle tapping of the tap tempo button
*/
if (timesTapped > 0 && timesTapped < MINIMUM_TAPS && (now - lastTapTime) > maximumTapInterval) {
// Single taps, not enough to calculate a BPM -> ignore!
// Serial.println("Ignoring lone taps!");
timesTapped = 0;
} else if (timesTapped >= MINIMUM_TAPS) {
long avgTapInterval = (lastTapTime - firstTapTime) / (timesTapped - 1);
if ((now - lastTapTime) > (avgTapInterval * EXIT_MARGIN / 100)) {
bpm = 60L * 1000 * 1000 * 10 / avgTapInterval;
updateBpm(now);
// Update blinkCount to make sure LED blink matches tapped beat
blinkCount = ((now - lastTapTime) * 24 / avgTapInterval) % CLOCKS_PER_BEAT;
timesTapped = 0;
}
}
#endif
/*#ifdef DIMMER_INPUT_PIN
int curDimValue = analogRead(DIMMER_INPUT_PIN);
if (curDimValue > lastDimmerValue + DIMMER_CHANGE_MARGIN
|| curDimValue < lastDimmerValue - DIMMER_CHANGE_MARGIN) {
// We've got movement!!
bpm = map(curDimValue, 0, 1023, MINIMUM_BPM, MAXIMUM_BPM);
updateBpm(now);
lastDimmerValue = curDimValue;
}
#endif*/
}
Sto lavorando con una teensy 3.5 ma ovviamente con l'ide Arduino.
So che la spiegazione è un po' contorta, se c'è bisogno sono qui per chiarire ogni dubbio.
Grazie mille
tu usi la costante MAXIMUM per definire il "minimum" e MINIMUM per il "maximum"?? Secondo me questo ti fa sballare i calcoli.
Poi ti segnalo che tutte le variabili che trattano millis() e micros() come now, lastTapTime ed altri, devono essere "unsigned long" non solo "long"
Inoltre nelle ISR (funzioni agganciate agli interrupt) bisogna limitarsi a fare pochissime cose, ad esempio evita soprattutto di scrivere sulla seriale!
Infine: ma per calcolare i tempi ti servono realmente i microsecondi? Se devi calcolare le battute al minuto (che in genere à un intero) bastano i millisecondi...
E poi ti consiglio di lasciar perdere il debounce software, metti ste due resistenzine e condensatore e risolvi decentemente.
PS: so che hai detto di aver trovato questo codice, ma ti prego, riformatta il codice in modo che si legga decentemente! Ti basta premere Ctrl-T nell'IDE...
Il codice è un "found"?...
File Ostico Unico Non Documentato?...
o è una brutale italianizzazione del verbo inglese "to found" impropriamente sostantivato?...
docdoc:
tu usi la costante MAXIMUM per definire il "minimum" e MINIMUM per il "maximum"?? Secondo me questo ti fa sballare i calcoli.
I bpm sono una frequenza; se l'intervallo è un periodo, è corretto.
I microsecondi sicuramente non servono, perché anche con 4000bpm si parla di 15ms, quindi 1ms già garantisce una risoluzione del 7% circa. Inoltre, mi sembra di capire che serve solo per l'anti rimbalzo, che mi sembra una complicazione inutile in quanto può essere sostituito da un condensatore ed eventualmente una resistenza senza complicare il codice. In una produzione in serie permette di risparmiare due spiccioli, ma in queste cose è una complicazione inutile. Eventualmente si può aggiungere alla fine, quando tutto funziona perfettamente e, se si manifesta un problema, si sa già che dipende dall'anti rimbalzo software, quindi si va a correggere quello.
Poi leggo:
// Save the BPM in 2 bytes, MSB LSB
EEPROM.write (EEPROM_ADDRESS, bpm/256);
Scrive solo l'MSB... Non vedo il resto della divisione!
Inoltre, scrivi sulla EEPROM ogni volta che premi il pulsante? Poverina...
Confermo che la scrittura sulla seriale nell'ISR è da evitare. In questo caso potrebbe essere un discreto anti rimbalzo, ma abituati a non farlo. Non so se può avere effetti sulla precisione di millis()... dipende dalle priorità.
Datman:
I bpm sono una frequenza; se l'intervallo è un periodo, è corretto.
Ehm, guarda bene come usa quelle variabili...
I microsecondi sicuramente non servono, perché anche con 4000bpm si parla di 15ms, quindi 1ms già garantisce una risoluzione del 7% circa.
Esatto. E se consideri che in genere i bpm vanno fino a 200 o poco più, abbiamo intervalli che vanno da circa 250 ms in su quindi la precisione al millisecondo è irrilevante. Inoltre parliamo di "tap" ossia "un umano che preme i pulsanti" e non credo proprio che abbia una precisione tale da influire sul valore dei bpm (considera che una differenza di 1 bpm significa 60 ms di differenza...).
Inoltre, mi sembra di capire che serve solo per l'anti rimbalzo, che mi sembra una complicazione inutile in quanto può essere sostituito da un condensatore ed eventualmente una resistenza senza complicare il codice.
E io che ho detto?
Poi leggo:
Scrive solo l'MSB... Non vedo il resto della divisione!
Inoltre, scrivi sulla EEPROM ogni volta che premi il pulsante? Poverina...
Ah, si, questo non l'avevo notato!
Per l'OP: le EEPROM hanno un certo numero massimo (statistico) di scritture entro il quale sono affidabili, per cui evita di scrivere sulla EEPROM così spesso, scrivi solamente quando effettivamente hai necessità di salvare un dato (es. solo se l'utente preme un tasto di conferma finale, non ad ogni variazione!) e se questo è effettivamente cambiato (inutile riscrivere lo stesso valore già memorizzato).
La cosa migliore, dato che mentre Arduino è in funzione le variabili mantengono i dati, è scrivere sulla EEPROM solo quando Arduino viene spento, ma ciò richiede un minimo di elettronica aggiuntiva. Io l'ho fatto con un interruttore che, anziché stare direttamente sull'alimentazione, comanda un MOSFET con un RC sul gate: quando l'interruttore viene spento, immediatamente parte la scrittura su EEPROM e dopo circa mezzo secondo il MOSFET spegne tutto.
E io che ho detto?
Scusa, ma non vedo dove dici che MAXIMUM e MINIMUM servono solo per l'antirimbalzo...
A proposito di quelle variabili: a prescindere da come le usa, se fa 1/x una frequenza diventa un periodo e la massima frequenza ha il minimo periodo e viceversa.
E io che ho detto?
Scusa, ma non vedo dove dici che MAXIMUM e MINIMUM servono solo per l'antirimbalzo...
Si ma io mi riferisco a "mi sembra una complicazione inutile in quanto può essere sostituito da un condensatore ed eventualmente una resistenza", dato che anche io gli ho detto di usare un debounce hardware (" ti consiglio di lasciar perdere il debounce software, metti ste due resistenzine e condensatore e risolvi decentemente"). Non parlavo del debounce software.
Per quelle due variabili, chiedevo perché avesse definito le costanti MINIMUM e MAXIMUM che poi va ad usare al contrario per calcolare maximum e minimum.
Ma dopo averlo scritto mi sono accorto che le costanti le usava al denominatore quindi è solo il nome che traeva in inganno (tra l'altro usa nomi logorroici per i miei gusti, userei i prefissi "max" e "min" ), in fondo l'avevo premesso che "Non ho ancora analizzato bene il tuo codice"
Comunque sia, oltre all'uso di debounce hardware credo che alcune semplificazioni sarebbero molto utili, come usare millis() e non micros(), ed in fondo non mi pare che sia necessario usare gli interrupt per contare la distanza tra due "tap"...
Grazie a tutti per le risposte, scusate ma sono giorni che non consulto il forum.
Allora:
Lo sketch l'ho trovato ed era l'unico che mi soddisfaceva.
Il problema principale è che sono un pirla e ho ovviamente incollato una versione leggermente più vecchia, quindi scusate, domani (sabato) posterò il più recente.
Intanto eseguiro' alcune modifiche suggerite, quindi togliendo la scrittura sull'eprom (sono un po' asino e pensavo fosse vitale per il funzionamento), modificando in unsigned long le variabili necessarie e provando a sostituire i micos con i Millis.
(la scrittura parziale nel codice è un refuso, prima scriveva in 2 allocazioni mettendomi il decimale ma non mi servivano valori con la virgola).
Altra cosa, debounce hardware e non software.
Per docdoc, ho bisogno che i bpm salgano anche a 500 o 600, lo step sequencer lo utilizzerò per un software da vj (resolume arena) e spesso ho bisogno di seguire i "drop" della musica, infatti ho 2 pulsanti che ad ogni pressione mi raddoppiano o mi dimezza o i bpm.
Per quanto riguarda l'interrupt... Intanto toglierò la scrittura su seriale e se tutte le altre modifiche andranno a buon fine cercherò, col vostro aiuto, di trasformarlo.
Datman:
Il codice è un "found"?...
File Ostico Unico Non Documentato?...
o è una brutale italianizzazione del verbo inglese "to found" impropriamente sostantivato?...
Ostico per me sicuramente, ma, qui, brutale italianizzazione del verbo
Buongiorno.
Sono riuscito a razionalizzare un po' il codice e ad aggiungere un debounce hardware (ho comunque ancora lasciato la parte software che aiuta un po' finche' non riusciro' a a farne uno hardware "serio").
Il tutto funziona correttamente, quindi, se non noate anomalie strane, tornerei ala domanda del primo post.
Intanto grazie a tutti.
#include <TimerOne.h>
#define TAP_PIN 32
#define TAP_PIN_POLARITY RISING
#define MINIMUM_TAPS 4
#define EXIT_MARGIN 150 // If no tap after 150% of last tap interval -> measure and set
#define CLOCKS_PER_BEAT 24
#define MIN 30 // Used for debouncing
#define MAX 6000 // Used for debouncing
unsigned long bpm; // BPM in tenths of a BPM!!
unsigned long BPM;
unsigned long minimumTapInterval = 60L * 1000 * 10 / MAX;
unsigned long maximumTapInterval = 60L * 1000 * 10 / MIN;
volatile long firstTapTime = 0;
volatile long lastTapTime = 0;
volatile long timesTapped = 0;
unsigned long stepDur;
void setup() {
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(TAP_PIN), tapInput, TAP_PIN_POLARITY);
}
void tapInput() {
unsigned long now = millis();
if (now - lastTapTime < minimumTapInterval) {
}
if (timesTapped == 0) {
firstTapTime = now;
}
timesTapped++;
lastTapTime = now;
}
void updateBpm(unsigned long now) {
unsigned long interval = calculateIntervalMilliSecs(bpm);
Timer1.setPeriod(interval);
}
void printlcd() {
BPM = bpm / 10;
stepDur = (60000L / BPM);
/* lcd.setCursor(0, 1);
lcd.print("Set BPM to: ");
lcd.setCursor(12, 1);
char buf[4];
sprintf(buf, "%3d", BPM);
lcd.print(buf);*/
Serial.println(BPM);
Serial.println(stepDur);
}
unsigned long calculateIntervalMilliSecs(int bpm) {
return 60L * 1000 * 10 / bpm / CLOCKS_PER_BEAT;
}
void loop() {
unsigned long now = millis();
if (timesTapped > 0 && timesTapped < MINIMUM_TAPS && (now - lastTapTime) > maximumTapInterval) {
timesTapped = 0;
}
else if (timesTapped >= MINIMUM_TAPS) {
long avgTapInterval = (lastTapTime - firstTapTime) / (timesTapped - 1);
if ((now - lastTapTime) > (avgTapInterval * EXIT_MARGIN / 100)) {
bpm = 60L * 1000 * 10 / avgTapInterval;
updateBpm(now);
printlcd();
timesTapped = 0;
}
}
}
P. S. La variabile stepDur la utilizzo nel codice del mio step sequencer, è quella che mi regola la distanza degli Step.