Trio VST/AU Synthesizer

Bald ist es soweit, ein lang ersehnter Wunsch von mir wird Wirklichkeit: Mein erster selbstgeschriebener Softwaresynthesizer.

Ich habe ihn "Trio" getauft, weil er mit drei Oszillatoren daherkommt. Im Moment gibt es den Synth nur als AU, geplant sind noch VST und evtl. LADSPA für Linux.

Ein paar technische Daten:

  • 3 Oszillatoren : Sägezahn, Sinus, Rechteck
  • 2 LFOs mit 3 Wellenformen
  • 12/24 dB Tiefpass Filter
  • 2 ADSR (Filter/Amp)
  • 2 Fach Modulatiosmatrix mit LFO->Osc Pitch, LFO->Filter Cutoff, LFO -> Filter Resonanz

Hier ein erstes Klangbeispiel:

Aktuell funktioniert schon fast alles:

- Parameter lassen sich alle voll automatisieren und per MIDI-Learn ansprechen
- Patches können gespeichert und geladen werden
- Mehrere Instanzen kommen sich nicht mehr ins Gehege

Als nächstes steht an:

- Sinus und Rechteck Oszillator
- Die restlichen LFOS
- Verdrahten der Modulationsmatrix

Als Schmankerl zum Schluss werde ich dann noch einen schönen Patch Browser bauen.

Ein paar Glitches habe ich auch noch:

- Tiefe Töne erzeugen noch Aliasing Artefakte
- Die Wertebereiche der Envelopes sind noch nicht handlich genug
- Memory leaks

Immerhin kann man damit schon spielen und Musik machen:

TRIO in der DAW

TRIO in der DAW

Innerhalb der nächsten zwei Wochen werde ich wohl zum Alpha Stadium kommen. Das UI ist auch noch nicht final, da werde ich noch die ein oder andere Stunde drüber hirnen müssen. 😉


Support your local dealer - Nur wofür?

Die Frage stelle ich mir gerade ernsthaft.

Ich habe mich letzte Woche dazu hinreissen lassen, mir neue Studiomonitore zu kaufen. Nach ausgiebigen Recherchen habe ich mich für die Adam A3X entschieden, aber das soll nicht das Thema dieses Beitrages sein. Vielmehr soll es um die Frage gehen, ob es sich lohnt, beim Händler vor Ort zu kaufen.

Die Boxen habe ich, wie gesagt, letzte Woche erworben. Ich bin höchstpersönlich in Freiburgs einzigen Musikfachhandel gefahren, wer das ist, könnt Ihr ja gerne selber googeln. Ich halte mich an dieser Stelle mit Namensnennung mal zurück. Natürlich habe ich ausgepackte Ware bekommen, also genau die, die als Vorführgerät im Vorführraum stand. Der Boden war leicht zerkratzt vom Herumschieben. Also dachte ich, es gäbe zumindest ein wenig Rabatt, oder wenigstens ein Kabel dazu. Am Ende habe ich für Vorführware auch noch 20 Euro mehr bezahlt als bei Thomann. Dafür konnte ich die Dinger wenigstens gleich mitnehmen. Dazu habe ich noch ein paar völlig überteuerte Kabel mitgenommen. Wenigstens habe ich den Unterschied zwischen unsymmetrischen und symmetrischen Leitungen gelernt. Ach und die netten Leute vom Fachhandel haben mir auch noch 8 Schaumstofffüße zum Aufkleben mitgegeben, ist das nicht reizend?

Ansonsten fing alles gut an, die Boxen klangen erwartungsgemäß großartig. Leider habe ich nach kurzer Zeit feststellen müssen, daß eine der beiden Boxen stark rauscht. Später kamen auch noch Störgeräusche hinzu. Was folgt, ist klar: Boxen wieder einpacken, zum Händler bringen und einschicken lassen. Prima!

Ich hätte an dieser Stelle zumindest erwartet, daß ich ein leihweise ein Ersatzgerät mitnehmen kann, von mir aus auch gegen Gebühr. Pustekuchen, der arme Händler hat nichtmal irgendwelche gebrauchten Studiomonitore rumstehen, die er bei Bedarf an Kunden verleihen kann. Irgendwas haben die da nicht ganz richtig verstanden. Bin gespannt, wann ich die defekte Box wieder abholen kann. Meint doch der Mann im Laden ernsthaft, mit einer Box käme man ja auch ganz gut aus. Aha.

Um das Mal zusammenzurechnen:

  • 2 Boxen, das Stück 269 Euro
  • 2 Kabel jeweils 1,5m XLR auf Klinke Symmetrisch, Stück 19 Euro
  • 3 x hin und zurück von Waldkirch nach Freiburg, entsprechend 3 x 44km mit 33cent pro km, also knapp 44 Euro.

In Summe sind das 620 Euro. Meine Fahrtzeit und die Zeit die man im Laden rumstehen muss, weil die Bude chronisch unterbesetzt ist, nicht mit eingerechnet. Was hätte mich das Ganze nun gekostet, wenn ich gleich bei Thomann bestellt hätte?

  • 2 Boxen, das Stück 259 Euro
  • 2 Kabel jeweils 1,5m XLR auf Klinke Symmetrisch, Stück 13,50 Euro

