Sammlung Teensy - Anfängerfragen

Wir sammeln hier (steht im ersten Beitrag) - bitte nur das hier posten…
Anhang anzeigen 199604

Da ich den Fehler der Buttons finden konnte, geht die Arbeit am Synthie weiter. Ich habe jetzt 3 Buttons implementiert, die die Wellenformen der Oszillatoren einstellen und das Tuning zwischen den Osc1 und Osc2. Langsam nicht das alles Form an. Geplant ist aber, dass man mit den Buttons jeweils Pages aufrufen kann, die Parameter auf die Encoder legen. Das kommt aber später, wenn die Soundstruktur an sich stimmt.

Allerdings habe ich gemerkt, dass die Ausgabe auf das Display via I2C den Audiostream verzögert. Das bedeutet, dass man bei jedem Dreh am Encoder den Audiostream unterbricht. Das geht natürlich nicht - so kann man den Synth ja nicht live spielen. Ich habe verschiedene Ansätze probiert. Auch das Austauschen der Wire-Bibliothek gegen die I2C-Wire von https://github.com/Richard-Gemmell/teensy4_i2c konnte das Problem nicht lösen.

Letztlich habe ich es so gemacht, dass das Display nur bei einem NoteOn aktualisiert wird und das funktioniert eigentlich ganz gut. Die Soundparameter werden ja auch erst mit der nächsten Note aktualisiert. Vielleicht hat noch jemand eine Idee, wie man I2C-Displays ohne Unterbrechung des Audiosignals nutzen kann.

@rolfdegen Was nutzt Du denn für ein Display? Kennst Du das o.g. Problem?

Hallo Solar Chrome
Wenn du die Teensy Audio Lib von Paul Stoffregen benutzt, dürfte es bei der Datenkomunikation über den I2C Bus keine Unterbrechungen geben. Schau mal in die Links..

Link: https://forum.pjrc.com/index.php?threads/audio-cuts-out-when-updating-i2c-oled-display.67141/
Link: https://forum.pjrc.com/index.php?threads/teensy-4-1-i2c-issues-when-using-audio.72793/

Ich verwende ein ST7735s Farb-Display mit 160x128 Pixel und der Teensy ST7735 Bibiothek von Adafruit. Das Display wird alle 50msec über den SPI Bus per DMA Funktion mit Daten gefüttert. Wenn man zum Beispiel tft.print(text) an das Display sendet, sorgt ein 20KByte großer FrameBuffer dafür, dass diese Daten mit den anderen Schreibbefehlen in den Zwischenspeichern landen und alle 50msec per DMA als komplette Bildschirmseite an das Display gesendet werden. Der Vorteil von DMA liegt in der schnelleren Datenübertragung bei gleichzeitiger Entlastung der CPU.

Gruß Rolf
 
Zuletzt bearbeitet:
Das Problem bei den vielen fertigen Bibliotheken ist, das dort oft nicht auf Echtzeitfähigkeit geachtet wird. Um richtige Echtzeitfähigkeit zu bekommen, muss man sich Gedanken über die Abläufe und deren Verteilung im ganzen System machen. Da spielen dann Interrupte, deren Prioritäten und wenn verfügbar - Threads eine große Rolle. Da gibt es dann viele Randbedingungen, u.a. die begrenzten Resourcen. Ich kenn mich mit dem Teensy-Zeug nicht so gut aus, aber nehmen wir mal an, das die ganze Audio-Verarbeitung im Interrupt stattfindet. Dann kann diese die anderen Aufgaben unterbrechen. Aber das funktioniert nur dann, wenn die Interrupt-Prioritäten korrekt eingestellt sind und an keiner anderen Stelle im Code die Interrupte für längere Zeit global gesperrt werden. Ich könnte mir vorstellen, das die I2C Bibliotheken ebenfalls Interruptroutinen verwenden. Wenn diese eine höhere Priorität haben als die Audio-Interrupte, dann können die I2C Interrupt die Audio-Interrupte unterbrechen und somit für Verzögerungen im Datenstrom sorgen.
Solche Echtzeitanwendungen zu programmieren ist nicht einfach, weil man viele Sachen beachten muss. Am Ende des Tages muss man sich auch mit asynchronen Abläufen beschäftigen, was das ganze nochmal komplizierter macht. Asynchron bedeutet, das ein Funktion nicht sofort fertig ist, nachdem an sie aufgerufen hat. z.B. beim I2C könnte man sich eine Funktion "Write_Request" vorstellen, die einen Schreibvorgang zwar startet, aber zurück kommt noch bevor der I2C Bustransfer fertig ist. Wenn der Transfer dann später fertig ist wird dann ein Event/Callback erzeugt auf den man dann wiederum reagieren muss damit es weiter geht. Man kann dann im code natürlich nicht einfach "Write_Request" mehrfach hintereinander schreiben, sondern muss warten bis man den "Complete" Event bekommt. Typischerweise braucht man dann eine State-Machine mit der man die I2C Sequenzen in einzelne Requests aufteilt und gestückelt abarbeitet.
Als ersten Schritt könntes Du mal die Interrupt-Prioritäten prüfen und so einstellen, das die Audio-Interrupte eine höhere Priorität haben als der Rest. (ARM CPUs wie die im Teensy haben typischerweise 8 - 16 Prioritätslevel für Interrupte)

Vielleicht hilft das hier weiter:


Ich komme zwar aus der Webprogrammierung, aber da sind ja asynchrone Techniken (React, Vue basierend auf AJAX) nicht mehr wegzudenken. Wer mit dem Teensy arbeitet, kommt auch nicht an Rolf's (@rolfdegen) Projekten vorbei und ich bin schon sehr beeindruckt, was er da im Alleingang entwickelt hat. Der Jeannie ist toll und den würde ich mir definitiv auch so ins Studio stellen.

Ich möchte ja erstmal einen kleinen Bass-Synth bauen, weil ich das alles sehr spannend finde. Die Idee einen Synth zu entwickeln habe ich seit vielen Jahren und mit dem Teensy habe ich eine Plattform, die mir das ermöglicht. Darin muss ich mich jetzt auch nicht völlig verlieren, bin aber sehr ehrgeizig und mag es knifflige Dinge zu lösen. Möglicherweise kann man auch Teile anderer Open-Source-Projekte übernehmen, wie es ja auch der Rolf macht - grundsätzlich möchte ich aber verstehen was passiert. Vor allem die Page-und Display-Programmierung ist reine Fleißarbeit - malste hier ein Rechteck, da eins, hier ne Linie. Aber das macht die Bedienung natürlich deutlich angenehmer.


