Author Archives: admin

I2S Mikrofon und PIC32MX170F256 als Alternative zu MSGEQ-7

Als günstige Alternative zu einem MSGEQ-7 (Einzelpreis 5,45€) für ein Graphic Equalizer Display (5-Kanal Lichtorgel) ein kleiner Exkurs in die DSP Welt:

Von Knowles gibt es sehr (sehr) kleine Mikrofone, die direkt das Audio Signal als I2S Datenstrom ausgeben (Einzelpreis 2€).

Dieses wird direkt an den I2S Port des PIC32 (Einzelpreis 4€) angeschlossen und mittels DMA im Hintergrund ausgelesen. Nach jeweils 4096 Samples bei 48 kHz wird der DMA Interrupt ausgelöst (etwa 48 mal pro Sekunde) und die Samples des unbeschalteten Kanals weggeworfen und je zwei aufeinanderfolgende Samples des verbleibenden Kanals gemittelt.

Die so übrig bleibenden 1024 Samples (mit einer Abtastfrequenz von nun 24 kHz) werden dann mittels FFT in den Frequenzraum überführt und auf die 5 Bänder aufgeteilt.

Die Anzeige der Balken erfolgt über eine Matrix aus WS2812 LEDs:

bildschirmfoto-2016-09-23-um-21-14-11

RS485 Interface an einem PIC16F1708

Neues Projekt mit RS-485 Interface: 29 Module mit je 37 LEDs, die über einen Raspberry Pi gesteuert werden sollen.

Problem: der PIC16F1708 ist nicht in der Lage festzustellen, ob noch eine Übertragung via EUSART Modul aktiv ist (Quote: “The TSR register is not mapped in data memory, so it is not available to the user.”). Die Transmitter-Enable Leitung (DE) kann also nicht mit dem letzten gesendeten Bit wieder deaktiviert werden.

Lösung: Beim Senden von jedem Zeichen die DE Leitung auf High setzen (und so den 75176 in Sendemodus bringen), den TMR0 (re-)starten und im Interrupt-Handler von TMR0 dann die DE Leitung wieder auf Low setzen:

void TMR0_CallBack(void) {
  IO_DE_SetLow();
}

void RS485_Put(uint8_t c) {
  IO_DE_SetHigh();
  TMR0_Reload();
  EUSART_Write(c);
}

void RS485_Print(const unsigned char* message) {
  char c; 
  while (c = *message++) {
    RS485_Put(c);
  } 
}

char to_hex(uint8_t v) {
  v = v & 0xf;
  if (v<10) {
    return '0'+v;
  }
  return 'A'-10+v;
}

void RS485_PrintHex8(uint8_t val) {
  RS485_Put(to_hex(val>>4));
  RS485_Put(to_hex(val));
}

void RS485_PrintHex16(uint16_t val) {
  RS485_Put(to_hex(val>>12));
  RS485_Put(to_hex(val>>8));
  RS485_Put(to_hex(val>>4));
  RS485_Put(to_hex(val));
}

void RS485_PrintDecimal(uint16_t val) {
  RS485_Put('0'+ ((val/10000) % 10 ));
  RS485_Put('0'+ ((val/1000) % 10 ));
  RS485_Put('0'+ ((val/100) % 10 ));
  RS485_Put('0'+ ((val/10) % 10 ));
  RS485_Put('0'+ ((val/1) % 10 ));
}

Mailserver mit Postfix, dovecot, spamassassin, opendkim und postgrey unter debian jessy

Der alte Server war dann doch etwas langsam, daher der Umzug auf neue Hardware. Mit dem Umzug galt es auch den bisherigen IMAP Server (cyrus) durch dovecot zu ersetzen. Soweit der Vorsatz, die Umsetzung war dann weitaus schwieriger…

Setup

Neben der Grundfunktionalität (Mails empfangen und senden) sollte der Mailserver wenigstens folgendes können:

  • DKIM zur Authentifizierung
  • SPF
  • Greylisting
  • spamassassin Unterstützung
  • Volltextsuche über IMAP
  • Mehrere virtuelle Domains
  • Weiterleitungen
  • Mehrere Benutzer für IMAP/SMTP
  • TLS Unterstützung

Continue reading

DCF77 Empfang mit Arduino

Da mein DCF-77 Empfänger ab und zu Aussetzer zeigte, funktionierte der Code von http://gonium.net/md/2007/01/06/arduino-dcf77-v02-released/ leider nicht auf Anhieb.