Das sind zusammen 545 Euro. Und falls die Dinger kaputt gewesen wären, hätte ich sie kostenlos bei der nächsten Postfiliale wieder abgeben können.

Ich habe also für weniger Service, einen deutlich höheren Zeitaufwand und ganz viel Ärger 75 Euro mehr bezahlt. Wie blöd muss man eigentlich sein???

Und Ihr liebe Inhaber des nicht näher genannten Fachhandels seht mich nur noch einmal, nämlich genau dann, wenn ich die hoffentlich reparierte Box wieder abhole.


Kapazität messen mit dem Arduino

In dieser Folge beschäftigen wir uns mit dem Messen von Kondensatoren, also Kapazitäten mit dem Arduino. Ich experimentiere schon seit einiger Zeit damit herum und habe vor ein paar Wochen günstige Arduino-Nano Clones aus China bekommen, mit denen ich nun ein kleines Projekt realisieren wollte.

title_image

Das Messen von Kapazitäten ist hierfür geradezu ideal. Nur wie misst man diese eigentlich? Dazu benötigen wir zunächst ein wenig Theorie, aber keine Angst, es hält sich in Grenzen.

Legt man an an einen Kondensator über einen Vorwiderstand eine Gleichspannung an, so lädt sich dieser nach einer bestimmten Zeit fast vollständig auf die angelegte Spannung auf. Gleichzeitig bezeichnet man diese Art der Schaltung schon als RC-Glied, oder Tiefpass. Die Eigenschaften als Tiefpass wollen uns an dieser Stelle aber nicht weiter interessieren.

RC-Glied

RC-Glied

Nun können wir einige Überlegungen anstellen.

Aus der Elektrotechnik wissen wir schon etwas über die Zeitkonstante "Tau", diese berechnet sich folgendermaßen:

tau = R*C

Diese Zeitkonstante besagt nach welcher Zeit sich der Kondensator auf ca. 63% der angelegten Spannung aufgeladen hat, das können wir also gut gebrauchen. Wie kommen wir denn aber nun auf die Einheit Sekunden? Ganz einfach, die Kapazität in Farad gemessen lässt sich auch schreiben als:

F = frac{A * s}{V}

Und der Widerstand in Omega gemessen ist natürlich laut dem Ohm'schen Gesetz frac{U}{I} also in Einheiten ausgedrückt frac{V}{A}.

Wir erhalten also die Einheit für R*C:

R * C = frac{A * s * V}{V * A}

Ampere(A) und Volt(V) kürzen sich raus und es bleibt nur noch die Sekunde, so ein Zufall!

Rein interessehalber schauen wir uns noch die vollständige Formel an, nach der sich die Spannung U an einem Kondensator zu einem beliebigen Zeitpunkt berechnen lässt, sozusagen die Ladekennlinie des Kondensators:

U(t) = U_{rm max} cdot (1 - e^{-frac{t}{tau}})

Das können wir uns auch mal als Grafik ausgeben, dann wird der Zusammenhang gleich viel klarer (Klicken zum Vergößern).

RC-Glied

RC-Glied

Die beiden gestrichelten Linien zeigen den Punkt an dem sich der Kondensator auf 63% der angelegten Spannung aufgeladen hat, also genau nach 1*Tau (reimt sich).

Natürlich verhält sich ein RC Glied in der Realität fast genauso. Im folgenden Bild habe ich ein RC-Glied mit 100nF und 1kOhm an eine Rechteckspannung mit ca. 250HZ angeschlossen und die Spannung am Kondensator gemessen, hier lässt sich der Lade -und Entladevorgang sehr gut beobachten:

RC-Glied an 250Hz

RC-Glied an 250Hz

Jetzt haben wir unser Handwerkszeug zusammen und können unsere Schaltung entwerfen.

Grundsätzlich müssen wir also einen Kondensator über einen Vorwiderstand aufladen und die Zeit messen, die benötigt wird, um den Kondensator aufzuladen. Weiterhin müssen wir den Kondensator auch entladen können. Also benötigen wir einen Widerstand zum Laden und einen zum Entladen, weiterhin müssen wir den ADC des Arduino bemühen, um die Spannung am Kondensator zu messen. Diese kann natürlich niemals höher als die Betriebssspannung des Arduino werden.

Um die ganze Sache abzurunden, benötigen wir ein Display, in diesem Falle ein HD44780 kompatibles 16x2, um die Messung anzuzeigen und einen Taster, um den Messvorgang starten zu können.

Das führt uns dann zu folgender Schaltung:

Schaltplan für den Kapazitätsmesser

Schaltplan für den Kapazitätsmesser

Der Arduino lädt nun über Pin D13 den Kondensator und misst die Zeit, die dafür benötigt wird. Die Messung der Spannung erfolgt am Pin A0 und die Entladung wird über Pin D10 vorgenommen. An JP3 wird dann der Taster zum Starten der Messung angeschlossen. JP2 dient der Spannungsversorgung, hier werden 5V angeschlossen. Alternativ kann die Schaltung auch über die USB-Buchse des Arduino versorgt werden.

Auf dem Steckbrett aufgebaut könnte das so aussehen:

Schaltung auf dem Steckbrett

Schaltung auf dem Steckbrett