Hallo Solar Chrome
Wenn du die Teensy Audio Lib von Paul Stoffregen benutzt, dürfte es bei der Datenkomunikation über den I2C Bus keine Unterbrechungen geben. Schau mal in die Links..

Link: https://forum.pjrc.com/index.php?threads/audio-cuts-out-when-updating-i2c-oled-display.67141/
Link: https://forum.pjrc.com/index.php?threads/teensy-4-1-i2c-issues-when-using-audio.72793/

Ich verwende ein ST7735s Farb-Display mit 160x128 Pixel und der Teensy ST7735 Bibiothek von Adafruit. Das Display wird alle 50msec über den SPI Bus per DMA Funktion mit Daten gefüttert. Wenn man zum Beispiel tft.print(text) an das Display sendet, sorgt ein 20KByte großer FrameBuffer dafür, dass diese Daten mit den anderen Schreibbefehlen in den Zwischenspeichern landen und alle 50msec per DMA als komplette Bildschirmseite an das Display gesendet werden. Der Vorteil von DMA liegt in der schnelleren Datenübertragung bei gleichzeitiger Entlastung der CPU.

Gruß Rolf

Danke Dir für Dein Feedback. Tatsächlich hatte ich schon mal im Code bei Dir reingeschaut. Ein SPI-Display wäre auch noch eine Idee. Bei I2C gibt es wenig Auswahl - meistens 128x64. Die beiden Threads schaue ich mir nochmal an. Die Idee mit dem Buffer klingt sinnvoll. Ich habe das sinngemäß aktuell so umgesetzt, dass die Display-Inhalt in Variablen festgehalten werden und beim nächsten NoteOn synchron mit ausgegeben werden.

Wenn ich nur die Encoder nutzt und die Ausgabe auf das Display unterdrücke, läuft alles prima in Echtzeit. Das Problem hängt also sehr sicher irgendwo am I2C-Bus/Display. Dafür nutze ich auch die Libs von Adafruit: Adafruit GFX und Adafruit SSD1306.

Was nutzt Ihr eigentlich als IDE? Die Arduino IDE oder Platform.IO?
Ich habe aktuell beides drauf. Platform.IO ist schon cool, die Arduino IDE scheint mir in sich aber etwas einfacher, aber stimmiger zu sein.
 