Offenbar war die Flankensteilheit des Ausgangs nicht ausreichend, so dass gerade bei den fallenden Flanken mehrere Interrupts ausgelöst wurden. Daher gibt es jetzt eine Prüfung, ob der Interrupt mindestens 10 ms nach dem letzten eingetreten ist.

/**
 * Arduino DCF77 decoder v0.3
 * Copyright (C) 2006 Mathias Dalheimer (md@gonium.net)
 *           (C) 2010 Wolfgang Jung (w@elektrowolle.de)
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#include <MsTimer2.h>

/**
 * Where is the DCF receiver connected? Must be attachable to an interrupt
 * (PIN 2 or PIN 3).
 */
#define DCF77PIN 2
/**
 * Where is the LED connected?
 */
#define BLINKPIN 13

/**
 * Turn debugging on or off
 */
#define DCF_DEBUG 1
/**
 * Number of milliseconds to elapse before we assume a "1",
 * if we receive a falling flank before - its a 0.
 */
#define DCF_split_millis 140
/**
 * There is no signal in second 59 - detect the beginning of 
 * a new minute.
 */
#define DCF_sync_millis 1200

/** 
 * Signals shorter than this should be ignored
 */
#define DCF_MAX_JITTER 10

/** 
 * Signals shorter than this should be ignored
 */
#define DCF_SHORT_SIGNAL 50

/**
 * DCF time format struct
 */
struct DCF77Buffer {
  unsigned long long prefix	:21;
  unsigned long long Min	:7;	// minutes
  unsigned long long P1		:1;	// parity minutes
  unsigned long long Hour	:6;	// hours
  unsigned long long P2		:1;	// parity hours
  unsigned long long Day	:6;	// day
  unsigned long long Weekday	:3;	// day of week
  unsigned long long Month	:5;	// month
  unsigned long long Year	:8;	// year (5 -> 2005)
  unsigned long long P3		:1;	// parity
};

struct {
	unsigned char parity_flag	:1;
	unsigned char parity_min	:1;
	unsigned char parity_hour	:1;
	unsigned char parity_date	:1;
} flags;

/**
 * Clock variables 
 */
volatile unsigned char DCFSignalState = 0;  
unsigned char previousSignalState;
int previousFlankTime;
int bufferPosition;
unsigned long long dcf_rx_buffer;

/**
 * time vars: the time is stored here!
 */
volatile unsigned char ss;
volatile unsigned char mm;
volatile unsigned char hh;
volatile unsigned char day;
volatile unsigned char mon;
volatile unsigned int year;
    

/**
 * used in main loop: detect a new second...
 */
unsigned char previousSecond;
    
/**
 * Initialize the DCF77 routines: initialize the variables,
 * configure the interrupt behaviour.
 */
void DCF77Init() {
  previousSignalState=0;
  previousFlankTime=0;
  bufferPosition=0;
  dcf_rx_buffer=0;
  ss=mm=hh=day=mon=year=0;
#ifdef DCF_DEBUG 
  Serial.println("Initializing DCF77 routines");
  Serial.print("Using DCF77 pin #");
  Serial.println(DCF77PIN);
#endif
  pinMode(BLINKPIN, OUTPUT);
  pinMode(DCF77PIN, INPUT);
  
#ifdef DCF_DEBUG
  Serial.println("Initializing timerinterrupt");
#endif
  MsTimer2::set(1000, advanceClock); // every second

#ifdef DCF_DEBUG
  Serial.println("Initializing DCF77 signal listener interrupt");
#endif  
  attachInterrupt(0, int0handler, CHANGE);
}

/**
 * Append a signal to the dcf_rx_buffer. Argument can be 1 or 0. An internal
 * counter shifts the writing position within the buffer. If position > 59,
 * a new minute begins -> time to call finalizeBuffer().
 */
void appendSignal(unsigned char signal) {
#ifdef DCF_DEBUG
  Serial.print(", appending value ");
  Serial.print(signal, DEC);
  Serial.print(" at position ");
  Serial.println(bufferPosition);
#endif
  dcf_rx_buffer = dcf_rx_buffer | ((unsigned long long) signal << bufferPosition);
  // Update the parity bits. First: Reset when minute, hour or date starts.
  if (bufferPosition ==  21 || bufferPosition ==  29 || bufferPosition ==  36) {
	flags.parity_flag = 0;
  }
  // save the parity when the corresponding segment ends
  if (bufferPosition ==  28) {flags.parity_min = flags.parity_flag;};
  if (bufferPosition ==  35) {flags.parity_hour = flags.parity_flag;};
  if (bufferPosition ==  58) {flags.parity_date = flags.parity_flag;};
  // When we received a 1, toggle the parity flag
  if (signal == 1) {
    flags.parity_flag = flags.parity_flag ^ 1;
  }
  bufferPosition++;
  if (bufferPosition > 59) {
    finalizeBuffer();
  }
}