Die eigentliche Messroutine ist erschreckend einfach:

	digitalWrite(chargePin, HIGH);  // set chargePin HIGH and capacitor charging
	startTime = millis();

	/* Measure Capacity */
	while (analogRead(analogPin) < 648){       // 647 = 63.2% of 1023, which corresponds to full-scale voltage 
	}

	elapsedTime = millis() - startTime;
	if (elapsedTime > 1) {  //output only if time was measured

		// convert milliseconds to seconds ( 10^-3 ) and Farads to microFarads ( 10^6 ),  net 10^3 (1000)  
		microFarads = ((float)elapsedTime / resistorValue) * 1000;
        }

Hier wird der Ladepin (D13) auf High Pegel, also 5V gesetzt. Wir starten die Zeitmessung und der Ladevorgang beginnt. Der ADC vom Arduino hat eine Auflösung von 10 Bit, also 1024 Schritte. Somit entsprechen 63.2% etwa frac{5V * 0.632}{5V / 1024} = 647 Schritten.

Warten wir also so lange, bis dieser Wert am ADC erreicht ist, können wir anschliessend die Ladezeit ablesen und unsere Kapazität berechnen (Zeile 12). Dann kann der ermittelte Wert auf dem Display ausgegeben werden.

Der ursprüngliche Source Code stammt von Paul Badger, wurde vom Make Magazin erweitert und von mir noch für das LCD und die Tastensteuerung modifiziert,

Das ist eigentlich auch schon die ganze Zauberei. Natürlich kann man die Schaltung noch verbessern, so lassen sich mit dem 10k Ladewiderstand keine kleinen Kapazitäten < 1µF messen. Hier wäre es wünschenswert die noch freien Pins zu verwenden und den Ladewiderstand automatisch umzuschalten.

Vielleicht schauen wir uns in einer der späteren Folgen an, wie man diese Schaltung auf einer Platine aufbaut und in ein schickes Gehäuse einbaut.

Alle Projektdateien, Quelltexte usw. finden sich hier. Viel Spass beim Nachbauen.


Matlab und das Picoscope