Ich komme zwar aus der Webprogrammierung, aber da sind ja asynchrone Techniken (React, Vue basierend auf AJAX) nicht mehr wegzudenken.
Perfekt, dann ist das für Dich ja nix Unbekanntes.
Vor allem die Page-und Display-Programmierung ist reine Fleißarbeit - malste hier ein Rechteck, da eins, hier ne Linie. Aber das macht die Bedienung natürlich deutlich angenehmer.
Ja, Benutzerinterface ist immer aufwändig. Oftmals steckt da auch Zeilenmäßig der meiste Code drinnen. Ist aber egal ob man Grafik-Display, Text Display oder einfach nur Leds, Buttons, Encoder oder Poties hat, bleibt trotzdem aufwändig. Vor allem sich ein sinnvolles Konzept zu überlegen, welche Funktionen man wie erreichbar macht. Da tue ich mich immer schwer mit. Ich bilde jede "Page" mit einer separaten Klasse ab. Übrigens danke für das Wort, das gefällt mir gut, bisher heißt bei mir als "Handler", werde ich gleich mal umbennen. Bis jetzt habe ich da mit virtuellen Funktionen gearbeitet um unterschiedliche Pages zu realisieren, im Moment probiere ich allerdings gerade mal mit "std::variant" herum. Damit kann man das auch schön mit abbilden.
Das Problem hängt also sehr sicher irgendwo am I2C-Bus/Display. Dafür nutze ich auch die Libs von Adafruit: Adafruit GFX und Adafruit SSD1306.
Ich habe mal kurz den Quellcode der Libs überflogen. Auf ersten blick scheint keine der beiden Bibliotheken selbst Interrupt zu verwenden oder zu blockieren. Ich denke die Probleme kommen daher wenn dann eher aus dem I2C Treiber selbst. Möglicherweise aber auch von der (seriellen) Konsole. (println und Konsorten) Das muss auch mit einem I2C Display ohne Störungen gehen. Wie gesagt, als erstes mal auf die Interrupte schauen. Da blockiert sich was gegenseitig. Man kann die Interrupt Prioritäten nachdem alles gestartet ist aucht mal auslesen mal auf die Konsole schreiben um mal drüber zu schauen (Achtung: der Code wird ne ganze menge ausgeben, eventuell muss da ein kleines sleep mit in die Schleife:

Code:
for(int i = 0; i < NVIC_NUM_INTERRUPTS; ++i)
{
  Serial.print("IRQ ");
  Serial.print(i);
  Serial.print(" Priority: ");
  Serial.println(NVIC_GET_PRIORITY(i));
}

Der SSD1306 Displaycontroller kann übrigens sowohl I2C als auch SPI interface. (Und auch 8-Bit Parallelen Bus) Je nachdem was für ein Display Du hast, ist das möglicherweise umschaltbar. (Die Pins dafür müssen rausgeführt sein, evtl reicht das ablöten eines Widerstands auf dem Display-Modul) Poste doch mal den genauen Typ des Displays(-Moduls).

Was nutzt Ihr eigentlich als IDE? Die Arduino IDE oder Platform.IO?
Ich habe aktuell beides drauf. Platform.IO ist schon cool, die Arduino IDE scheint mir in sich aber etwas einfacher, aber stimmiger zu sein.
Arduino IDE nutze ich nur, wenn ich mal ganz schnell was austesten will. Ich arbeite normalerweise direkt auf Basis der Toolchains/SDKs der jeweiligen MCU Hersteller (bei Espressif, RaspberryPI) oder wenn möglich ganz ohne Herstellerspezifische Toolchain/Frameworkpakete (bei AVR, WCH, Atmel ATSAM). Bei letzteren nutze ich meine eigenen Build-Scripte auf Basis von waf (https://waf.io/) Als Editor verwende ich VS Code, theoretisch tut es aber auch jeder einfache Text-Editor, nur etwas unkomfortabel. Früher habe ich Eclipse als Editor verwendet, das ist mir aber zu lahm. (Für ein berufliches Projekt mach ich gerade was mit einer Renesas MCU, da muss ich erstmal leider Eclipse nutzen, grauenhaft) Was ich noch nicht probiert habe, ist es den GDB Debugger mit VS Code zu verwenden. Da mir ein Lauterbach T32 zur Verfügung steht, war das bisher noch nicht nötig.
So habe ich die Kontrolle über viele Dinge, allerdings braucht es auch seine Zeit sich reinzuarbeiten. Für Neulinge ist das aber Nix, ich beschäftige mich beruflich seit fast 20 Jahren nicht nur mit Softwareentwicklung im Embedded Bereich sondern auch den ganzen Dinge Drumherum die dazugehören. Beim Espressif/ESP32 und RP2040 habe ich dann aber auch nicht alles selbst gemacht, dort nutze ich die mitgelieferten SDKs und dementsprechend CMake Build-Skripte. Da war mir dann die Zeit zu schade und ich wollte erstmal zum Ziel kommen.
 
Bei meinem Midi Sequencer (auf Basis eines ESP32) nutze ich VS Code, PlatformIO und die Espressif IDF. Für größere Projekte finde ich die Arduino IDE ungeeignet, da man schnell die Übersicht über den Code verliert.

I2C ist im Vergleich zu SPI wesentlich langsamer, daher muss man u.U. andere Techniken nutzen, um die Nachteile zu kompensieren. Anstelle von Bitmap Grafiken versuche ich mit eigenen Zeichensätzen zu arbeiten, ähnlich wie beim C64. Das spart unter Anderem auch wertvollen Arbeitsspeicher. Für ein rudimentäres UI reicht das oft aus und bringt viele weitere Vorteile.

Der große Vorteil beim ESP32 sind die Dual Core Fähigkeiten. Das komplette UI (Display, Taster und Encoder) läuft auf einem eigenen Core, so kommen die Timing relevanten Prozesse nicht mit dem UI in Berührung. Auf dem Teensy könnte man das UI in einen eigenen Thread packen.

Disclaimer: Ich bin aber auch eher auf Anfängerlevel unterwegs und muss mir alles mühsam aneignen. Dabei will ich auch gerne die Hintergründe nachvollziehen können und Dinge auf Low Level Ebene verstehen.

In dem Zusammenhang kann ich jedem Interessierten https://www.nand2tetris.org/ empfehlen. Ich habe mich einige Wochen durch den Kurs durchgearbeitet und nun ein viel besseres Verständnis wie Computer eigentlich funktionieren. Zudem macht der Kurs richtig Spaß!
 
Der große Vorteil beim ESP32 sind die Dual Core Fähigkeiten. Das komplette UI (Display, Taster und Encoder) läuft auf einem eigenen Core, so kommen die Timing relevanten Prozesse nicht mit dem UI in Berührung. Auf dem Teensy könnte man das UI in einen eigenen Thread packen.
Ja Dual Core ist nett. Ich mache hier gerade was mit dem RP2040, der hat auch zwei Cores, der eine macht nur Audio. Spannend wäre es auch mal mit dem i.MX RT600 zu arbeiten. Der enthält neben einen Cortex-M33 (300Mhz) einen Tensilica HIFI 4 DSP (600 Mhz) sowie ~4MB SRAM.
 
Boa, ich seid cool! Hätte nie gedacht, dass ich hier richtige Experten antreffe. Als Einsteiger ist das echt die halbe Miete.

Ich verwende ein Display von Keyestudio (gibts bei Eckstein). Im Prinzip habe ich mich nach dem Code aus dem Wiki orientiert und das lief auch auf Anhieb. Ich hatte mir das alles sehr kompliziert vorgestellt, war es aufgrund der guten Dokumentation aber gar nicht. Wenn SDI schneller ist, macht es vielleicht Sinn bei einem größeren Projekt (dem Drumsynth) so eines zu verwenden. Es sollte dann auch etwas größer sein.



Ja, Benutzerinterface ist immer aufwändig. Oftmals steckt da auch Zeilenmäßig der meiste Code drinnen. Ist aber egal ob man Grafik-Display, Text Display oder einfach nur Leds, Buttons, Encoder oder Poties hat, bleibt trotzdem aufwändig. Vor allem sich ein sinnvolles Konzept zu überlegen, welche Funktionen man wie erreichbar macht. Da tue ich mich immer schwer mit. Ich bilde jede "Page" mit einer separaten Klasse ab. Übrigens danke für das Wort, das gefällt mir gut, bisher heißt bei mir als "Handler", werde ich gleich mal umbennen. Bis jetzt habe ich da mit virtuellen Funktionen gearbeitet um unterschiedliche Pages zu realisieren, im Moment probiere ich allerdings gerade mal mit "std::variant" herum. Damit kann man das auch schön mit abbilden.

Ich komme ja vom Web und da ist immer alles eine Page und Views:schwachz:

Jetzt wo die Komponenten laufen, merke ich, dass ich nun wirklich auch ein Konzept für den Synth brauche - auch für einen kleinen Bass-Synth. Aktuell hat er 2 Osc., wobei der 1.Osc den 2.Osc stufenlos modulieren kann. Damit lassen sich auch schöne, schräge Sachen machen. Filter ist drin, ADSR ... LFO fehlt noch. Einen Spread-Parameter wollte ich noch einbauen, der die Osc gegeneinander verstimmt und einen Overdrive. Filterhüllkurve wäre ja eigentlich auch Pflicht ...

Sehr cool wäre es, wenn das GUI-Tool von Teensy auch eine Sound-Option hätte, bei der man den Synth vorhören könnte. So muss man natürlich die Struktur immer erst in den Code implementieren und auf den Chip bringen, ggf. Parameter auf die Encoder legen, um zu hören wie es klingt. Über MIDI geht das bisschen einfacher - da kann man fix mal einen Parameter auf MIDI CC legen und dann via Controller über die DAW steuern.

Naja, und wenn die Module dann soweit verschaltet sind und gut klingen, muss man das User-Interface entwickeln. Die Idee einen Encoder für die Page-Navigation zu nehmen (wie beim Jeannie) finde ich sehr gut. Je Page könnte man beim kleinen Bass-Synth 2 Parameter abbilden und die über die anderen beiden Encoder steuern. Über die Buttons könnte man event. Osc 1, 2 und Sys vorauswählen. Nice ist natürlich auch das Konzept vom Hydrasynth (abgeschaut vom ESQ1), der für jede Baugruppe einen Button hat. Man kommt man direkt zu den Pages der jeweiligen Baugruppe, wie Osc1, LFO, Filter etc.

Aber erstmal brauche ich paar Funktionen/Klasse, die die Pages und die Navi abbildet. Dabei denke ich insbesondere an Arrays.
Man könnte sicherlich irgendwo fertigen Code übernehmen, aber da fehlt mir Lerneffekt. Den ein oder anderen Tipp für den Anfang nehme ich aber sehr gern entgegen.


Disclaimer: Ich bin aber auch eher auf Anfängerlevel unterwegs und muss mir alles mühsam aneignen. Dabei will ich auch gerne die Hintergründe nachvollziehen können und Dinge auf Low Level Ebene verstehen.

Beruflich arbeite ich ja auf recht hoher Abstraktionsebene. Webservices befinden sich ja eher am anderen Ende der Level-Ebenen. Mich habe aber immer schon auch hardwarenahe Dinge fasziniert. Bisher fehlte mir aber der Zugang dazu. Mein Freund hat mich auch bisschen mit den embedded Sachen infiziert und Arduino ist eine tolles Plattform. Das erste Projekt nun soll auch dazu dienen alles mal kennenzulernen und zu verstehen.
 
Schau dir mal das Bedienkonzept des Micromonsta 2 an. Das ist für mich die Messlatte, wie man mit wenigen Buttons, Encodern und einem ordinären 16x2-Display ein Optimum an Usability rausholt.
 
Schau dir mal das Bedienkonzept des Micromonsta 2 an. Das ist für mich die Messlatte, wie man mit wenigen Buttons, Encodern und einem ordinären 16x2-Display ein Optimum an Usability rausholt.

Toller Synth. Bei dem Preis komme ja sogar ich ins Grübeln ...
Aber eigentlich wollte ich ja was Eigenes bauen und nicht schon wieder Gear kaufen. :selfhammer:


Ich habe jetzt begonnen an einer Pages-Klasse zu arbeiten, die die einzelnen Pages verwalten soll und auch die Zuordnung auf die Controller. Vorerst ganz simple als Textanzeige. Vier Zeilen habe ich ja beim Display.
 
Heute habe ich begonnen einen Benutzerführung/Navi zu implementieren. Bisher war alles nur eine lose Sammlung von Encodern und Buttons, die jeweils einen Parameter gesteuert haben. Um alle Parameter zugänglich zu machen, gibt es jetzt die Pages, die man mit dem rechten Button durchswitchen kann. Die beiden angezeigten Parameter sind dann jeweils an zwei Encodern einstellbar. Mit einem größeren Display ließen sich auch 3 Parameter darstellen.


1704348672351.png

Im nächsten Schritt möchte ich die finale Modulearchitektur aus dem GUI in den Code übertragen und die Parameter entsprechend mit den Pages verknüpfen. Auf Kurz oder Lang muss dafür eine eigene Klasse her, die auch unterschiedliche Datentypen vereinen kann. Momentan nutze ich nur ein Array, weil ich erstmal schauen wollte, wie es überhaupt funktionieren könnte. Das Rendern erfolgt beim NoteOn und NoteOff, weil es da keine Glitches gibt.
 
Was ist das für ein Display ?

Das ist ein Keyestudio I2C OLED Display mit 128x64 Pixeln.
In etwas wie hier: https://wiki.keyestudio.com/Ks0271_keyestudio_OLED_Display_OLED_Module

Der Code hat auch auf Anhieb funktioniert. Eigentlich war es nur ein Testkauf und ich hatte mich nochmal nach einer größeren Version umgschaut. Heute sind schon meine neuen Displays eingetroffen: 1,5" Zoll OLED I2C/SPI mit 128x128 Pixeln von Waveshare. Da geht schon bisschen mehr. Momentan klappt da aber leider nicht gar nichts.

Im Gegensatz zum I2C-Bus sind hier via SPI gleich 7 Pins (Teensy 4.1) zu verbinden:

VCC ---> 3.3V
GND ---> GND
DIM (MOSI) ---> Pin 11 (MOSI - optional Pin 26)
SCK ---> Pin 13 (SCK - optional Pin 27)
CS ---> Pin 10 (hatte testweise auch andere Pins)
DC ---> Pin 9 (hier ebenso)
RST ---> 8 (Reset ... hatte ich aauch testweise mal an Pin4)

Ich habe das nun mit der Adafruit SD1306-Lib probiert, als auch mit der SD1327, die für meine Begriffe exakt auf das Display passt. Ich konnte aber den ganzen Abend keinen Pixel zum leuchten bringen. So kompliziert hatte ich das nicht erwartet. Der Beispielcode von Waveshare selbst läuft nicht, als auch das Beispiel von Adafruit - es meldet nur, dass er das Display nicht initialisieren kann.

Das Problem ist auch, dass man keine Möglichkeit mal irgendwas zu prüfen. Gibt ja keine Logfiles oder sowas.
Vielleicht hat ja jemand einen Tipp?

.
.
.
.

Mit dem Triber https://github.com/olikraus/u8g2/ hat es sofort geklappt.
Das weiß man ja vorher immer nicht. Ich lass es trotzdem mal dokumentiert, falls jemand auf ein ähnliches Problem stößt.
Beim u8g2-Treiber kann man das Modell aus einer Liste wählen. Das ist schon sehr hilfreich.
Die Pins lassen sich aber trotzdem anpassen, wenn man eine andere Konfiguration hat.
 
Da jetzt schon mehrmals Leute gefragt haben, wie der Synth denn nun klingt, habe ich heute ein kleines (schrottiges) Video aufgenommen.

Die Soundengine als solche ist noch nicht fertig und wird weiter ausgebaut. Grundsätzlich soll der Synth aber 3 Osc haben, wobei der 3. Osc als Modulationsquelle für die beiden anderen dient. Rein von der Plattform habe ich jetzt erst etwas, womit man das richtig austesten kann. Die Implementierung der Nutzeroberfläche und Bedienung macht der viel Arbeit. Das funktioniert jetzt aber recht gut. Weitere Dinge folgen ...

 
Ich habe wieder eine Reihe von Fragen, die ich in den letzten Tagen gesammelt habe.
Insbesondere hoffe ich auf die Hilfe von @rolfdegen und @amesser.

1.) Das Timing haut immer noch nicht richtig hin. Der Vorgeschlagene Fraembuffer ist Teil der u8g2-Bibliothek. Den nutze ich nun auch. Veränderungen werden also erst in den Buffer geschrieben und bei NoteOn() auf dem Screen ausgegeben. Dabei nutze ich einen Flag-Parameter, der symbolisert, ob ein Wert geändert wurde und auch nur dann erfolgt die Aktualisierung. Mit Sicherheit ist das nicht die beste Herangehensweise. Solange der Synth im Abstand von 8tel Noten arbeitet, passt das so. Wenn ich aber 16tel ausgebe und dann an den Parametern drehen, entstehen wieder diese Verzögerungen.

An welcher Stelle und wie oft sollte man denn den Screen aktualisieren?


2.) Das zweite Problem betrifft auch nochmal das Display: Hin und wieder wird die Anzeige im Display verschoben. Wenn ich den Synth via DAW als Hardware-Insert einbinde, passieren da manchmal sehr seltsame Dinge im Display - entweder es verschiebt sich oder wird völlig kryptisch dargestellt. Manchmal kommt es, wenn ich die DAW öffne - oder andere Aktionen machen. Dann verschiebt sich der Screen nach unten und stellt das nicht mehr komplett dar. Ich erkenne hier keine richtige Systematik. Das Problem habe ich erst, seit ich das Audioshield nutze.

