Drehzahlmesser für Fräse

Mill with rotation speed display

Fräse mit Drehzahlanzeige

Für meine Rotwerk Fräse fehlte mir lange eine Anzeige für die Drehzahl der Spindel. Nur wenn man beim Fräsen eine zum Fräser und Material passende Drehzahl wählt erhält man auch die besten Ergebnisse. Irgendwann habe ich mir dann also ein Herz genommen und ein Drehzahlmessgerät mit 7-Segment-Anzeigen gebastelt.

Saubere Impulse

Die Drehzahl wird mittels einer Reflexlichtschranke und einem auf die Spindelmutter der Fräse geklebten Aluminiumfolienstreifen als Reflektor abgenommen. Die Spindelmutter war die einzige einigermaßen gut zugängliche Stelle an der Spindel ohne beim Arbeiten im Weg zu sein.

Schaltplan 1: Lichtschranke

Die Reflexlichtschranke vom Typ OPB706A habe ich irgendwann mal aus einem Gerät ausgebaut und ist eine einfache Kombination aus Infrarot-LED und Fototransistor mit Infrarotfilter davor (um Umgebungslicht abzuhalten). Der optimale Abstand zum Reflektor, den ich versucht habe einzuhalten, beträgt 1.27 Millimeter. Die Grundbeschaltung besteht aus einem 120Ω Vorwiderstand für die IR-LED (bei 5V) und einem 1kΩ Widerstand vom Kollektor des Fototransistors nach +5V.

Raw signal

Rohsignal

Als nächsten Schritt habe ich die Lichtschranke installiert und ein Oszilloskop an das Rohsignal angeschlossen (bei drehender Spindel). Wenn sich der Reflektor an der Lichtschranke befindet wird der Fototransistor leitend und die Spannung am Ausgang fällt entsprechend ab. In nebenstehendem Bild ist gut zu erkennen, dass das Signal recht unsauber ist.

Schaltplan 2: RC-Glied

Schaltplan 2: RC-Glied

Um dieses Signal etwas zu glätten habe ich ein RC-Glied nachgeschaltet, bestehend aus einem 10nF Kondensator und einem 10kΩ Widerstand.

Smoothed signal

Geglättetes Signal

Am Oszilloskop kann man sehen, dass das Signal tatsächlich ein wenig sauberer ist und weniger Spannungsspitzen hat. Starke Spannungsspitzen könnten nämlich später dazu führen, dass mehrere Impulse detektiert werden, ähnlich wie bei prellenden Tastern. Die Dimensionierung der Bauteile habe ich durch Experimentieren bestimmt. Wenn man das Signal noch stärker glätten würde, würde die Differenzspannung zwischen „high“ und „low“ immer geringer, was das sichere Erkennen des Impulses schwerer macht.

Schmitt trigger circuit

Schaltplan 3: Schmitt-Trigger

Um das geglättete Signal in ein digitales Signal umzuwandeln, das mit einem Mikrocontroller ausgewertet werden kann, habe ich einen Schmitt-Trigger mit dem Operationsverstärker IC UA741CN aufgebaut. Am Oszilloskop habe ich abgelesen, dass eine Einschaltschwelle U_E von 4V und eine Ausschaltschwelle U_A von 4,75V sinnvoll erscheint. Die Werte der Widerstände habe ich dann ausgehend von R_5 (mit 1kΩ vorgegeben) berechnet (die Formeln sind von mikrocontroller.net):

R_4 = \frac{R_5 \cdot U_E}{V_{cc} - U_A} = \frac{10k\Omega \cdot 4V}{5V - 4.75V} = 16k\Omega

Für R_6 habe ich den nächstliegenden Wert genommen, den ich vorrätig hatte.

R_6 = \frac{R_5 \cdot U_E}{U_A - U_E} = \frac{1k\Omega \cdot 4V}{4.75V - 4V} \approx 5.3k\Omega \approx 5.6k\Omega

Digital signal

Digitales Signal

