Convertisseur MIDI CV-GATE ultra simple

Cet article décrit la réalisation convertisseur MIDI CV-GATE pour synthétiseurs analogique ultra simple grâce à l'utilisation d'un microcontrôleur Attiny85 programmable avec l'IDE Arduino.

C’est un tutoriel sur l’utilisation de l'USI sur l’ATtiny en tant que périphérique UART-RX et le MIDI est aussi efficace que toute application pour un UART.

Il y a 3 sorties supplémentaires non utilisées sur l'ATtiny45 / 85 qui peuvent être utilisées pour CV / Gates / Triggers ou Sync. Si vous exécutez ce code sur l’ATtiny44 / 84, 10 sorties au total peuvent être utilisées.

Si vous n'aimez pas ces convertisseurs USB-MIDI sophistiqués ou si vous êtes obligés de convertir votre clavier MIDI en signal CV / Gate pour un équipement vintage, vous pouvez réaliser ce montage.

Electronique

Le convertisseur MIDI2CV prend une entrée TTL-MIDI et génère un CV 1V / Octave et un signal Gate.




Le brochage pour l'ATtiny45 / 85 est :

  • PB0 est l'entrée MIDI.
  • PB1 est la sortie CV.
  • PB2 est la sortie de la porte

L’entrée MIDI est une entrée TTL et nécessite l’optocoupleur habituel.
.
La sortie CV est un signal PWM et nécessite un filtre passe-bas. La plage est C2-C7, 0-5 volts.

La sortie Gate est de 5 volts pour la note activée et de 0 volt pour la note désactivée.

Remarque: la puce peut fonctionner avec une alimentation fantôme MIDI, mais pour que le CV soit réglé correctement, la tension d'alimentation doit être exactement de 5 volts.
Programme

Deux principaux fonctions de base sont nécessaires à la fabrication du convertisseur:

  1. Convertisseur AD / A pour la sortie CV
  2. Une entrée série UART pour le MIDI

L'ATtiny n’ayant pas de CAN, nous en créons un en utilisant le PWM.

Il manque également une RS232 mais nous pouvons utiliser le périphérique USI pour cela.

Ce code prend en charge la configuration du PWM et de l'USI.

//Set Fuses to E1 DD FE for PLLCLK 16MHz
//Global variables for the MIDI handler
volatile uint8_t MIDISTATE=0;
volatile uint8_t MIDIRUNNINGSTATUS=0;
volatile uint8_t MIDINOTE;
volatile uint8_t MIDIVEL;
void setup() {
    
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1<
  
  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                     // Timer interrupts OFF
  TCCR1 = 1<// PWM A, clear on match, 1:1 prescale
  
  // Setup GPIO
  pinMode(1, OUTPUT); // Enable PWM output pin
  pinMode(0, INPUT);  // Enable USI input pin
  pinMode(2, OUTPUT); // Enable Gate output pin
  
  //Setup the USI
  USICR = 0;          // Disable USI.
  GIFR = 1<// Clear pin change interrupt flag.
  GIMSK |= 1<// Enable pin change interrupts
  PCMSK |= 1<// Enable pin change on pin 0
  
  GTCCR = 0;
  OCR1C = 239; //Set count to semi tones
  OCR1A = 0; //Set initial Pitch to C2
  digitalWrite(2,LOW); //Set initial Gate to LOW;
}

Ensuite, nous avons besoin d’une interruption de changement d'état pour gérer le bit de départ dans l’entrée série MIDI.

Les données MIDI sont des données série à 31250 bits / s, de sorte qu'une durée de bit équivaut à 32 microsecondes.

Le début d'un octet provoque une interruption de changement d'état de broche. Dans la routine d’interruption, nous vérifions qu’il s’agit d’un front descendant et, si tel est le cas, nous configurons le Timer / Counter0 en mode CTC. Nous voulons mettre en place un délai d'un demi-bit, pour entrer au milieu du bit de départ. Le plus proche que nous pouvons obtenir est un pré-décaleur de 8 et une comparaison de 32. Enfin, nous désactivons et activons l'interruption de comparaison de sortie.