Ich hatte erst die Aktive DI im Verdacht, aber die scheint es auch nicht zu sein. Und damit kommen wir zu Problem


3.) Generell entstehen irgendwo Brummschleifen. Wenn das Display dabei ist, entstehen zusätzlich sehr störende Brummgeräusche. Wie werde ich die denn am besten los? Wie erwähnt hat die aktive DI zwischen Synth und Audiointerface seltsame Displayfehler verursacht. Mit der DI konnte ich allerdings die Störgeräusche völlig eliminieren. Irgendwas muss da also gehen.


4.) Ich überlege, ob ich event. auch die Oszillatoren vom Braids nutze. Da sind ja tolle Sachen dabei. @rolfdegen Du hattest das ja auch bei Dir implementiert. Sind dazu viele Anpassungen notwendig oder kann ich die Klassen (inkl. Dependencies) in mein Projekt einbinden und das unmittelbar nutzen?
 
Ein Schaltplan deiner Verdrahtung wäre sehr hilfreich. Es handelt sich bei den Störung vermutlich um eine schlechte Stromversorgung oder fehlende Stütz Elkos.

Zum Display kann ich nichts weiter sagen. Ich kenne den Treiber nicht. Man benötigt auch Zeit um den Framebuffer zu füllen bzw zu beschreiben.
Das sollte nie am Anfang eines Midi Empfangs sein. Am besten pollt man die Tastenabfrage und andere Funktionen mit einer festen Zeitkonstante.

