16 Bit Port Extender mit I2C-Bus PCA9555

Texas Instruments bietet mit dem PCA9555 einen 16 Bit Port-Extender an, von dem maximal 8 Stück parallel an einem I2C Bus betrieben werden können. Insgesamt können so 128 individuell als Ein- oder Ausgang konfigurierbare Leitungen bereitgestellt werden, wobei alle Eingänge bei jedem Flankenwechsel einen Interrupt auslösen.

Die Ausgänge sind in der Lage 25 mA dauerhaft gegen Masse zu schalten, für LEDs also ausreichend.

Sobald die Bestellung da ist, wird an dieser Stelle die dazu passende Arduino Bibliothek veröffentlicht.

Sammlung von Tipps und Tricks

Microchip hat unter 01146B.pdf eine schöne Sammlung an Tipps zusammengestellt, die sich auch auf andere Mikroprozessoren anwenden lassen.

Aus der Übersicht:

  1. 4×4 Keyboard with 1 Input
  2. 5V → 3.3V Active Clamp
  3. Brushless DC Fan Speed Control
  4. Creating a Dithered PWM Clock

Und viele weitere. Insbesondere Punkt 2 war für mich eine überraschend einfache Lösung für das übliche Problem mit Klemmdioden (Sättigung der 3,3V Schiene):

Auszug aus obigen PDF

Analog Multiplier MPY634

In dieser Kategorie landet von Zeit zu Zeit ein integrierter Schaltkreis, der für den einen oder anderen vielleicht praktisch sein könnte.

Diese Woche: Burr Brown (mittlerweile Texas Instruments) MPY-634. Dieser Chip enthält alles um rein analog Multiplikationen, Divisionen oder die Bestimmung von Quadratwurzeln durchzuführen.

Das Datenblatt auf focus.ti.com sagt eigentlich alles. In dem Zusammenhang gibt es von Analog Devices noch die App-Note MT-079 auf www.analog.com die die Grundlagen dieser (in Vergessenheit geratenen) Bausteine erklärt.

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);
}