Kurzschluss
Zwei Geräte habe ich aus China erhalten. Eines, hier im Bild, geht recht gut, das andere hatte einen Kurzschluss, den ich beheben konnte, und einen, den ich nicht finde. Zudem ist die Stromversorgung nicht ideal: Je nach Zustand der Kondensatoren, und auch wie die externe Stromversorgung zeitlich die 12 und 5 Volt beliefert, startet das OLED-Display nicht korrekt. Eine nächste Revision wird somit nötig: mit teurerem Produktionsverfahren (hoffentlich dann ohne Kurzschlüsse) und einer überarbeiteten Stromversorgung.
Aufgeräumt
Hier mein aktuelles Setup:
Links drei schöne analoge Synthesizer: ein A4 von GRP, ein Nachbau eines ARP 2600 aus Spanien und ein MidiMini V30, mit der DNA eines Moogs. Obendrauf das Tape Echo, und natürlich der Eurorack. Rechts davon und nicht im Bild steht noch ein Prophet.
Vorne dann die digitale Abteilung: zwei Geräte von Elektron und ein kleiner, feiner Synthesizer von Yamaha. Er macht polyphon Töne und hat auch die Kontrolle über die anderen Geräte.
Der eigentliche Star des Setups sieht man kaum unten links, ein oranges Kabel names Retrokit RK-002, das eine kleine Schwäche des Yamahas kompensiert: Die Kanalwahl bei dem Gerät ist sehr umständlich. Also lasse ich es alle Signale auf den Midi-Kanal 16 und durch das spezielle Kabel schicken. Das hat einen winzigen, eingebauten Mikroprozessor, der auf das durchgehende Signal hört und es ändern kann. Ich habe den so programmiert, dass er spezielle Tastenkombinationen des Keyboards erkennt und sich dann merkt, auf welchen korrekten Ausgangskanal das Signal zu wechseln ist. Z. B. auf Kanal 1 für den ARP oder Kanal 4 für den MidiMini. So kann man ganz einfach den Kanal wählen, indem man zwei, drei spezielle Tasten auf dem Keyboard gleichzeitig drückt.
Das ganze Setup wartet jetzt darauf, Musik zu machen; und auf ein Gerät, das ich gerade am entwickeln bin, den Eurorack Sequencer ES-16, der all die Musiker dirigieren wird.
Langsam
Langsam nimmt mein Synthesizer (eigentlich ein Sequencer) Formen an, hier zwei Impressionen. Alle drei Platinen der Hardware sind auf dem Bild sichtbar, aber noch fehlerhaft: speziell an der Stromversorgung und am Digital-Analog Wandler bin ich dran, dazu ist gerade die nächste Iteration in der Fertigung. Parallel arbeite ich an der Software, die Hardware-Anbindung ist schon fast fertig, es gibt da 9 Teilsysteme: Knöpfe (am Ende werden es 180 sein!), Encoder, OLED, LED, Status, Beat, Memory, MIDI RX und TX.
Es geht weiter
Hier der nächste, zweite Prototyp meines Synthesizer-Projektes. Noch ohne (externes) RAM und Flash, aber immerhin ist der Umstieg vom PIC32-Prozessor, den ich bisher genutzt und gut gekannt habe auf einen ARM-Prozessor (von STM32) geglückt.
Selbstgemacht
Zwei ganz unterschiedliche Dinge: Links der Bausatz „Noise Toaster“, eben zusammengelötet. Rechts ein allererster Prototyp, mehr ein Versuch, meines eigenen Synthesizers; von Grund auf selbst entworfen.
RX
Habe nun das Programm erweitert, so dass es nicht nur Daten senden (TX), sondern auch empfangen kann (RX):
// serial 9600,N,8,1
// led on PB3, tx on PB4, rx on PB0
#include <avr/io.h>
#include <avr/interrupt.h>
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // clear bit
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // set bit
volatile uint8_t tx_buffer = 0; // tx buffer to send
volatile uint8_t tx_register = 0; // tx register
volatile uint8_t rx_buffer = 0; // rx bufffer to receive
volatile uint8_t rx_register = 0; // rx register
ISR (PCINT0_vect) {
if (!(PINB & (1 << PB0))) { // only trigger if level 1 -> 0
cbi(GIMSK, PCIE); // disable pin change interrupt
rx_register = 1; // rx has work
}
}
ISR (TIM0_COMPA_vect) {
if (tx_register != 0) { // skip if nothing to send
if (!(tx_register & 1)) { // skip every second step
tx_register++; // next step
} else if (tx_register == 1) { // index = work
cbi(PORTB, PB3); // send start bit
tx_register = 2; // next step
} else if (tx_register <= 17) { // index = data
if (tx_buffer & 1) { // from lsb to msb
sbi(PORTB, PB3); // send data bit
} else {
cbi(PORTB, PB3); // send data bit
}
tx_buffer >>= 1; // remove lsb
tx_register++; // next bit
} else if (tx_register == 19) { // index = end
sbi(PORTB, PB3); // send stop bit
tx_register = 0; // work done
}
}
if (rx_register != 0) { // only work if triggered
if (!(rx_register & 1)) { // skip every second step
} else if (rx_register == 1) { // start
rx_buffer = 0; // new rx_buffer
rx_register = 3; // catch the next wave
} else if (rx_register <= 19) { // receive data
rx_buffer |= ((PINB & (1 << PB0)) << ((rx_register >> 1) - 2));
}
rx_register++; // next bit
}
}
void serial_putc(char c) {
while(tx_register); // wait while busy
tx_buffer = c; // data to transmit
tx_register = 1; // tx has work
}
char serial_getc() {
while(rx_register < 23); // wait until buffer ready
rx_register = 0; // reset rx register
sbi(GIMSK, PCIE); // activate pin change interrupt
return rx_buffer; // return data
}
int main(void) {
sbi(DDRB, PB4); // set led pin as output
sbi(DDRB, PB3); // set tx as output
sbi(PORTB, PB3); // tx level is high
cbi(DDRB, PB0); // rx as input
sbi(PORTB, PB0); // pull up rx
sbi(PCMSK, PCINT0); // enable pin change on pin 0
sbi(GIMSK, PCIE); // enable pin change interrupt
sbi(TCCR0A, WGM01); // CTC mode, clear timer on compare match
sbi(TCCR0B, CS01); // prescaler clk/8 -> 1 tic = 1us for 8mhz
OCR0A = 53; // set compare register A
// 103us from wormfood.net/avrbaudcalc.php
sbi(TIMSK, OCIE0A); // enable interrrupt for OCROA==TCNT0
sei(); // enable all interrupts
for (;;) { // main loop
char command = serial_getc(); // read command
serial_putc(command); // serial out
if (command == '1') {
PORTB ^= (1 << PB4); // toggle led
}
}
};
Das Programm empfängt ein am Computer eingegebenes Zeichen und schickt es zurück. Wenn es ein „1“ ist, wird zudem die LED umgeschaltet. Es ist rund 430 Byte gross.
Senden funktioniert wie bis anhin: alle 53 * 8 Takte (wobei jedes zweite Mal nichts passiert – dazu später mehr – und eigentlich sollten es 52 * 8 sein, aber der interne Taktgeber ist ungenau) wird abgefragt, ob es etwas zum Senden gibt. Wenn ja, wird zuerst ein Start-, dann alle Daten- und am Ende das Stoppbit am Ausgangspin angelegt.
Zum Empfangen wird ständig geprüft, ob sich der Pegel am Eingangspin ändert. Wenn ja, und es ein Startbit ist, schalten wir die Prüfung aus und beginnen mit dem Lesen der Daten. Da dies nicht zu Beginn, sondern am besten in der Mitte eines Bits geschehen soll, wartet wir zusätzlich 53 * 8 Takte und gehen dann ähnlich wie beim Senden vor. (Damit dies zuverlässig geschieht, und man gleichzeitig Senden und Empfangen kann, haben wir den Timer von 103 auf 53 „halbiert“.) Ganz am Ende schalten wir die Prüfung des Eingangspins wieder ein.
Als nächstes möchte ich den Code weiter verkleinern und robuster machen; dann soll die Kommunikation auf mehrere ATtiny ausgebaut werden.
TX
Der ATtiny ist nun über eine Serielle Schnittstelle mit dem PC verbunden. Vorerst sendet er nur Daten und kann keine empfangen, aber immerhin.
Da der ATtiny diese Schnittstelle nicht von sich aus unterstützt, musste ich sie ausprogrammieren. So sieht der komplette Sourcecode aus:
// serial 9600,N,8,1
// led on PB3, tx on PB4
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // clear bit
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) // set bit
volatile uint8_t tx_work = 0; // work request
volatile uint8_t tx_index; // loop for on byte
volatile uint8_t tx_buffer; // bufffer to send
ISR(TIM0_COMPA_vect) { // interrupt service routine
if (tx_work) { // work has to be done
if (tx_index == 0) { // index = 0
cbi(PORTB, PB3); // send start bit
} else if (tx_index <=8) { // index = data
if (tx_buffer & 1) { // from lsb to msb
sbi(PORTB, PB3); // send data bit
} else {
cbi(PORTB, PB3); // send data bit
}
tx_buffer >>= 1; // remove lsb
} else if (tx_index >= 9) { // index = 9
sbi(PORTB, PB3); // send stop bit
tx_work = 0; // work done
}
tx_index++; // next index
}
}
void serial_putc(char c) {
while(tx_work); // wait if busy
tx_index = 0; // reset index
tx_buffer = c; // data to transmit
tx_work = 1; // isr has work
}
void serial_print(const char *str) {
uint8_t i;
for (i = 0; str[i]; i++) {
serial_putc(str[i]); // write each character
}
}
int main(void) {
sbi(DDRB, PB4); // set led pin as output
sbi(DDRB, PB3); // set tx as output
sbi(TCCR0A, WGM01); // CTC mode, clear timer on compare match
sbi(TCCR0B, CS01); // prescaler clk/8 -> 1 tic = 1us for 8mhz
OCR0A = 103; // set compare register A
// 103us from wormfood.net/avrbaudcalc.php
sbi(TIMSK, OCIE0A); // enable interrrupt for OCROA==TCNT0
sei(); // enable interrupts
uint8_t v; // init visual counter
for(v = 0;; v++) { // main loop
PORTB ^= 1<<PB4; // toggle led
serial_print("toggle\r\n"); // serial out
_delay_ms (200); // wait 200ms
}
};
Das LED leuchtet dadurch alle 400 ms – so sieht das Ergebnis auf dem PC aus:
toggle toggle ...
PS: Das Programm verbraucht 324 Bytes. Eine frühere Version welche elegant mit fdevopen(&serial_putc, 0) und printf gearbeitet hat, musste ich verwerfen: sie war mit 2’552 Bytes, also rund 60% des verfügbaren Speichers, viel zu gross.
Winzig
Nach dem ATmega328 (steckt im Arduino) und dem ARM Cortex M0 (im Simblee) möchte ich einen neuen Mikrocontroller kennenlernen, den ATtiny45. Wieder ein AVR-Prozessor, sehr ähnlich wie sein grosser Bruder ATmega328, nur kleiner und leichter: Er hat 256 Byte RAM, 4 KB Flash-Speicher, 8 Füsschen und – was schmerzt – von Haus aus kein einfaches, serielles Interface, nur SPI. Und I2C, ok, vielleicht kann ich das irgendwie nutzten? Oder soll ich das serielle Interface mit Software nachbilden? geht das mit nur 4KB? Dazu später mehr.
Trotz seiner Grösse ist der ATtiny ein vollständiger kleiner Mikroprozessor: hat 2 Timer, Analog/Digital-Wandler, einen Stromsparmodus, und ist robust bezüglich der Stromversorgung (2 bis 5.5V). Er kostet etwa 1 EUR/CHF.
Mit diesem Projekt möchte ich weg von der Arduino IDE: es gibt zum Glück eine ausgezeichnetes Paket, das für AVR-Mikrocontroller alles nötige enthält, den C-Compiler und avrdude, um die eigene Software auf den Prozessor zu schreiben. Daneben nutzte ich jetzt den Editor Atom.
So sieht die Hardware aus:
Oben rechts: Der UBSTinyISP um den Chip via SPI zu programmieren, wird von avrdude sehr gut unterstützt. Um den Programmer einfacher mit dem Breadboard zu verbinden, habe ich mir dafür einen Stecker zusammengelötet.
Unten rechts: Ein FTDI-Adapter, der aus einem USB-Ausgang einen seriellen Port macht. Noch nicht im Einsatz.
Links: Das Breadboard mit dem ATtiny45 selbst (winzig, oder?) Zudem eine LED zum Testen und die Verkabelung zum UBSTinyISP, der auch die Stromversorgung übernimmt. Die Taktung erfolgt über den Chip selbst, ich habe noch kein Gefühl dafür, ob diese genau genug ist, oder es einen externen Oszillator braucht.
Die ersten Versuche haben gut geklappt.
- Verbindung prüfen
avrdude -p t45 -c usbtiny avrdude: AVR device initialized and ready to accept instructions Reading | ################################ | 100% 0.00s avrdude: Device signature = 0x1e9206 [...] avrdude done. Thank you.
- Die Einstellungen (sogenannte Fuses) des ATtiny richtig setzten, damit er mit 8MHz läuft, ab Werk sind es nur 1Mhz.
avrdude -p t45 -c usbtiny -U lfuse:w:0xe2:m -U hfuse:w:0xdf:m [...]
- Ein kleines Programm schreiben, welches die LED – sie ist an Pin PB4 angeschlossen – ein- und ausschaltet.
#include <avr/io.h> #include <util/delay.h> int main(void) { DDRB = (1<<4); for(;;) { _delay_ms(1000); PORTB ^= (1<<4); } return 0; }
- Und dieses kompilieren (umwandeln in eine hex-Datei) und auf den Chip hochladen (hier das Makefile).
make flash avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny45 -c main.c -o main.o avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny45 -o main.elf main.o rm -f main.hex avr-objcopy -j .text -j .data -O ihex main.elf main.hex avr-size --format=avr --mcu=attiny45 main.elf AVR Memory Usage ---------------- Program: 84 bytes (2.1% Full) Data: 0 bytes (0.0% Full) avrdude -p t45 -c usbtiny -U flash:w:main.hex:i [...] avrdude: erasing chip avrdude: reading input file "main.hex" avrdude: writing flash (84 bytes): Writing | ######################################## | 100% 0.19s avrdude: 84 bytes of flash written avrdude: verifying flash memory against main.hex: [...] avrdude done. Thank you.
Soweit so gut.
Alles wir gut
Ach, es ist nicht einfach: Hier zwei Stücke, Gehversuche, „Vorher-Bilder“. Aber irgendwo muss man doch beginnen?!
Filter
Das beste an einem modularen Synthesizer: Man kann alle Module auf unterschiedlichste Weise nutzen, etwa einen Filter:
- Der kann einen Ton filtern, etwa die hohen oder tiefen Frequenzen drinnlassen, den Rest entfernen, so den Ton interessanter gestalten. Die Filterfrequenz dazu auch modulieren, also schnell oder langsam oder zufällig höher und tiefer machen.
- Einen Ton strukturieren; wenn etwa eine Taste vom Keyboard gedrückt wird, den Filter öffnen, auch abhängig von der Stärke des Anschlages, sonst komplett schliessen.
- Eine Melodie spielen, dazu den Filter mehr oder weniger öffnen, abhängig von der Höhe der zu spielenden Note. Das Eingangssignal kann dafür auch ein Rauschen sein, das Ergebnis „tönt“.
Manche Filter kann man in Resonanz bringen. Dazu lässt man einen Teil des Tones vom Ausganges in den Einganz zurückfliessen. Das gibt interessante Effekte und erlaubt einen sehr speziellen Einsatzzweck, man kann den Filter als eigenständige Tonquelle nutzen, wie einen Oszillator:
- In den Audioeingang kommt kein richtiger Ton, nur ein kurzes Knacken, ein Trigger. Das braucht der Filter zum Einschwingen, um sich in Resonanz zu bringen. Wir geben diesen Trigger bei jeder zu spielenden Note von neuem.
- In den Frequenzeingang kommt ein Stromsignal, das die gewünschte Tonhöhe angibt, wobei 1 Volt einer Oktave entspricht. Damit es am Ende schönt klingt, muss man diesen Eingang stimmen, so dass z. B. 3 Volt einem C entspricht (das ist einfach) und dann 4 Volt dem nächst höherem C und so weiter (das ist schwierig).
- Den Regler „Q“, der angibt, wie viel der Filter von seinem Ausgang in den Eingang zurückspielt, wie stark er also in Resonanz ist, stellen wir auf Null. Dafür haben wir einen Stromeingang, der das gleiche einfordert. Und dem geben wir immer dann Strom, während eine Keyboard Taste gedrückt wird. Würden wir das nicht machen, wäre der Filter immer in Resonanz würde ununterbrochen tönen.
- Unser Filter hat verschiedenen Ausgänge, jeder filtert etwas anders. Wir nutzen den Ausgang „4B“, das ist ein steiler Bandpassfilter, der lässt die gewünschte Frequenz und nur ein bisschen darunter und darunter durch.
Und so tönt das Ergebnis: Ganz einfach…
multiconnect
Mein erstes selbst gemachtes Modul ist fertig! Es funktioniert nur als Paar und verbindet zwei Synthesizer miteinander, etwa um den Kabelsalat zu verringern oder zwei fertig verdrahtete Cases einfach zu transportieren. multiconnect ist von dem – leider noch nicht lieferbaren – Modul A180-9 inspiriert und ganz einfach aufgebaut: es überträgt die Signale von einem Modul zum anderen, einige Anschlüsse werden dabei verdreifacht. Das Modul ist passiv, verstärkt die Signale nicht; da diese meist ganz am Anfang der Signalkette (Trigger, Gates) oder knapp vor der Ausgabe stehen, sollte das kein Problem sein.
Zum Bau habe ich drei Anläufe gebraucht, zuerst zwei Prototypen und jetzt das fertige Modul:
- Erster Prototyp (viertes Bild): das Panel ist auf Anhieb schon recht hübsch geworden, aber alles war um ein paar Millimeter falsch dimensioniert, etwa die Löcher für die Stecker zu gross. Aus Fehlern lernt man! Eine Palatine hatte dieser Prototyp noch nicht, die Stecker sind über Kabel direkt aneinander gelötet.
- Zweiter Prototyp: das Panel ist nun korrekt auch die Bauteile entsprechen der endgültigen Fassung. Einzig die Platine (siehe fünftes Bild) ist noch nicht final.
- Final Version (erstes und zweites Bild): Eine eigene Leiterplatte ist schon etwas Schönes! Reduziert das Löten beträchtlich und sieht gut aus. Sowohl die Platine als auch das Frontpanel kann man mit einem speziellen Computerprogramm selbst zeichnen, die Pläne dann dem Hersteller (nach Holland und Deutschland) mailen – eine Woche später und bei überschaubaren Kosten erhält man die massgefertigten Teile zurück.
Test und Endergebnis: Wie man auf dem letzten Bild sieht, überträgt multiconnect auch Audiosignale mit relativ hohen Frequenzen sauber, hier getestet mit einen Signalweg von 1.5 Meter bei 22 kHz.
Drei von Vier
Habe versucht ein Modul zu „verbessern“: drei Änderungen haben geklappt, eine habe ich vermurkst. Zuerst habe ich den falschen Widerstand genommen, 200 anstelle von 200k, dann eben diesen beim rauslöten gebacken. Oh Mann!
Und hier noch ein paar neue „Töne“:
Gleichzeitig
Ich habe zwei Sequencer: der Acid Stepper kann kurze Melodien mit bis zu 256 Noten speichern und abspielen. Den A-155 habe ich eher für die Begleitung vorgesehen, er kann nur 8, dafür komplexere Signale nacheinander abgeben.
Bis gestern konnte ich die beiden zwar dazu bringen gleich schnell zu sein, im Takt zu arbeiten, sie waren aber nicht synchron: während der eine eben begonnen hat, war der andere bereits beim zweiten Ton oder umgekehrt. Das Problem: Zwar kann der Acid Stepper sagen, wann er loslegt, der A-155 springt dann tatsächlich zum ersten Ton, aber spielt ihn nicht, sondern beim nächsten Impuls und dann gleich den zweiten. Nun gäbe es ein Zusatzmodul A-154, das interpretiert das Zurücksetzen (Reset) anders, und springt beim darauffolgenden Impuls zum ersten Ton, aber das Modul habe ich nicht.
Heute und nach vielen Versuchen habe ich dann eine feine Lösung gefunden: Immer wenn der A-155 mit seinen 8 Schritten fertig ist, stoppt er erst mal. Das kann man auf unterschiedliche Arten machen, bei mir triggert der letzte Schritt den Stop-Schalter. Läuft die Melodie, wird ständig und im Takt der Start aktiviert, und alles beginnt von vorne. Ist sie aber fertig, bleibt der A-155 wie gewünscht beim letzten Schritt stehen. Beim nächsten Start kann er direkt und wie gewünscht mit dem Ersten beginnen. Mit dieser kleinen Logik starten die beiden Sequencer – ausser beim allerersten Mal, da muss der eine den letzten Schritt suchen – synchron, und, als Zugabe, schaltet der eine den anderen ein und aus. Das ohne den eigentlich für diese Aufgabe vorgesehenen „Reset“ zu nutzen.
So sieht die Verkabelung aus, und die beiden Signale: Unten in Blau der Acid Stepper, oben in Gelb der A-155:
A-100
Ich versuche mich an einem analogen, modularen Synthesizer. Naja, etwas digital ist er schon, aber nur ein Modul (auf dem Bild das unten rechts):
Um Töne zu machen, muss man die einzelnen Module (Klangquellen, Filter, Verstärker etc.) zusammenstecken und regeln. Hier ein erste Hörprobe, meine allerersten Schritte:
Mikrocontroller
Meine letzten beiden Projekte habe ich abgeschlossen:
- Messung der Geschwindigkeit einer Billardkugel
Zuerst kallibrieren sich eine Zentrale mit Display und drei Aussenstellen, verbunden mit nRF24. Dabei werden die Distanzen der Aussenstellen zueinander ausgemessen und die internen Uhren geeicht. Anschliessend hören sie den Stoss, und bestimmen daraus Position und Geschwindigkeit der Kugeln. - Virtuelles Schaufenster
Wenn man in die Nähe dieses Bluetooth-Gerätes kommt, notifiziert es die zugehörige iOS-App. Diese erkennt die vier LEDs des Gerätes und projiziert eine Augmented Reality in das Kamerabild.
Beide Projekte nutzen einen ATmega32-Mikroprozessor, mit Arduino-Bootloader, extern getaktet und mit 3V versorgt.
Ein drittes Projekt, Amulett, habe ich gestoppt. Das Ziel des Projektes war – wie der Name schon sagt – ein Amulett, das man sich um den Hals hängen kann. Es versorgt mit einer kleinen Batterie oder Solarzelle den Prozessor und das Bluetooth-Modul im Innern. Alles soll sehr leicht und sparsam sein. Das Amulett kann – mit eingebauten LEDs – in mehreren Farben leuchten und lässt sich über eine App konfigurieren; man kann etwa einstellen, welche „Interessen“ man hat. Ist man in der Nähe eines anderen Amulettes, das von einer Person getragen wird oder an einer speziellen Stelle positioniert ist, leuchten beide Amulette eine kurzen Zeit in der gleichen Farbe. Alle solche Begegnungen können über die App ausgelesen und an einer zentralen Stelle gesammelt und ausgewertet werden. Als Hardware habe ich Simblee versucht. Die Software darauf war fast fertig, aber der (Test-)aufwand für verschiedenen, gleichzeitig aktive Geräten war zu viel für mich.