Hier ein kleines Beispiel aus meinem Code:
C-ähnlich:
//*************************************************************************
// Main Loop
//*************************************************************************
FLASHMEM void loop(void)
{
    if (loopCount++ > 15)
    {
        checkSwitches();                // Buttons read
        checkPots();                    // update pots
        displayThread();                // update screen
        updateLEDstatus();                // update LEDs
        updateTempAudioUsage();            // update CPU Temp and AudioProzess usage
        update_Sound_mute();            // update audio mute
        updateLfo3FxMod();                // update LFO3 Modulation
        myPrgChange();
        myMidiBankSel();
        update_Sendsysex();
        loopCount = 0;
    }
    usbMIDI.read(midiChannel);             // USB Client MIDI
    MIDI.read(midiChannel);                   // MIDI 5 Pin DIN
    encoder.tick();                     // Encoder read        
    EncoderQuery();
    polySequencer();
}





//*************************************************************************
// Display Thread
//*************************************************************************
void displayThread()
{
    const uint16_t Time_1 = 40; // render pages
    const uint16_t Time_2 = 50; // Screen refresh
    if ((millis() - Disp_Timer1) > Time_1)
    {
        tft.waitUpdateAsyncComplete();  // - Wait for any active update to complete
        if (PageNr == 0)
        {
            vuMeter = false;
            VoicLEDtime = 85;    // note off time for LED symbols
            render_Scope = true; // render scope line
            clear_scope_line();
            enableScope(true);
            renderCurrentPatchPage(); // render menu page
        }
        else if (PageNr < 96)   // PageNr < 96 - without Save/Load menu
        {
            if (renderPageFlag == true)
            {
                renderCurrentPatchPage(); // render menu page
                renderPageFlag = false;
            }
            vuMeter = true;
            enableScope(false);
            MidiSymbol();
            renderPeak();
            drawVoiceLED();
            SysExRecTimer();
            drawProgressbar();
         
            VoicLEDtime = 85;   // note off time for LED symbols
            // Time for green SUB Page Marker
            if ((millis() - Blinki_Timer) > blinkiTime)
            {
                blinkiTime = 250;
                Blinki_Timer = millis();
                drawSubPageInfo();
            }
        }
    }
    if ((millis() - Disp_Timer1) > Time_2)
    {
        tft.updateScreenAsync(false); // one-time update     
        Disp_Timer1 = millis();
    }
}
 
Zuletzt bearbeitet:
4.) Ich überlege, ob ich event. auch die Oszillatoren vom Braids nutze. Da sind ja tolle Sachen dabei. @rolfdegen Du hattest das ja auch bei Dir implementiert. Sind dazu viele Anpassungen notwendig oder kann ich die Klassen (inkl. Dependencies) in mein Projekt einbinden und das unmittelbar nutzen?

Einige Anpassung im Braids Code sind schon erforderlich, da die Oszillator Engine von Paul Stoffregen anders aufgebaut ist. Hilfestellung ist kein Problem. Hab das auch auf meiner github Seite dokumentiert.
 
Zuletzt bearbeitet:
Moin ;-) Also erstmal vorab, die "Probleme" mit denen du kämpfst kosten Entwicklern die meisten Haare, gehören aber zum Tagesgeschäft :D