Was tut man, wenn man ein Picoscope 3203D besitzt und Messwerte von dort in Matlab einlesen (und sich deswegen extra die Instruments Toolbox gekauft hat :()möchte?

Die kurze Antwort: das geht so erstmal nicht so einfach, weil dieses Modell zur Zeit (noch) nicht unmittelbar unterstützt wird. Zumindest habe ich es noch nicht zum Laufen bekommen.

Die lange Antwort: Es gibt zum Glück einen einfachen Workaround!

Die Picoscope Software kann zum Glück Messdaten im Matlab Format speichern. Hierzu zeichnet man ganz normal sein Eingangssignal auf und speichert diese Wellenform dann mit "Datei -> Wellenform speichern unter" als Matlab-4 Datei.

Picoscope Triangle

Picoscope Messung mit Dreiecksspannung

Picoscope speichert dann 32 Wellenformen mit jeweils 1000000 Samples. Wir benötigen zum Matlab Import erstmal nur eine davon und kopieren diese in unser Matlab Arbeitsverzeichnis. Nennen wir sie mal "triangle_01.mat".

Jetzt basteln wir uns ein kleines Script, was diese Daten 1 zu 1 ausplottet:

Fs = 500000000;       % Sampling frequency

data = importdata('triangle_01.mat');

T = 1/Fs;             % Sampling period
L = data.Length;      % Length of signal
t = (0:L-1)*T;        % Time vector

G = transpose(data.B);

plot(t,G);
grid on;

print('triangle.eps','-depsc');

Wichtig ist nur, daß man in der ersten Zeile die Sampling Frequenz richtig einstellt, damit später im Plot die Zeitachse richtig beschriftet wird.

Weiterhin ist die Zeile 9 vielleicht verwirrend, diese ist aber nötig, da der Import einen Spaltenvektor erzeugt, der plot Befehl aber einen Zeilenvektor erwartet. Mit transpose drehen wir diesen einfach um. Wenn alles richtig eingegeben ist, erhalten wir einen Plot, der wie folgt aussehen sollte:

Matlab Ausgabe

Matlab Ausgabe

Der Plot wird dann noch zusätzlich im Arbeitsverzeichnis als EPS-Vektordatei ausgegeben, so einfach kann das Leben sein!


Picoscope 3203D im Test

Seit einiger Zeit plagt mich das Thema, welches Oszilloskop ich nun erwerben soll, um mein altes Hameg HM512 zu ersetzen, welches ja nun mittlerweile weit über 40(!) Jahre alt ist.

Ich hatte bereits das "Vergnügen", mit einem Rigol DS1052E und einem DS2072A zu arbeiten. Ich spare mir an dieser Stelle die Details. Nur soviel dazu: Wer keinen Düsenjet auf seinem Schreibtisch stehen haben will, sollte tunlichst die Finger davon lassen! Insgesamt ist alles aus dem Hause Rigol, was ich bisher in den Fingern hatte, als billiger Schrott zu bezeichnen, das schliesst das Tischmultimeter DM3058E und das Labornetzteil DP832 ein. Einzig der Funktionsgenerator DG4102 vermochte zu überzeugen. Alle anderen Geräte scheiden allein wegen der lauten Lüfter aus, die allesamt eine absolute Zumutung sind! Scheinbar sind alle Chinesen taub.

Aber genug davon, wenden wir uns den angenehmen Dingen zu...

(mehr …)


Raspberry Pi Wecker mit Webradio (Teil 5)

Webradio steuern

Wie versprochen geht es heute weiter mit der Steuerung des Webradios. Wir wollen uns heute ansehen, wie man den Raspberry Pi über den GPIO Port ansteuert und dort Taster abfragt und eine LED ansteuert.

Hierzu müssen wir zunächst ein wenig Software installieren. Zu aller erst benötigt man die WiringPi Bibliothek. Eine Anleitung, wie man diese herunterlädt und installiert gibt es hier.

Anschliessend muss man noch den Music Player Daemon installieren und dazu den passenden Client, das geschieht mittels

sudo apt-get install mpc mpd

Jetzt öffnet man die Datei /etc/mpd.conf mit einem beliebigen Editor wie z.B. nano oder vim und sucht sich die Zeile, in der steht "bind to address" hier nehmen wir das # am Anfang weg und starten den Daemon mit

sudo /etc/init.d/mpd restart

neu, damit die Änderungen übernommen werden.

Als nächstes benötigen wir eine Playlist, die eine Liste von URLs zu Radio-Streams enthält, die gibt es zuhauf im Internet. Als Beispiel mag diese hier dienen:

http://www.ndr.de/resources/metadaten/audio/m3u/n-joy.m3u
http://mp3-live.swr3.de/swr3_m.m3u
http://mp3-live.dasding.de/dasding_m.m3u
http://mp3-live.swr.de/swr1bw_m.m3u
http://www.ndr.de/resources/metadaten/audio/m3u/ndr2_sh.m3u
http://www.chromanova.de/chromanova.-.chill.high.pls

Speichert diese einfach als webradio.pls im Ordner /home/pi/webradio ab. Diese benötigen wir später.

Jetzt kommt zunächst die WiringPi Bibliothek ins Spiel. Damit wir das Webradio von aussen steuern können, benötigen wir wenigstens 3 Tasten und eine LED. Die drei Tasten bekommen die Funktionen Start/Stop und Next (Nächstes Element der Playlist), eine Taste halten wir uns frei, damit wir später die Weckerfunktionen steuern können, die wir in einer der nächsten Folgen behandeln.

Verdrahten wir also unseren Raspberry GPIO Port wie folgt:

Raspberry Buttons

Raspberry Buttons

Die Pins 1,3, und 5 vom Stecker SV1 verbinden wir noch mittels dreier 10kOhm WIderstände mit Plus. Ich habe dies über einen Wannenstecker gelöst, damit ich die Buttons auf einer externen Platine montieren kann, die an die Oberseite des Gehäuses befestigt wird. Dazu in einer der späteren Folgen mehr.

Wenden wir uns der Software zu und erstellen uns ein kleines C-Programm, daß die Tasten auswertet und die LED schaltet.

#include <stdio.h>
#include <wiringPi.h>
#include <stdlib.h>
#include <syslog.h>

#define DEBOUNCE_TIME   150

int debounceTime = 0;

int on = 0;

void startStopRadio(void) {

        if (millis () < debounceTime) {
                debounceTime = millis () + DEBOUNCE_TIME ;
                return ;
        }

        if (on) {
                on = 0;
                system("mpc stop");
                syslog(LOG_NOTICE,"On.\n");
        }
        else {
                on = 1;
                system("mpc play");
                syslog(LOG_NOTICE,"Off.\n");
        }

        digitalWrite(1,on);

    while (digitalRead (0) == LOW)
                delay(1) ;

    debounceTime = millis () + DEBOUNCE_TIME ;

}

void nextSong(void) {

        if (millis () < debounceTime) {
                debounceTime = millis () + DEBOUNCE_TIME ;
                return;
        }

        // action

        system("mpc next");
        syslog(LOG_NOTICE,"Next song.\n");

        while (digitalRead (3) == LOW)
                delay(1);

        debounceTime = millis () + DEBOUNCE_TIME;
}

int main (int argc, char** argv)
{
        setlogmask (LOG_UPTO (LOG_NOTICE));

        openlog (NULL, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
        syslog (LOG_NOTICE, "Webradio control started by User %d", getuid ());

        int pin;

    if (wiringPiSetup () == -1) {
        syslog(LOG_NOTICE,"wiringPiSetup failed.\n");
                return 1 ;
        }

        system("gpio edge 0 falling");
        system("gpio edge 3 falling");

        pinMode(0,INPUT);
        pinMode(1,OUTPUT);
        pinMode(3,INPUT);

        if (wiringPiISR (0, INT_EDGE_FALLING, &startStopRadio) < 0) {
                syslog(LOG_NOTICE,"wiringPiISR for pin 0 failed.\n");
                return 1;
        }

        if (wiringPiISR (3, INT_EDGE_FALLING, &nextSong) < 0) {
                syslog(LOG_NOTICE,"wiringPiISR for pin 3 failed.\n");
                return 1;
        }

        for(;;){
                delay(100);
        }

        return 0;

}

Die beiden Funktionen startStopRadio und nextSong werden in Zeile 78 und 83 mit wiringPiISR an die fallende Flanke der jeweiligen Pins gebunden. Das heisst, daß jedesmal wenn der Pegel an den entsprechenden Pins auf LOW wechselt, wird ein Interrupt ausgelöst und die jeweilige Funktion aufgerufen.Wir erinnern uns, daß wir die Pins der Taster ja auf Masse gelegt haben und mit den Tastern auf Plus "ziehen".

Ansonsten läuft das Programm nun in einer Endlosschleife und wartet auf Ereignisse. In der Funktion startStopRadio wird auch noch die LED eingeschaltet, wenn das Webradio läuft.

Das Ganze speichern wir nun als webradio.c (auch wieder im Ordner /home/pi/webradio) und kompilieren mit

gcc -o webradio webradio.c -lwiringPi

Jetzt fehlt uns nur noch ein Skript, was das Programm beim Systemstart als Dienst startet:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          webradio
# Required-Start:    $all
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Webradio
# Description:       Webradio button control
### END INIT INFO

# Author: Matthias Pueski <matthias@pueski.de>
#

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Webradio"
NAME=webradio
DAEMON=/usr/sbin/$NAME
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon -b --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon -b --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
                $DAEMON_ARGS \
                || return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.

        mpc clear
        mpc load /home/pi/webradio/webradio.pls

}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
        [ "$?" = 2 ] && return 2
        # Many daemons don't delete their pidfiles when they exit.
        rm -f $PIDFILE
        return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        #
        # If the daemon can reload its configuration without
        # restarting (for example, when it is sent a SIGHUP),
        # then implement that here.
        #
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
        return 0
}