Am Ausgang des Schmitt-Triggers liegt dann ein reines digitales Signal an, wie man wiederum auf dem Oszilloskop sehen kann. Der Schmitt-Trigger hat übrigens die in diesem Fall nützliche Eigenschaft das Signal zu invertieren, d.h. beim Vorbeilaufen des Reflektors gibt es jetzt einen positiven Impuls statt wie vorher einen negativen (auch wenn sich beides problemlos mit einem Mikrocontroller auswerten lässt).

 

Mikrocontroller und Spannungsversorgung

Als Mikrocontroller habe ich einen ATtiny2313 gewählt, da er von der Anzahl der I/O-Pins her den Anforderungen entspricht und den benötigten Timer/Counter sowie externe Interrupts hat (mehr dazu weiter unten).

Microcontroller wiring

Schaltplan 4: Mikrocontroller

Power supply

Schaltplan 5: Spannungsversorgung

Den Mikrocontroller habe ich standardmäßig beschaltet, die 5V Spannungsversorgung stelle ich mit dem altbekannten 7805-Festspannungsregler her.

 

7-Segment-Anzeigen

Da meine Rotwerk Fräse einen Drehzahlbereich von etwa 0 – 2500 U/min hat, sollte die Anzeige 4-stellig sein.

Um die Ansteuerung der vier Anzeigen zu vereinfachen habe ich vier CD4511BE BCD zu 7-Segment Decoder Bausteine verwendet. Diese ICs haben vier Eingänge, an die die darzustellende Ziffer als 4-stelliger Binärcode angelegt wird. Mit einem Latch kann der angelegte Wert auch zwischengespeichert werden, so dass das Multiplexen der vier Bausteine sehr einfach wird: Ich verwende nur vier Leitungen für den BCD-Code an den alle vier ICs angeschlossen ist und wähle die Anzeige (d.h. die Dezimalstelle) mit der Latch Enable Leitung an.

7 segment display driver

Schaltplan 6: 7-Segment-Anzeigen

Die Leitungen zu den einzelnen Segmenten a,b,…,f,g habe ich hier im Schaltplan weggelassen. Ich verwende 7-Segment-Anzeigen mit gemeinsamer Kathode der LEDs. Jede LED ist an ihrer Anode über jeweils einen 470Ω Vorwiderstand an den entsprechenden Ausgang des Treiber-IC’s angeschlossen.

Den gesamten Schaltplan habe ich hier noch einmal in einem PDF-Dokument zusammengefasst: Gesamtschaltplan

Gehäuse

Die gesamte Schaltung habe ich in einem Plastikgehäuse untergebracht und mit einem Biegehals (früher Teil eines Tischventilators) an der Fräse befestigt.

Software

Die Software habe ich in C geschrieben. Prinzipiell verwende ich den Timer/Counter 1 des ATtiny2313 dazu die Zeit zwischen zwei Impulsen, d.h. die Zeit einer Umdrehung zu messen. Anfang und Ende einer Umdrehung werden durch je einen Impuls bestimmt, die den externen Interrupt INT0 auslösen. Daraufhin wird die gemessene Zeit in einer Variable (ms_since_last, us_since_last) gespeichert. Alle 250 ms wird aus diesem Wert die Drehzahl berechnet und auf den Anzeigen ausgegeben.

Ich hoffe der Quellcode ist durch die Kommentare einigermaßen selbsterklärend:

/*
 * Drehzahlmesser.c
 *
 * Created: 30.08.2014 10:51:36
 * Author: Jacob Seibert
 */
#include <avr/io.h>
#include 
#ifndef F_CPU
#warning "F_CPU war noch nicht definiert, wird nun mit 8000000 definiert"
#define F_CPU 8000000UL     /* Quarz mit 8 MHz */
#endif
#include <util/delay.h>
#include <avr/interrupt.h>
#define DECIMAL_0 16
#define DECIMAL_1 32
#define DECIMAL_2 64
#define DECIMAL_3 128
#define BLANK_DIGIT 15
// display update rate (milliseconds)
#define UPDATE_TIME 250
// counters
volatile uint16_t ms_since_last = 0,
                  us_since_last = 0,
                  ms_temp = 0;