ISR (PCINT0_vect) {
  if (!(PINB & 1<// Ignore if DI is high
    GIMSK &= ~(1<// Disable pin change interrupts
    TCCR0A = 2<// CTC mode
    TCCR0B = 0<// Set prescaler to /8
    TCNT0 = 0;                    // Count up from 0
    OCR0A = 31;                   // Delay (31+1)*8 cycles
    TIFR |= 1<// Clear output compare flag
    TIMSK |= 1<// Enable output compare interrupt
  }
}
L'interruption de correspondance de comparaison se produit au milieuL'interruption de comparaison se produit au milieu du bit de début. Dans la routine d'interruption, nous réinitialisons la correspondance avec la durée d'un bit, 64 (32uS), permettons à l'USI de commencer à décaler les bits de données lors de la comparaison suivante et activons l'interruption de dépassement d'USI.

ISR (TIMER0_COMPA_vect) {
  TIMSK &= ~(1<// Disable COMPA interrupt
  TCNT0 = 0;                      // Count up from 0
  OCR0A = 63;                    // Shift every (63+1)*8 cycles 32uS
  // Enable USI OVF interrupt, and select Timer0 compare match as USI Clock source:
  USICR = 1<
  USISR = 1<// Clear USI OVF flag, and set counter
}
Notez que nous définissons le mode de connexion sur 0 avec 0 << USIWM0. Cela garantit que la sortie du registre à décalage USI n'affectera pas la broche de sortie audio, PB1.

Lorsque 8 bits ont été décalés dans l’USI, une interruption de débordement se produit. La routine d'interruption désactive l'USI, lit le registre à décalage USI et active l'interruption de changement de broche prête pour l'octet suivant.

ISR (USI_OVF_vect) {
  uint8_t MIDIRX;
  USICR = 0;                      // Disable USI        
  MIDIRX = USIDR;
  GIFR = 1<// Clear pin change interrupt flag.
  GIMSK |= 1<// Enable pin change interrupts again
  //Wrong bit order so swap it
  MIDIRX = ((MIDIRX >> 1) & 0x55) | ((MIDIRX  << 1) & 0xaa);
  MIDIRX = ((MIDIRX >> 2) & 0x33) | ((MIDIRX  << 2) & 0xcc);
  MIDIRX = ((MIDIRX >> 4) & 0x0f) | ((MIDIRX  << 4) & 0xf0);
   
  //Parse MIDI data
  if ((MIDIRX>0xBF)&&(MIDIRX<0xf8 code="">
    MIDIRUNNINGSTATUS=0;
    MIDISTATE=0;
    return;
  }
  if (MIDIRX>0xF7) return;
  if (MIDIRX & 0x80) {
    MIDIRUNNINGSTATUS=MIDIRX;
    MIDISTATE=1;
    return;
  }
  if (MIDIRX < 0x80) {
    if (!MIDIRUNNINGSTATUS) return;
    if (MIDISTATE==1) {
    MIDINOTE=MIDIRX;
    MIDISTATE++;
    return;
  }
  if (MIDISTATE==2) {
    MIDIVEL=MIDIRX;
    MIDISTATE=1;
    if ((MIDIRUNNINGSTATUS==0x80)||(MIDIRUNNINGSTATUS==0x90)) {
      if (MIDINOTE<36 code="" midinote="36;">//If note is lower than C2 set it to C2
      MIDINOTE=MIDINOTE-36; //Subtract 36 to get into CV range
      if (MIDINOTE>60) MIDINOTE=60; //If note is higher than C7 set it to C7
        if (MIDIRUNNINGSTATUS == 0x90) { //If note on
        if (MIDIVEL>0) digitalWrite(2, HIGH); //Set Gate HIGH
        if (MIDIVEL==0) digitalWrite(2, LOW); //Set Gate LOW
        OCR1A = MIDINOTE<<2 code="">//Multiply note by 4 to set the voltage (1v/octave)
      }
      if (MIDIRUNNINGSTATUS == 0x80) { //If note off
        digitalWrite(2, LOW); //Set Gate LOW
        OCR1A = MIDINOTE<<2 code="">//Multiply note by 4 to set the voltage (1v/octave)
      }
    }
    return;
  }
}

L'UART envoie les bits LSB en premier, tandis que l'USI suppose que le MSB est le premier. Nous devons donc inverser l'ordre des bits après la réception.

Le gestionnaire MIDI est également inclus ici.

L'état actuel est ce que 99% de tous les synthétiseurs et séquenceurs MIDI font aujourd'hui, ce qui signifie que si l'octet d'état est le même que le précédent, il n'est pas transmis, ce qui entraîne moins d'octets sur la ligne.

Gestion de «l'état de fonctionnement»
1. La mémoire tampon est effacée (c'est-à-dire mise à 0) à la mise sous tension.
2. Le tampon stocke l’état lorsqu’un état de catégorie de voix (0x80 à 0xEF) est reçu.
3. La mémoire tampon est effacée lors de la réception d'un statut de catégorie système commun (c'est-à-dire 0xF0 à 0xF7).
4. Rien n'est fait dans la mémoire tampon lorsqu'un message de catégorie RealTime est reçu.
5. Tous les octets de données sont ignorés lorsque la mémoire tampon est à 0.

Le gestionnaire gère le statut en cours et appelle les fonctions noteon / noteoff et CC.

Les octets 0x80, 0x90 et 0xB0 correspondent au statut MIDI pour Note-on, Note-off et MIDI-CC sur le canal n ° 01. Si vous voulez un autre canal de réception, par exemple le canal 02, vous modifiez le quartet inférieur en 0x01, c’est-à-dire 0x91.

De plus, la majorité des émetteurs utilisent Note-on avec une vélocité nulle pour la Note-off.

La sortie CV est dans la plage C2 à C7 (0-5 volts). Nous soustrayons donc 36 de la clé pour rendre C2 0 volt.

Nous vérifions également si la note est supérieure à C7, ce qui correspond à 5 volts, et la limitons.

Ensuite, nous écrivons la clé dans la sortie du CV.

8 bits est en fait une résolution trop basse pour la hauteur mais une astuce le fait fonctionner.
La plage de CV est définie sur 0-239 au lieu de 255, ce qui donne à chaque semi-ton 4 étapes exactement.

Si l'événement est une note, nous définissons la sortie Gate à l'état haut.

Voici le code complet:
// (*) All in the spirit of open-source and open-hardware
// Janost 2019 Sweden 
// The goMIDI2CV interface
// http://blog.dspsynth.eu/diy-good-ol-midi-to-cv/
// Copyright 2019 DSP Synthesizers Sweden.
//
// Author: Jan Ostman
//
// This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//Set Fuses to E1 DD FE for PLLCLK 16MHz
#include
#include
#include
  
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
  
volatile uint8_t MIDISTATE=0;
volatile uint8_t MIDIRUNNINGSTATUS=0;
volatile uint8_t MIDINOTE;
volatile uint8_t MIDIVEL;
  
void setup() {
    
  // Enable 64 MHz PLL and use as source for Timer1
  PLLCSR = 1<
  
  // Set up Timer/Counter1 for PWM output
  TIMSK = 0;                     // Timer interrupts OFF
  TCCR1 = 1<// PWM A, clear on match, 1:1 prescale
  
  // Setup GPIO
  pinMode(1, OUTPUT); // Enable PWM output pin
  pinMode(0, INPUT);  // Enable USI input pin
  pinMode(2, OUTPUT); // Enable Gate output pin
  
  //Setup the USI
  USICR = 0;          // Disable USI.
  GIFR = 1<// Clear pin change interrupt flag.
  GIMSK |= 1<// Enable pin change interrupts
  PCMSK |= 1<// Enable pin change on pin 0
  
  GTCCR = 0;
  OCR1C = 239; //Set count to semi tones
  OCR1A = 0; //Set initial Pitch to C2
  digitalWrite(2,LOW); //Set initial Gate to LOW;
}
ISR (PCINT0_vect) {
  if (!(PINB & 1<// Ignore if DI is high
    GIMSK &= ~(1<// Disable pin change interrupts
    TCCR0A = 2<// CTC mode
    TCCR0B = 0<// Set prescaler to /8
    TCNT0 = 0;                    // Count up from 0
    OCR0A = 31;                   // Delay (31+1)*8 cycles
    TIFR |= 1<// Clear output compare flag
    TIMSK |= 1<// Enable output compare interrupt
  }
}
ISR (TIMER0_COMPA_vect) {
  TIMSK &= ~(1<// Disable COMPA interrupt
  TCNT0 = 0;                      // Count up from 0
  OCR0A = 63;                    // Shift every (63+1)*8 cycles 32uS
  // Enable USI OVF interrupt, and select Timer0 compare match as USI Clock source:
  USICR = 1<
  USISR = 1<// Clear USI OVF flag, and set counter
}
ISR (USI_OVF_vect) {
  uint8_t MIDIRX;
  USICR = 0;                      // Disable USI        
  MIDIRX = USIDR;
  GIFR = 1<// Clear pin change interrupt flag.
  GIMSK |= 1<// Enable pin change interrupts again
  
  //Wrong bit order so swap it
  MIDIRX = ((MIDIRX >> 1) & 0x55) | ((MIDIRX  << 1) & 0xaa);
  MIDIRX = ((MIDIRX >> 2) & 0x33) | ((MIDIRX  << 2) & 0xcc);
  MIDIRX = ((MIDIRX >> 4) & 0x0f) | ((MIDIRX  << 4) & 0xf0);
    
  //Parse MIDI data
  if ((MIDIRX>0xBF)&&(MIDIRX<0xf8 code="">
    MIDIRUNNINGSTATUS=0;
    MIDISTATE=0;
    return;
  }
  if (MIDIRX>0xF7) return;
  if (MIDIRX & 0x80) {
    MIDIRUNNINGSTATUS=MIDIRX;
    MIDISTATE=1;
    return;
  }
  if (MIDIRX < 0x80) {
    if (!MIDIRUNNINGSTATUS) return;
    if (MIDISTATE==1) {
    MIDINOTE=MIDIRX;
    MIDISTATE++;
    return;
  }
  if (MIDISTATE==2) {
    MIDIVEL=MIDIRX;
    MIDISTATE=1;
    if ((MIDIRUNNINGSTATUS==0x80)||(MIDIRUNNINGSTATUS==0x90)) {
      if (MIDINOTE<36 code="" midinote="36;">//If note is lower than C2 set it to C2
      MIDINOTE=MIDINOTE-36; //Subtract 36 to get into CV range
      if (MIDINOTE>60) MIDINOTE=60; //If note is higher than C7 set it to C7
        if (MIDIRUNNINGSTATUS == 0x90) { //If note on
        if (MIDIVEL>0) digitalWrite(2, HIGH); //Set Gate HIGH
        if (MIDIVEL==0) digitalWrite(2, LOW); //Set Gate LOW
        OCR1A = MIDINOTE<<2 code="">//Multiply note by 4 to set the voltage (1v/octave)
      }
      if (MIDIRUNNINGSTATUS == 0x80) { //If note off
        digitalWrite(2, LOW); //Set Gate LOW
        OCR1A = MIDINOTE<<2 code="">//Multiply note by 4 to set the voltage (1v/octave)
      }
    }
    return;
  }
}
void loop() {
}

Ajouter un CV pitchbend

Les valeurs de CV de 240 notes sont trop peu nombreuses pour ajouter un pitchbend.

Nous utilisons donc une autre sortie PWM et un filtre RC qui fournit la valeur de pitchbend. Ce CV peut être utilisé séparément ou ajouté à la note CV au moyen d’une résistance de 4,7K.

Ajoutez ce code à la fonction setup et au gestionnaire MIDI:
//Setup addon for pitchbend output
pinMode(4, OUTPUT); // Enable PB4 output pin for pitchbend CV
GTCCR = 1<// PWM B, clear on match
//USI_OVF_vect addon
if (MIDIRUNNINGSTATUS == 0xE0) { //If pitchbend data
  if (MIDIVEL<4 code="" midivel="4;">//Limit pitchbend to -60
  if (MIDIVEL>119) MIDIVEL=119; //Limit pitchbend to +60
  MIDIVEL-=4; //Center the pitchbend value
  OCR1B = (MIDIVEL<<1 code="">//Output pitchbend CV
}
Le CV de sortie pour pitchbend sera sur PB4 (broche 3 de la puce).

Commentaires

Posts les plus consultés de ce blog

Rouler à l'E85 version Arduino

Rouler à l'E85 version Arduino simplifiée et sécurisée

Roulez à l'E85 la conception d'un boitier