case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
  #reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        #log_daemon_msg "Reloading $DESC" "$NAME"
        #do_reload
        #log_end_msg $?
        #;;
  restart|force-reload)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac

:

Dieses Skript liegt übrigens als Vorlage unter /etc/init.d/skeleton und kann für jeden beliebigen Zweck angepasst werden. Hierzu kopiert man sich einfach das Skript und passt die Variablen an seine Zwecke an. Man kopiert nun das Skript nach /etc/init.d und das Program nach /usr/sbin und führt folgenden Befehl aus:

update-rc.d webradio defaults

Jetzt wird das Skript nach jedem Start automatisch geladen und startet das Webradio Program im Hintergrund. Starten können wir es natürlich jetzt schon mit:

/etc/init.d/webradio start

Nun sollte sich das Radio mittels Taster starten lassen und die LED einschalten. Ob alles klappt, kann man sich mit

tail -f /var/log/syslog

ausgeben lassen. Falls es nicht funktioniert, muss man natürlich auf Fehlersuche gehen.

Das war es für heute. In der nächsten Folge löten wir uns die Uhrenplatine zusammen, die ich bereits habe fertigen lassen und machen uns Gedanken über die Alarmfunktionen.

Bis dahin wünsche ich allen Lesern fröhliches Basteln. 😉


Raspberry Pi Wecker mit Webradio (Teil 4)

In der letzten Folge hatten wir uns ja die Innereien des Uhrenmoduls angesehen. Heute wollen wir einen kurzen Blick darauf werfen, wie die Uhrzeit vom Raspberry auf das Uhrenmodul kommt und wie die Auswertung der Zeit erfolgt, die ja über die serielle Schnittstelle übertragen wird.

Zunächst müssen wir dem ATMega beibringen, Daten von der seriellen Schnittstelle zu lesen. Ich verwende hierzu die UART-Bibliothek von Peter Fleury.  Um diese zu verwenden, müssen wir die Bibliothek mittels des Befehls:

uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );

initialisieren. Die UART_BAUD_RATE beträgt in meinem Falle 9600 Baud, es geht natürlich auch schneller. Die Geschwindigkeit ist aber für die Übertragung der Zeit völlig ausreichend. Nun müssen wir in der Hauptschleife des Programm zyklisch prüfen, ob Daten an der seriellen Schnittstelle anliegen, dafür basteln wir uns eine Funktion:

void getUart() {
    /*
     * Get received character from ringbuffer
     * uart_getc() returns in the lower byte the received character and
     * in the higher byte (bitmask) the last receive error
     * UART_NO_DATA is returned when no data is available.
     *
     */
    char c = uart_getc();

    if ( c & UART_NO_DATA )
    {
        /*
         * no data available from UART
         */
    }
    else
    {
        /*
         * new data available from UART
         * check for Frame or Overrun error
         */
        if ( c & UART_FRAME_ERROR )
        {
            /* Framing Error detected, i.e no stop bit detected */
            uart_puts_P("UART Frame Error: ");
        }
        if ( c & UART_OVERRUN_ERROR )
        {
            /*
             * Overrun, a character already present in the UART UDR register was
             * not read by the interrupt handler before the next character arrived,
             * one or more received characters have been dropped
             */
            uart_puts_P("UART Overrun Error: ");
        }
        if ( c & UART_BUFFER_OVERFLOW )
        {
            /*
             * We are not reading the receive buffer fast enough,
             * one or more received character have been dropped
             */
            uart_puts_P("Buffer overflow error: ");
        }

        if (charNum < 16) {

            if ((unsigned char)c == 'r') {

            	parseCommand(buffer,charNum);

            	uart_puts("rn");
            	uart_puts("Ok.rn");

            	charNum = 0;
            }
            else {
            	if (isalnum(c) || isblank(c)) {
					uart_putc(c);
					buffer[charNum] = (unsigned char)c;
					charNum++;
            	}
            }

        }

    }

}