Erstmal zum Display. Ich mache das ehrlich gesagt je nach Anwendungsfall unterschiedlich. Ich habe z.B. bei einem Radiowecker ein Grafikdisplay dran. Dort baue ich bei Änderungen das Bild erst im Framebuffer zusammen und lasse den per DMA über das SPI ausgeben. Bei meinem Delay-Modul mache ich das anders, dort aktualisiere ich mein "Display" (Eigentlich 4x9 RGB Leds) periodisch mit einer Frequenz von ca 100Hz, also alle 10ms unabhängig von Änderungen. Dort berechne ich je 9 Leds vor und schiebe die dann händisch per I2C raus. Ich denke immer noch, das es bei Dir ein Problem mit den Interrupten gibt. Soweit ich die Teensy Audio-Bibliothek kenne, werden die Samples dort Blockweise jeweils in der DMA Interrupt Routine neu berechnet. D.h. es werden immer x Samples (x wird in der größenordnung von 32 bzw 64 sein) vorberechnet und diese dem DMA Controller übergeben so das diese ausgegeben werden. Vermutlich wird das mit Doppelpuffern arbeiten - Einer wird ausgegeben während der andere neu berechnet wird. Wenn diese DMA Interrupt Routine jetzt nicht drankommt, weil z.B. die Interrupte gesperrt sind oder gerade eine Interrupt Routine mit gleicher oder höherer Priorität abgearbeitet wird, dann kann es passieren, das der DMA Controller alle Samples an den DAC ausgegeben hat, bevor der andere Puffer komplett berechnet wurde. Und dann holperts. Du kannst ja mal Deinen Projekt/Quellcode zur Verfügung stellen, dann schaue ich gerne mal drüber. Ich habe hier leider nur einen Teensy 3.2, der ist mit dem 4er nicht ganz vergleichbar, sonst könnte ich Dein Projekt hier auch mal austesten.

Das Problem mit den verschoben Anzeige im Display kann mehrere Ursachen haben:
  • Es treten Störungen bei der Datenübertragung vom Controller zum Display auf. Das SPI wird sicher mit ein paar MHz laufen. Wenn die Schaltflanken des SPI Taktsignals nicht sauber beim Display ankommen, dann "merkt" das Display manchmal nicht das bereits das nächste Pixel an der Datenleitung liegt und schaltet nicht weiter. Im Prinzip verlierst Du dann Takte. Dann verschieben sich die Pixel, weil an die Datenleitung das nächste Pixel kommt, das Display aber nocgh beim vorherigen ist. Gerade diese frei fliegende Verdrahtung kann dazu führen, vor allem dann wenn es keinen vernünftigen "Rückweg" gibt. Hier wäre erstmal die Masseführung zu prüfen. Es sollte ein "GND" Kabel direkt vom Display zurück zum Teensy geben. Am besten man verdrillt Masse und Taktleitung sogar. Diese Steckbretter sind dann leider auch nicht ganz unproblematisch: Dummerweise bilden die Kontaktreihen der Steckbretter kleine Kondensatoren mit den Nachbarreihen. Diese zusätzlichen parasitären Kapazitäten dämpfen das SPI Signal ebenso und "verschmieren" die Schaltflanken. Abhilfe könnte da auch ein langsamerer SPI Takt bringen. Dann braucht allerdings die Übertragung des Framebuffers an das Display länger. Muss man rechnen ob es reicht.
  • Vielleicht stimmt auch das Timing an der SPI Schnittstelle generell nicht. Jedes Display hat eine "maximale" Geschwindigkeit bzw. minimalen Zeiten zwischen Signaländerungen mit denen es betrieben werden kann. Wenn man das nicht beachtet, kann es zu allen möglichen Artefakten kommen
Die Störgeräusche vom Display könnten ebenfalls von der Verkabelung her kommen. Die Masseführung ist hier sehr wichtig, die sollte Sternartig erfolgen. D.h. in der Mitte der Teensy. Encoderblock, Tastenblock, Display und Audio-Shield bilden jeweils ein "Spitze des Sterns". Es darf keinen (mindestens bei der Masse und Betriebsspannung) Leitung geben welche von einer Spitze zur nächsten geht, die Leitung müssen am Teensy verbunden werden. Dazu kommt dann noch, das während die Pixel an das Display übertragen werden, das SPI Taktsignal und auch -Datensignal selber einen "Ton" erzeugen. Das SPI Taktsignal ist meist unhörbar, das ist ein sauberes Rechteck im MHz Bereich. Das Datensignal jedoch, naja. Nehmen wir an man gibt in einer Zeile nur 1 Leuchtendes Pixel aus und 127 Dunkle. Dann liegt am am SPI Daten Signal für 1 Pixel "3.3V" und für den Rest "0V" Damit hat man einen Taktteiler 128:1 gebaut und an der Datenleitung liegt quasi SPI Clock / 128 als Frequenz an. Bei einer SPI Takt Frequenz von 1.2 MHz hat man dann an der Datenleitung ein schönes 10kHz Signal. Im Prinzip kann man den Displayinhalt hören :) Dagegen hilft nur eine gute Signalführung. Bei so einer fliegenden Verdrahtung würde ich als erstes mal alle Kabel zu jedem "Sternspitze" verdrillen. Man könnte auch noch ALU Folie testweise drumwickeln.

Es kann aber auch sein, das die Störgeräusche vom OLED Display selbst kommen. Displays, insbesondere OLED Display haben auf der Platine Spannungswandler, weil die zum Betrieb viel höhere Spannungen brauchen als die 3.3V. Dieser Spannungswandler, kann je nachdem wie er ausgeführt wurde auch massiv Störgeräusche erzeugen. Um das Auszuschließen könntes Du mal eine Art Schachbrettmuster auf dem Display ausgeben, dann das Program in einer Endlosschleife halten, so das nix mehr über das SPI läuft und mal prüfen ob es dann immer noch Störgeräusche gibt.

Uff, sorry, das war jetzt viel Text und viel Theorie aufeinmal. Hoffe das ist nicht zu anstrengend.
 
Zuletzt bearbeitet:
2.) Das zweite Problem betrifft auch nochmal das Display: Hin und wieder wird die Anzeige im Display verschoben.
Ein Schaltplan deiner Verdrahtung wäre sehr hilfreich. Es handelt sich bei den Störung vermutlich um eine schlechte Stromversorgung oder fehlende Stütz Elkos.

