Neuere Stromzähler sind zum Teil mit einem Optokoppler ausgestattet, der für jede verbrauchte Wattstunde einen Impuls liefert (1000 Impulse = 1 kWh). Der Anschluss an einen Arduino gestaltet sich dem entsprechend einfach: D+ Ausgang des Zählers auf 5V legen und den D- Ausgang an einen digitalen Eingang des Arduino anschließen, dieser muss noch mit einem 4k7 Widerstand gegen Masse gezogen werden um ein klares Signal zu erhalten.
Category Archives: arduino
Neues Midi-Pult (Update 4)
Nachdem aus dem 120 RGB-LED Projekt ein wunderbares Leitsystem für die Nachrichtenmeisterei bei der Museumsnacht 2010 geworden ist (Video folgt noch), steht nun ein neues Projekt vor der Tür: Ein edles Midi-Pult mit 32 Fadern, 64 Drehencodern und den darin integrierten Tastern.
Zum Aufbau: das Pult ist in zwei Teile, jeweils 19-Zoll/5HE, aufgeteilt. Beide Hälften verfügen über eigenständige A/D-Wandler (MCP3208) für die 100mm Fader und Digital-Inputs (74HC165) für die Encoder und Taster. Diese geben Ihre Daten auf einen zentralen Bus, der von dem Microcontroller auf MIDI umgesetzt wird.
Bei der Wahl des Prozessors standen die üblichen Verdächtigen zur Auswahl (PIC, Atmel und MSP), es ist aber – trotz der anhaltenden Lieferengpässe bei Atmel – ein Atmega328 geworden. Nachdem die Software recht schnell auf dem Arduino lauffähig war, ging es an die Code-Optimierung. Sobald ein wenig mehr IO-Leistung gebraucht wird (für die Encoder werden insgesamt 256 Bit über den Bus geschoben), sollte auf digitalRead und digitalWrite verzichtet werden. Die Umstellung von digitalRead(10) auf ((PINB & 0x04) >>2) brachte Faktor 40, so dass nun auch schnelle Drehungen ohne Verlust von Schritten erfasst werden. Das war dann auch der Abschied von Arduino.App und der Beginn der Umstellung auf Eclipse-CDT, das auch unter MacOS eine gute Unterstützung der kompletten AVR-Suite bietet.
Da das Pult länger halten und angenehm zu bedienen sein sollte, kommen die edlen ALPS-Produkte zum Einsatz, die in Stückzahlen auch bezahlbar sind. Die Encoder stammen aus der Automotive Serie, sind staubdicht und auf 500.000 Zyklen ausgelegt.
Momentan sind die Platinen in der Produktion bei pcb-pool, die Frontplatte wird von Schaeffer-AG hergestellt. Sobald es an den Zusammenbau geht, folgen weitere Bilder.
Update 1:
Mittlerweile gibt es die ersten Bilder von pcb-pool:
Jetzt bleibt nur zu hoffen, dass die Stencils für die richtige Seite angefertigt werden.
Schaeffer hat die Frontplatten bereits gefräst, da der Versand für den 01.10. geplant ist, müssten diese also zusammen mit den Platinen am Samstag in der Post sein.
Update 2:
Es wurde dann doch Montag, aber sowohl die Platinen als auch die Frontplatten waren heute in der Post. Da die Encoder erst am 18.10. geliefert werden, konnten die Frontplatten schon teilweise bestückt werden:
Morgen geht es, sofern die Zeit es zulässt an die Bestückung der Platinen:
Update 3:
Die SMD-Bestückung der kleinen Platinen ist erledigt, nur für die Wandlerplatinen reichte die Lötpaste dann doch nicht mehr. Am Wochenende wird das Gehäuse gesägt und der Aufbau getestet.
Update 4:
Das Gehäuse war dann recht schnell gesägt, die Bestückung erledigt, alle Stecker (68 Stück für die Flachbandkabel im Inneren) verpresst. Der erste Test verlieft fast gut, aber drei Lessons Learned gab es dann doch noch bei dem Projekt:
- Niemals mit 100 nF X7R Abblockkondensatoren geizen
- Einen A/D Wandler immer nur mit einer Taktfrequenz betreiben, die tatsächlich kleiner ist als die maximal zulässige (der Atmel war dann doch schneller als erwartet)
- Traue niemals einem Datenstrom, der seriell übertragen wurde (es kann tatsächlich mal ein Byte fehlen)
Aber nun ist das Pult fertig und konnte erfolgreich am MOTU-828MK2 MIDI-Port betrieben werden, alle 160 Midi-Signale werden einwandfrei übermittelt, in dem zugehörigen Live-Set konnten allerdings auf Grund der Ableton-Einschränkung nur jeweils 128 gleichzeitig getestet werden.
Zum Abschluss noch ein paar Bilder vom fertigen Gerät:
Serial RGB-Dot (Update 4)
Ein neues Projekt ist gerade unterwegs: Ähnlich dem FNordlicht bzw. BlinkM suchte ich eine Lösung eine einzelne RGB-LED seriell anzusteuern, die 100 einzelnen LEDs werden etwa einen Meter entfernt voneinander sein, so dass die bereits existierenden RGB Treiber für 8 oder 64 LEDs nicht wirklich praktikabel oder zu teuer waren. Momentan liefert digikey die PIC12F508 samt RGB-LEDs und pcb-pool wird nächste Woche die passenden Platinen in den Briefkasten werfen.
Statt I2C wird in dem PIC ein 24-Bit Schieberegister mit Latch realisiert. Nach dem Latch liegen dann die drei Software PWMs. Da links und rechts auch noch Lötpads hinmüssen, ist die Platine mit 10mmx17mm dann doch größer geworden als ursprünglich geplant, aber immer noch kleiner und deutlich günstiger.
Der PIC-Code samt Arduino-Library folgt sobald die ersten Tests erfolgreich waren.
Update 1:
Die Platinen sind zwar noch unterwegs, aber der PIC-Code ist mittlerweile fertig. Vorerst nur mit einem 8-Bit PWM, später folgt vielleicht ein Upgrade auf 10-Bit.
Ursprünglich sollte der Latch über den Watchdog-Timer erfolgen (wenn 18ms lang kein Taktsignal vorliegt, werden die Daten aus dem Schieberegister in die PWM-Register kopiert). Da unter den einzelnen Prozessoren der WDT-Timer aber schwanken kann (laut Datenblatt zwischen 9ms und 30ms) erfolgt jetzt die Übernahme über den integrierten Timer nach 10ms.
Update 2:Von pcb-pool kam jetzt das erste Bild vom Produktionsprozess. So wie es ausschaut, wird am Wochenende alles zusammengebaut.
Update 3:Alle Platinen sind nun bestückt und verlötet, morgen erfolgt der Upload des PIC-Codes:
Update 4:Die LEDs sind jetzt auch alle verbaut, fehlt nur noch der Funktionstest.
;; PWM-Code fuer RGB-Dot ;; Copyright 2010 Wolfgang Jung (w@elektrowolle.de) list p=12f508 #include p12f508.inc __CONFIG _MCLRE_OFF & _CP_OFF & _WDT_OFF & _IntRC_OSC ;; Register REG_B_RED EQU 0x08 REG_B_GREEN EQU 0x09 REG_B_BLUE EQU 0x0A REG_C_RED EQU 0x0B REG_C_GREEN EQU 0x0C REG_C_BLUE EQU 0x0D REG_LCLK EQU 0x0E REG_OVERFLOW EQU 0x0F REG_PWMCNT EQU 0x10 REG_WORK EQU 0x11 ;; Pinassignments PIN_RED EQU H'0' PIN_GREEN EQU H'1' PIN_BLUE EQU H'2' PIN_SDIN EQU H'3' PIN_SDOUT EQU H'5' PIN_SCLK EQU H'4' ;; Konstanten DEF_OPTION EQU 0xC7 ; Kein Wakeup, Kein Pull-Up, TOCS=fOSC/4, TOSE=1, PSA=TMR0, Prescale=256 DEF_TIMEOUT EQU 39 ; Bei 4 MHz und Prescale=256 -> etwa 10ms ORG 0x000 ;; startup MOVLW DEF_OPTION OPTION ;; Port-Config OIIOOO MOVLW 0x18 TRIS GPIO L_MAIN MOVF GPIO, W ; Lade Port nach W XORWF REG_LCLK, W ; XOR mit letztem CLK MOVWF REG_WORK BTFSS REG_WORK, PIN_SCLK ; Flanke von SCLK? GOTO L_NO_EDGE L_EDGE_DETECTED: BTFSC REG_LCLK, PIN_SCLK GOTO L_FALLING_EDGE L_RISING_EDGE: ;; steigende Flanke -> SDIN auswerten CLRF REG_OVERFLOW BCF STATUS, C ; clear CARRY-Flag BTFSC GPIO, PIN_SDIN BSF STATUS, C ; CARRY-Flag enthaelt nun SDIN, nun das Bit durch die 24-Bit schieben RLF REG_B_RED, 1 RLF REG_B_GREEN, 1 RLF REG_B_BLUE, 1 ;; letztes Bit merken fuer das naechste Modul BTFSC STATUS, C BSF REG_OVERFLOW, 0 GOTO L_CLR_WDT L_FALLING_EDGE: ;; Fallende Flanke -> letztes Bit wieder auf SDOUT schreiben BCF GPIO, PIN_SDOUT BTFSC REG_OVERFLOW, 0 BSF GPIO, PIN_SDOUT L_CLR_WDT: ;; Timer0 zuruecksetzen, Prescaler muss danach angepasst werden MOVLW 0 MOVWF TMR0 MOVLW DEF_OPTION OPTION ;; letzten Input merken MOVF GPIO, W MOVWF REG_LCLK L_NO_EDGE: ;; Keine Flanke seit DEF_TIMEOUT timer0 MOVLW DEF_TIMEOUT SUBWF TMR0, W ;; Prescaler muss angepasst werden MOVLW DEF_OPTION OPTION ;; Skip, wenn tmr0 < DEF_TIMEOUT BTFSS STATUS, C GOTO L_DO_PWM L_COPY_BUFFER_TO_PWM: ;; Daten aus Schieberegister in PWM-Register MOVF REG_B_RED, W MOVWF REG_C_RED MOVF REG_B_GREEN, W MOVWF REG_C_GREEN MOVF REG_B_BLUE, W MOVWF REG_C_BLUE L_DO_PWM: DECF REG_PWMCNT, F ; Ist pwmCnt == 0? BTFSS STATUS, Z GOTO L_PWM_RED ;;; PWMCnt == 0 -> Also alle Pins auf high BSF GPIO, PIN_RED BSF GPIO, PIN_GREEN BSF GPIO, PIN_BLUE L_PWM_RED: MOVF REG_PWMCNT, W SUBWF REG_C_RED, W BTFSS STATUS, Z ; Skip, wenn pwmCnt > red GOTO L_PWM_GREEN MOVF REG_C_RED, W ; Wenn red==0, dann niemals an BTFSS STATUS, Z BCF GPIO, PIN_RED L_PWM_GREEN: MOVF REG_PWMCNT, W SUBWF REG_C_GREEN, W BTFSS STATUS, Z GOTO L_PWM_BLUE MOVF REG_C_GREEN, W BTFSS STATUS, Z BCF GPIO, PIN_GREEN L_PWM_BLUE: MOVF REG_PWMCNT, W SUBWF REG_C_BLUE, W BTFSS STATUS, Z GOTO L_MAIN ; Zurueck zum Anfang MOVF REG_C_BLUE, W BTFSS STATUS, Z BCF GPIO, PIN_BLUE
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); }