Da wir jeweils immer nur ein Zeichen einlesen können, müssen wir eine maximale Länge (hier 16) definieren und uns merken, an welcher Position wir uns befinden. Am Ende des Kommandos erfolgt ein CR LF, damit der Controller weiss, daß wir einen kompletten Befehl empfangen haben. Jetzt fehlt nur noch das Auswerten des empfangenen Kommandos und das geht so:

void parseCommand(char* cmd, uint8_t len) {

	if (strncmp(cmd,"time",4) == 0) {

		char* t = &buffer[5];

		uint16_t time   = atoi(t);

		int hour = time / 100;
		int minute = time - ((time / 100) * 100);

		tmp_date.hour = hour;
		tmp_date.minute = minute;
		tmp_date.month = 1;
		tmp_date.year = 0;
		tmp_date.day = 1;

		if (isValidTime(tmp_date)) {
			current_date.hour = hour;
			current_date.minute = minute;
		}

	}
	else if(strncmp(cmd,"gettime",7) == 0) {
		sprintf(buffer,"rn%02d:%02d",current_date.hour,current_date.minute);
		uart_puts(buffer);
	}

}

Nun können wir über die serielle Schnittstelle die Zeit empfangen und die aktuelle Zeit ausgeben. Wie aber senden wir nun die Daten vom Raspberry?

Hierzu benötigen wir zunächst ein einfaches Python Script, was die Zeit als Kommandozeilenargument übergeben bekommt und diese dann an die serielle Schnittstelle sendet:

import serial
import sys
ser = serial.Serial("/dev/ttyAMA0",9600);
ser.write("time "+sys.argv[1]+"rn")
ser.close()

Und das Shellscript, welches wir später periodisch über einen Cronjob anstossen, um die Zeit zu synchronisieren:

#!/bin/bash ntpdate -s 0.de.pool.ntp.org date +"%H%M" | xargs python /home/pi/webradio/time.py

Damit haben wir nun alles zusammen und können einen Cronjob anlegen, der einmal in der Stunde die Zeit aktualisiert, hierzu verwenden wir den Befehl crontab -e und fügen folgende Zeile hinzu:

0 */1 * * * /home/pi/webradio/time.sh

Das Script muss sich natürlich am entsprechenden Ort befinden.

Das war's dann für heute. In der nächsten Folge schauen wir uns an, wie das eigentliche Webradio auf dem Raspberry funktioniert und wie wir selbiges mit externen Tastern steuern können.


Raspberry Pi Wecker mit Webradio (Teil 3)

Zeit ist relativ

Wir wollen jetzt hier keine Diskussion zur Relativitätstheorie anfangen. Ich hatte ja in der letzten Folge versprochen, daß wir uns das Webradio mal in der Praxis ansehen. In dieser Folge wollen wir uns die komplette Uhrenschaltung ansehen und auch darüber sprechen, wie denn nun die Firmware für den ATMega aussieht. Insbesondere die Implementierung der Uhr und das Multiplexing sollen uns hier interessieren. Wie versprochen hier also zunächst die gesamte Schaltung der Uhrenplatine:

Schaltung der Uhrenplatine

Schaltung der Uhrenplatine

Ich setzte an dieser Stelle voraus, daß sich der interessierte Leser bereits mit Mikrocontrollern, insbesondere denen aus der ATMega Familie auskennt. Wer hier noch keinen Überblick hat, sollte sich hier mal ansehen um was es geht. Vielleicht schreibe ich auch hierzu irgendwann mal einen Artikel. Interessant ist dieses Gebiet auf jeden Fall.

Die Beschaltung des Mikrocontrollers entspricht ziemlich genau dem, was notwendig ist, um den Baustein zu betreiben. So verwende ich ier einen normalen Quarz zur Taktung, ISP ist der EInfachheit halber nicht angeschlossen. Praktisch alles, was sich links vom Controller befindet, ist Standard um den Baustein zu betreiben. auf der rechten Seite kann man die Leitungen sehen, die die 7-Segmentanzeigen steuern. Der Übersicht halber habe ich das hier über einen Bus geführt. Mit den Pins PB2-PB5 steuere ich die Transistoren, die die jeweilige Anzeige aktivieren. Was hier nicht zu sehen ist, ist der UART, den ich direkt mit dem Raspberry verbunden habe, um die Uhrzeit vom Raspberry an den ATMega zu übertragen.  Es stellt sich nun die Frage, wie man den ATMega dazu bewegt, die Uhrzeit anzuzeigen. Und hier sind wir auch beim Kern der Sache.

Ich habe das ganz einfach über einen Timer gelöst, der im CTC-Modus läuft. Im Code sieht das dann so aus:

void timer1_start_ctc(uint16_t cmp) {
	OCR1A = cmp; // set value to output compare register
	TCCR1B = (1 << WGM12) | (1 << CS12) | (0 << CS11) | (0 << CS10); // ctc, 256 prescale
	//Enable the Output Compare A interrupt
	TIMSK1 |= (1 << OCIE1A);
}

Hier wird das Register OCR1A mit einem Wert geladen, der bestimmt, wann die Timer ISR ausgelöst wird. Dies geschieht eben dann, wenn der Timer den Wert erreicht, der im OCR1A Register hinterlegt ist. Dann wird der Timer so eingestellt, daß er mit 1/256 der Taktfrequenz  läuft. Anschliessend  laden wir das OCR1A Register mit dem Wert 31250 und starten den Timer. Nun wird die ISR-Routine für den Timer genau einmal in der Sekunde ausgelöst. Aber wie komme ich denn nun genau auf 31250?