Ich hatte mal ein ähnliches Problem mit einem OLED, immer wieder fehlerhafte Darstellungen. Da war es der Optokoppler am Midi Interface der mit einem Kondensator zwischen Masse und VCC gestützt werden musste.
 
Danke für Eure Hinweise. :kussi:
Ich werde den einzelnen Punkten nochmal auf den Zahn fühlen. Bisher hatte ich noch nie mit solchen Hardware-Projekten zu tun, aber es macht irre Spaß und mir ging es ja darum das alles mal kennenzulernen. Der Drumsynth steht auch noch auf dem Plan. Die Infrastruktur vom ersten Projekt kann ich dann schon mal übernehmen.
 
Heute habe ich noch ein paar Updates am Code vorgenommen und ein wenig mit der Display-Bibliothek (u8g2) herumgespielt.

Zumindest läuft es schon mal, dass er das Display nur aktualisiert, wenn etwas verändert wurde. Ich habe auch probiert die Aktualisierung nicht mit dem NoteOn() zu synchronisieren und das resultierte wieder in Sounddelays. Vielleicht habe ich auch einen grundsätzlichen Denkfehler. Den Code stelle ich gern zur Verfügung, wenn ich da ein wenig aufgeräumt habe. Das Projekt ist ja ursprünglich aus den ersten "Hello-World-Programmen" entstanden und hat auch keine echte Strukturierung. Sowas würde ich nie fürs Sharing rausgeben, aber es dient ja Lehrzwecken.

Die Synthparameter sind "freifliegend" in Variablen gespeichert. Irgendwann hatte ich ein Array für die Displayausgabe reingenommen und später auch ein Array für das Encodermapping (0-127). Ursprünglich wollte ich das als Klasse definieren, um die unterschiedlichen Datentypen bestmöglich abzubilden. Als PHP-Coderin bin ich da allerdings etwas verwöhnt und wollte die Zeit nicht investieren, um erstmal Datentypen und Klassen unter C++ aufzufrischen. In PHP schreibe "echo $variable;" und da ist es egal, ob String, Int, etc. ... in C++ muss ich ja einen Int-Wert erst als String casten ... dauert halt alles.

Mehr oder weniger fliegt mir das alles gerade bisschen um die Ohren. :pcsuxx:
Wenn ich erstmal alle Encoder-Parameter bewegt habe, sind Encodermap und freie Variablen recht gut miteinander synchronisiert. Das sollte aber auch aus dem Stand gehen. Da fehlt es noch an einer gescheiten Routine - eben, weil einige Werte als Maximalwert 1.0 haben, anderen dann irgendwas in Hz oder Millisekunden. In die Encodermap muss also noch eine Vorschrift, wie ich den Originalwert aus dem Encoderwert (0-127) ermitteln kann.

Ich bin aber dran!
Schaltungsplan mache ich auch gern fertig. Das OLED-Display ist von Wavehshare mit 128x128 Pixeln.
Der Rest ist Standard-Kram.



1705205987694.png
 
Den Code stelle ich gern zur Verfügung, wenn ich da ein wenig aufgeräumt habe. Das Projekt ist ja ursprünglich aus den ersten "Hello-World-Programmen" entstanden und hat auch keine echte Strukturierung.
Mach Dir keinen Kopf, bei meinen Privatprojekten ist die Codequalität auf Deutsch gesagt "unter aller Sau". Beruflich ist das anders, aber da habe ich mehr Zeit bzw. es wird ja bezahlt. Wobei wenn ich an so einige Supportcalls von Kunden denke und mich an deren Quellcode erinnere... Eieiei, die haben nicht mal die Funktionen in unseren Beipielen umbenannt, da hat dann in deren Produktcode alles noch mit "Example_XYZ..." angefangen. Da wird einem ganz anders wenn man mit einem mehrere Tonnen Roboterarm in der Zelle steht.
 
Erstmal zum Display. Ich mache das ehrlich gesagt je nach Anwendungsfall unterschiedlich. Ich habe z.B. bei einem Radiowecker ein Grafikdisplay dran. Dort baue ich bei Änderungen das Bild erst im Framebuffer zusammen und lasse den per DMA über das SPI ausgeben. Bei meinem Delay-Modul mache ich das anders, dort aktualisiere ich mein "Display" (Eigentlich 4x9 RGB Leds) periodisch mit einer Frequenz von ca 100Hz, also alle 10ms unabhängig von Änderungen. Dort berechne ich je 9 Leds vor und schiebe die dann händisch per I2C raus. Ich denke immer noch, das es bei Dir ein Problem mit den Interrupten gibt. Soweit ich die Teensy Audio-Bibliothek kenne, werden die Samples dort Blockweise jeweils in der DMA Interrupt Routine neu berechnet. D.h. es werden immer x Samples (x wird in der größenordnung von 32 bzw 64 sein) vorberechnet und diese dem DMA Controller übergeben so das diese ausgegeben werden. Vermutlich wird das mit Doppelpuffern arbeiten - Einer wird ausgegeben während der andere neu berechnet wird. Wenn diese DMA Interrupt Routine jetzt nicht drankommt, weil z.B. die Interrupte gesperrt sind oder gerade eine Interrupt Routine mit gleicher oder höherer Priorität abgearbeitet wird, dann kann es passieren, das der DMA Controller alle Samples an den DAC ausgegeben hat, bevor der andere Puffer komplett berechnet wurde. Und dann holperts. Du kannst ja mal Deinen Projekt/Quellcode zur Verfügung stellen, dann schaue ich gerne mal drüber. Ich habe hier leider nur einen Teensy 3.2, der ist mit dem 4er nicht ganz vergleichbar, sonst könnte ich Dein Projekt hier auch mal austesten.

Paul Stoffregen (Entwickler der Teensy Audio Lib) schreibt dazu...

Die Audiobibliothek verfügt über zwei Arten von Interrupts: hohe und niedrige Priorität.

Die Interrupts mit hoher Priorität gelten für DMA und andere Hardware. Jedes Hardwareobjekt ist anders. Einige unterbrechen möglicherweise häufiger als alle 128 Proben. Diese Interrupts mit hoher Priorität haben eine sehr kurze Dauer.