/**
 * Evaluates the information stored in the buffer. This is where the DCF77
 * signal is decoded and the internal clock is updated.
 */
void finalizeBuffer(void) {
  if (bufferPosition == 59) {
#ifdef DCF_DEBUG
    Serial.println("Finalizing Buffer");
#endif
    struct DCF77Buffer *rx_buffer;
    rx_buffer = (struct DCF77Buffer *)(unsigned long long)&dcf_rx_buffer;
    if (flags.parity_min == rx_buffer->P1  &&
        flags.parity_hour == rx_buffer->P2  &&
        flags.parity_date == rx_buffer->P3) 
    { 
#ifdef DCF_DEBUG
      Serial.println("Parity check OK - updating time.");
#endif
      //convert the received bits from BCD
      mm = rx_buffer->Min-((rx_buffer->Min/16)*6);
      hh = rx_buffer->Hour-((rx_buffer->Hour/16)*6);
      day= rx_buffer->Day-((rx_buffer->Day/16)*6); 
      mon= rx_buffer->Month-((rx_buffer->Month/16)*6);
      year= 2000 + rx_buffer->Year-((rx_buffer->Year/16)*6);
      // Start or reset the timer for the seconds
      MsTimer2::start();      
    }
#ifdef DCF_DEBUG
      else {
        Serial.println("Parity check NOK - running on internal clock.");
    }
#endif
  } 
  // reset stuff
  ss = 0;
  bufferPosition = 0;
  dcf_rx_buffer=0;
}

/**
 * Dump the time to the serial line.
 */
void serialDumpTime(void){
#ifdef DCF_DEBUG
  Serial.print("Time: ");
  Serial.print(hh, DEC);
  Serial.print(":");
  Serial.print(mm, DEC);
  Serial.print(":");
  Serial.print(ss, DEC);
  Serial.print(" Date: ");
  Serial.print(day, DEC);
  Serial.print(".");
  Serial.print(mon, DEC);
  Serial.print(".");
  Serial.println(year, DEC);
#endif
}

/**
 * Evaluates the signal as it is received. Decides whether we received
 * a "1" or a "0" based on the 
 */
void scanSignal(void){ 
    digitalWrite(BLINKPIN, DCFSignalState);
    if (DCFSignalState == 1) {
      int thisFlankTime=millis();
      if (thisFlankTime - previousFlankTime > DCF_sync_millis) {
#ifdef DCF_DEBUG
        Serial.println("####");
        Serial.println("#### Begin of new Minute!!!");
        Serial.println("####");
#endif
        finalizeBuffer();
      }
      previousFlankTime=thisFlankTime;
#ifdef DCF_DEBUG
      Serial.print(previousFlankTime);
      Serial.print(": DCF77 Signal detected, ");
#endif
    } 
    else {
      /* or a falling flank */
      int difference=millis() - previousFlankTime;
#ifdef DCF_DEBUG
      Serial.print("duration: ");
      Serial.print(difference);
#endif
      if (difference < DCF_SHORT_SIGNAL) {
        // ignore short signals
      }  else if (difference < DCF_split_millis) {
        appendSignal(0);
      } else {
        appendSignal(1);
      }
    }
}

/**
 * The interrupt routine for counting seconds - increment hh:mm:ss.
 */
void advanceClock() {
    ss++;
    if (ss==60) {
      ss=0;
      mm++;
      if (mm==60) {
        mm=0;
        hh++;
        if (hh==24) 
          hh=0;
      }
    }
}

/**
 * Interrupthandler for INT0 - called when the signal on Pin 2 changes.
 */
void int0handler() {
  int length = millis() - previousFlankTime;
  if (length < DCF_MAX_JITTER) {
    // ignore jitter
    return;
  }
  // check the value again - since it takes some time to
  // activate the interrupt routine, we get a clear signal.
  DCFSignalState = digitalRead(DCF77PIN);
}


/**
 * Standard Arduino methods below.
 */

void setup(void) {
  // We need to start serial here again, 
  // for Arduino 007 (new serial code)
#ifdef DCF_DEBUG
  Serial.begin(9600);
#endif
  DCF77Init();
}


void loop(void) {
  
  if (ss != previousSecond) {
    serialDumpTime();
    previousSecond = ss;
  }
  if (DCFSignalState != previousSignalState) {
    scanSignal();
    previousSignalState = DCFSignalState;
  }
    //delay(20);
}