/*
 * Displays a digit at the given decimal. Use macros for the decimal.
 */
void displayDigit(uint8_t digit, uint8_t decimal) {
  // mask lower 4 bit
  digit = digit & 0xf;
     
  // set digit
  PORTB = 0xf0 | digit;
     
  // disable latch
  PORTB &= ~decimal;
     
  _delay_us(1);
     
  // enable latch
  PORTB |= 0xf0;
}
 
/*
 * Blank the display
 */
void displayBlank(void) {
  // disable latch and display blank digit
  PORTB = BLANK_DIGIT;
     
  _delay_us(1);
     
  // enable latch
  PORTB |= 0xf0;
}
 
/*
 * Displays an integer
 */
void displayNumber(uint16_t num) {
  // limit to 4 decimals
  num = num % 10000;
     
  // set decimal 0
  displayDigit(num % 10, DECIMAL_0);
     
  // set decimal 1
  num /= 10;
  displayDigit(num > 0 ? num % 10 : BLANK_DIGIT, DECIMAL_1);
     
  // set decimal 2
  num /= 10;
  displayDigit(num > 0 ? num % 10 : BLANK_DIGIT, DECIMAL_2);
     
  // set decimal 3
  num /= 10;
  displayDigit(num > 0 ? num : BLANK_DIGIT, DECIMAL_3);
}
 
/*
 * Initialization
 */
void init(void) {
  // set data direction registers
  DDRB = 0xff;
     
  // sensor input pin (external interrupt)
  DDRD &= ~(1 << PD2);
     
  // initialize timer/counter 1
  //        CTC mode, TOP: OCR1A
  TCCR1A &= ~((1 << WGM11) | (1 << WGM10));
  TCCR1B &= ~(1 << WGM13);
  TCCR1B |= (1 << WGM12);
  //        system clock, pre-scaler: 8
  TCCR1B &= ~((1 << CS12) | (1 << CS10));
  TCCR1B |= (1 << CS11);
  //        compare value: 1000 (compare match event every 1000 µs)
  OCR1A = 1000;
     
  // initialize external interrupt
  //        rising edge
  MCUCR |= (1 << ISC01) | (1 << ISC00);
  //        enable external interrupt 0
  GIMSK |= (1 << INT0);
     
  // globally enable interrupts
  sei();
     
  // blank display
  displayBlank();
}
 
/*
 * Interrupt service routine for the external sensor signal interrupt
 */
ISR(INT0_vect) {
  // start counting if not already counting
  if (!(TIMSK & (1 << OCIE1A))) {
    ms_temp = 0;
    TCNT1 = 0;
    TIMSK |= (1 << OCIE1A);
    return;
  }
     
  // store µs since last revolution and reset
  us_since_last = TCNT1;
  TCNT1 = 0;
     
  // store ms since last revolution and reset
  ms_since_last = ms_temp;
  ms_temp = 0;
}
 
/*
 * Interrupt service routine for the timer/counter 1 compare A match interrupt
 */
ISR(TIMER1_COMPA_vect) {
  // 1000 µs elapsed
  ms_temp++;
     
  if (ms_temp >= UPDATE_TIME) {
    // stop counting, assume 0 rpm
    TIMSK &= ~(1 << OCIE1A);
    us_since_last = 0;
    ms_since_last = 0;
  }
}
 
int main(void) {
  init();
  while(1) {
    // compute rpm
    uint32_t rpm;
    cli();
    if ((ms_since_last == 0) && (us_since_last == 0)) {
      rpm = 0;
    } else {
      rpm = 60000000UL / ((uint32_t)ms_since_last * 1000UL + (uint32_t)us_since_last);
      ms_since_last = 0;
      us_since_last = 0;
    }
    sei();
         
    displayNumber((uint16_t)rpm);
         
    _delay_ms(UPDATE_TIME);
  }
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Bitte beantworte kurz folgende Frage, um zu zeigen, dass du kein Roboter bist: *