Ganz einfach, die Taktfrequenz beträgt in meinem Fall 8MHz. Also wird, wenn im OCR1A der Wert 0 steht, die Timer ISR mit einer Frequenz von 8MHz/256 ausgelöst, also 31250Hz. Somit müssen wir das Register genau mit diesem Wert vorladen, um eine Frequenz von 1Hz zu bekommen. Für andere Taktfrequenzen muss man das dann entsprechend umrechnen.

Jetzt ist es einfach, wir definieren uns eine Struktur:

typedef struct { uint8_t hour; uint8_t minute; uint8_t second; uint8_t day; uint8_t day_of_week; uint8_t month; uint8_t year; } date_t;

Dann definieren wir den entsprechenden Typ mit

volatile date_t current_date;

Und können die ISR implementieren:

ISR (TIMER1_COMPA_vect) {

	if (current_date.second < 59) {
		current_date.second++;
	}
	else {
		current_date.second = 0;

		if (current_date.minute < 59) {
			current_date.minute++;
		}
		else {
			current_date.minute = 0;

			if (current_date.hour < 23) {
				current_date.hour++;
			}
			else {
				current_date.hour = 0;
			}
		}
	}

}

In der Hauptschleife aktualisieren wir dann immer die aktuelle Zeit. Nun fehlt noch die Ausgabe an die 7-Segment anzeigen.

Es werden jetzt einige Definitionen für die Anzeigemodule benötigt. Da die Module über eine gemeinsame Anode verfügen, müssen die Segmente zur Anzeige auf LOW geschaltet werden.

#define setPin(PORT,PIN) PORT |= (1 << PIN)
#define clearPin(PORT,PIN) PORT &= ~(1 << PIN)

#define SEG_A clearPin(PORTB, 4)
#define SEG_B clearPin(PORTC, 5)
#define SEG_C clearPin(PORTC, 3)
#define SEG_D clearPin(PORTC, 2)
#define SEG_E clearPin(PORTB, 2)
#define SEG_F clearPin(PORTB, 3)
#define SEG_G clearPin(PORTC, 4)

Jetzt können wir die Methoden für die Zeitanzeige implementieren:

void displayNumber(uint8_t num, boolean withDot) {

	// turn all segments off
	setPin(PORTC, 5);
	setPin(PORTC, 4);
	setPin(PORTC, 3);
	setPin(PORTC, 2);
	setPin(PORTB, 4);
	setPin(PORTB, 3);
	setPin(PORTB, 2);
	setPin(PORTB, 1);

	if (withDot) {
		clearPin(PORTB, 1);
	}
	else {
		setPin(PORTB,1);
	}

	if (num == 0) {
		SEG_A;
		SEG_B;
		SEG_C;
		SEG_D;
		SEG_E;
		SEG_F;
	}
	else if (num == 1) {
		SEG_B;
		SEG_C;
	}
	else if (num == 2) {
		SEG_A;
		SEG_B;
		SEG_G;
		SEG_D;
		SEG_E;
	}
	else if (num == 3) {
		SEG_A;
		SEG_B;
		SEG_C;
		SEG_D;
		SEG_G;
	}
	else if (num == 4) {
		SEG_B;
		SEG_C;
		SEG_F;
		SEG_G;
	}
	else if (num == 5) {
		SEG_A;
		SEG_F;
		SEG_G;
		SEG_C;
		SEG_D;
	}
	else if (num == 6) {
		SEG_A;
		SEG_F;
		SEG_G;
		SEG_C;
		SEG_D;
		SEG_E;
	}
	else if (num == 7) {
		SEG_A;
		SEG_B;
		SEG_C;
	}
	else if (num == 8) {
		SEG_A;
		SEG_B;
		SEG_C;
		SEG_D;
		SEG_E;
		SEG_F;
		SEG_G;
	}
	else if (num == 9) {
		SEG_A;
		SEG_B;
		SEG_C;
		SEG_D;
		SEG_F;
		SEG_G;
	}

}

void displayTime(int hours, int minutes, int seconds, boolean dot) {

	int s_tenths = seconds / 10;
	int s_ones = seconds - s_tenths * 10;

	int m_tenths = minutes / 10;
	int m_ones = minutes - m_tenths * 10;

	int h_tenths = hours / 10;
	int h_ones = hours - h_tenths * 10;

	clearPin(PORTD,5);
	clearPin(PORTD,6);
	clearPin(PORTB,6);
	setPin(PORTB,7);
	displayNumber(m_ones,false);
	_delay_ms(SW_DELAY);

	clearPin(PORTD,5);
	clearPin(PORTD,6);
	setPin(PORTB,6);
	clearPin(PORTB,7);
	displayNumber(m_tenths,false);
	_delay_ms(SW_DELAY);

	clearPin(PORTB,6);
	clearPin(PORTB,7);
	clearPin(PORTD,5);
	setPin(PORTD,6);
	displayNumber(h_ones,dot);
	_delay_ms(SW_DELAY);

	clearPin(PORTB,6);
	clearPin(PORTB,7);
	setPin(PORTD,5);
	clearPin(PORTD,6);
	displayNumber(h_tenths,false);
	_delay_ms(SW_DELAY);
}