Der Interrupt mit niedriger Priorität wird alle 128 Samples ausgeführt. Es wird durch einen der Interrupts mit hoher Priorität ausgelöst, weshalb alle Designs mindestens ein Hardware-Eingangs- oder -Ausgangsobjekt haben müssen. Der Interrupt mit niedriger Priorität erledigt die gesamte CPU-intensive Arbeit. Die Idee besteht darin, zuzulassen, dass die hardwarebasierten Interrupts und Interrupts für USB-, serielle und andere Bibliotheken, die Sie verwenden, weiterhin ordnungsgemäß funktionieren, während die Audiobibliothek ihre DSP-Arbeit erledigt.


Falls ein Scope vorhanden, sollte man überprüfen, wieviel Zeit die Aktualisierung und Ausgabe auf dem Display benötigt wird. Der "Midi NoteOn" Befehl benötigt etwas weniger als 1ms. Wenn man z.B einen Sequenzer hat und Midi Noten mit 120 BPM sendet, hat man zwischen den empfangenen Midi Noten ca. 499ms Zeit etwas anderes zu tun. Da ist Zeit genug um ein Display anzusteuern. Auch ohne Framebuffer.
 
Zuletzt bearbeitet:
Möglicherweise unterbricht die SPI, I2C, USB und/oder UART Isr zu lange die Berechnungs Isr von der Soundbibliothek.
 
Paul Stoffregen (Entwickler der Teensy Audio Lib) schreibt dazu...

Die Audiobibliothek verfügt über zwei Arten von Interrupts: hohe und niedrige Priorität.

Die Interrupts mit hoher Priorität gelten für DMA und andere Hardware. Jedes Hardwareobjekt ist anders. Einige unterbrechen möglicherweise häufiger als alle 128 Proben. Diese Interrupts mit hoher Priorität haben eine sehr kurze Dauer.

Der Interrupt mit niedriger Priorität wird alle 128 Samples ausgeführt. Es wird durch einen der Interrupts mit hoher Priorität ausgelöst, weshalb alle Designs mindestens ein Hardware-Eingangs- oder -Ausgangsobjekt haben müssen. Der Interrupt mit niedriger Priorität erledigt die gesamte CPU-intensive Arbeit. Die Idee besteht darin, zuzulassen, dass die hardwarebasierten Interrupts und Interrupts für USB-, serielle und andere Bibliotheken, die Sie verwenden, weiterhin ordnungsgemäß funktionieren, während die Audiobibliothek ihre DSP-Arbeit erledigt.


Falls ein Scope vorhanden, sollte man überprüfen, wieviel Zeit die Aktualisierung und Ausgabe auf dem Display benötigt wird. Der "Midi NoteOn" Befehl benötigt etwas weniger als 1ms. Wenn man z.B einen Sequenzer hat und Midi Noten mit 120 BPM sendet, hat man zwischen den empfangenen Midi Noten ca. 499ms Zeit etwas anderes zu tun. Da ist Zeit genug um ein Display anzusteuern. Auch ohne Framebuffer.

Die Displayausgabe an sich funktioniert ja verzögerungsfrei. Wenn man aber an einem Encoder dreht, aktualisiert sich der Wert ja vorlaufend auch auf dem Display und unterbricht via Interrupt möglicherweise auch alles andere. Bis zu einer 8tel Note funktioniert auch das prima, aber sobald ich 16tel Noten mit dem Teensy verarbeite, packt er das nicht mehr. Ich hatte auch schon überlegt eine Art Displaysperre von 200ms o.ä. nach einer Aktualisierung zu verhängen. Dann schaut das vielleicht etwas ruckeliger beim Encoderwert aus, aber das Audiosignal bleibt erhalten.

Mit den Interrupts muss ich mich noch genauer beschäftigen.
Gibt es dazu eine Übersicht o.ä. etwas zur Praxis damit?


Eine Frage zu den Encodern:

Mit ist auch aufgefallen, dass ich die Encoder nur an den vorderen Pins zum Laufen gebracht habe. Aktuell habe ich die drei Encoder, die an Pin0/1, Pin2/3 und Pin4/5 hängen. Ich habe mehrmals versucht die hinteren Pins vom Teesny 4.1. zu nutzen, aber da funktionieren sie nicht. Mit Display und Taster ging das prima. Da das Audioshield die Pins 0-5 nicht verwendet, funktionierte das aber trotzdem so. Ich habe auch nirgends etwas dazu gefunden, ob das Audioshield die benötigten Pins allein für sich beansprucht - ich gehe aber davon aus.

Mir stellt sich generell auch die Frage, wie ich dann z.B. 4 Encoder ansteuern soll oder mehr?
Je Encoder benötige ich ja zwei freie Pins - und wahrscheinlich funktionieren nicht alle digitalen Pins dafür.

Signale beim Audioshield 4.x

1705272773822.png
 
Ich muss völlig verrückt sein! :selfhammer:

So, habe mir nochmal das Display als solche vorgenommen. Die u8g2lib nutzt auch der Götz aus den Video-Tutorials und so kam ich nochmal drauf. Es gibt da nämlich einen entscheidenden Unterschied bei den Treibern. Einmal gibt es den frei konfigurierbaren Software-Mode und dann den Hardware-Mode, der auf die dedizierten Pins zugreifen und deutlich schneller arbeitet. Testweise habe ich das Audioshield nochmal abgeklemmt und das allein getestet.

Mit dem Hardware-Mode läuft das Display deutlich flüssiger und verursacht selbst bei 32tel Noten keine Soundprobleme mehr. Genauso muss und soll das sein. So lässt sich der Synth richtig gut bedienen. Problem ist nur, dass Pin 10,11,12 bereits vom Audioshield benötigt werden. Irgendwie muss ich also die SPI-Kommunikation auf die höheren Pins von SPI1 verschieben. Eigentlich geht das mit der u8g2, weil dafür ein anderer Constructor aufgrufen werden muss. Irgendwie funktioniert das dann aber nicht.

Weiß nicht, ob von Euch jemand die u8glib oder u8g2lib nutzt oder generell schon mal mit den höheren Pin-Anschlüssen an einem Display gearbeitet habt.
Vielleicht hat jemand eine Idee? Habe mich auch nochmal direkt an den Entwickler gewendet.

Wir kommen aber der Lösung näher! :weich:
 


Zurück
Oben