Hier kann man gut sehen, wie die einzelnen Anzeigeelemente umgeschaltet werden, indem der entprechende Pin geschaltet wird, bevor die jeweilge Ziffer angezeigt wird. Schauen wir uns das ganze mal auf dem Logic Analyzer an:

Multiplexing

Multiplexing

Hier sehen wir von oben nach unten die einzelnen Ausgänge des MIkrocontrollers für die Ansteuerung der Transistoren. Hier lassen sich noch zwei Messgrößen ablesen: Zum einen können wir sehen, daß jedes einzelne Segment mit einer Frequenz von knapp 118 Hz angesteuert wird und somit kein Flimmern mehr zu sehen ist, zum andern kann man sehen, daß jedes einzelne Segment für knapp 2ms aktiv ist, bevor zum nächsten umgeschaltet wird.

Im Grunde ist das die gesamte "Magie", die benötigt wird, um die Zeit anzuzeigen. In der nächsten Folge beschäftigen wir uns damit, wie wir über den UART die aktuelle Zeit vom Raspberry an das Uhrenmodul übertragen.

Der Code für das Projekt kann wie immer auf Github eingesehen und heruntergeladen werden. Hier ist auch die Schaltung im EAGLE Format enthalten.


Ruhig Blut

dummy

Soweit so gut, ich habe mich wieder ein wenig beruhigt nach der Linux-Orgie.

Ich habe noch ein wenig recherchiert und herausgefunden, daß mein Soundproblem offenbar ein bekannter Bug ist

https://bugzilla.kernel.org/show_bug.cgi?id=87771#c23

Das Problem betrifft wohl alle Mainboards mit dem Realtek ALC1150 Chipsatz, also in meinem Falle das MSI Z97 GAming 3 Board. Ich kann das komplett so nachvollziehen, die Mühe, das Kernel upzudaten spare ich mir mal, da ich meistens sowieso direkt Windows oder Linux starte und im laufenden Betrieb nicht neustarte.


Nichts dazugelernt?

Junge, junge! Ich war echt der Meinung, daß sich möglicherweise in den letzten 10 Jahren im Linux-Desktop Bereich etwas getan hat. Da habe ich leider falsch gelegen.

Nachdem ich im letzten Post berichtete, daß die Installation von Linux Mint auf meinem Notebook wie Butter erstaunlich gut von der Hand ging, dachte ich, daß es an der Zeit wäre, das ganze auch mal auf meinem Desktop zu installieren. Soweit so gut, ich besitze einen Standard PC von der Stange mit einer NVIDIA GTX970 und keinerlei exotischen Komponenten. Sollte also funktionieren oder? Also nicht lange gefackelt, die Windows Partition verkleinert und den bootfähigen USB-Stick vom letzten Mal eingesteckt.

Nachdem ich die ersten Probleme mit dem UEFI Boot umschifft habe, mit denen ich allerdings gerechnet hatte, lies sich Linux Mint dann parallel installieren und hat auch brav den Bootmanager installiert. Aber was soll ich euch sagen? Die NVIDIA Karte wurde nicht erkannt! Eine NVIDIA Karte! Hallo, geht's noch??? Da muss ich doch tatsächlich die Treiber manuell von der NVIDIA Seite herunterladen und ein Kernelmodul kompilieren. Ich kam mir vor wie in der Steinzeit, der gleiche Müll wie vor 10 Jahren! Abgesehen davon, daß sich der NVIDIA Installer darüber beschwerte, daß das Kernelmodul nicht geladen werden konnte, lief die ganze Geschichte dann nach einem Neustart, warum auch immer.

Als ich dann die Grafikkarte dann am Laufen hatte, gingen die Probleme natürlich weiter. Irgendwie konnte ich zwar Audio-Dateien abspielen, aber ich habe nichts gehört. Ich habe dann solange an den diversen Lautstärkereglern rumgespielt, bis ich irgendwann mal was gehört habe, leider alles viel zu leise. Eine umfangreiche Recherche in diversen Foren ergab dann, daß ich angeblich mittels des Alsamixers auf der Konsole (!!!!) den Sound lauterstellen sollte. Zahllose Versuche später, nachdem ich mich durch diverse Mixer und EInstellungtools gewühlt hatte, warf ich dann die Flinte ins Korn. Liebe Linux Entwickler, es kann echt nicht sein, daß es mindestens vier verschiedene Sound Systeme gibt, die irgendwie miteinander zusammenhängen: PulseAudio, Alsa, Jack, Mixer hier, Konsole da, Alsactl, blah blah blah. Was soll das? Wer soll das bedienen? Habt Ihr 'ne Meise?

Fuck you

Aber am Besten fand' ich die Tatsache, daß sich das ganze irgendwie von selbst repariert hat. *kopfkratz*

Ich höre immer das Geheule, daß sich Linux ja nicht auf dem Desktop durchsetzt, aber ich weiss jetzt wieder genau warum. Ich weiss, es kostet nichts und ich weiss auch, daß die meisten Entwickler in ihrer Freizeit an Linux arbeiten, aber irgendwas läuft hier gewaltig schief.


(c) 2016 Matthias Pueski