/* Name: fz.cpp; TABSIZE=8, ENCODING=UTF-8, LINESEP=LF
* Projekt: Drehzahlmesser
* Zweck: Ausstattung einer Fräsmaschine: Spindeldrehzahl
* Autor: Henrik Haftmann
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/fuse.h>
#include <avr/signature.h>
FUSES={
0b11011101, // Keramikresonator, schnell hochfahren, kein Taktteiler
0b01010101, // RESET als Portpin, BrownOut bei 2,7 V
0b11111111, // unverändert
};
/* Pin-Zuordnung, LED-Anzeige gemeinsame Katode im Charlieplex
Pin Port Funk. Verwendung
1 Ucc 5 V über 2 Dioden
2 PB0 XTAL1 Quarz 12 MHz
3 PB1 XTAL2 Quarz 12 MHz
4 PB3 LED h
5 PB2 LED g
6 PA7 ICP Frequenzmess-Eingang
7 PA6 LED f
8 PA5 LED e
9 PA4 LED d
10 PA3 LED c,K3
11 PA2 LED b,K2,c
12 PA1 LED a,K1,b
13 PA0 LED K0,a
14 GND Masse
*/
typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;
typedef unsigned long long qword;
#define NOINIT __attribute__((section(".noinit")))
static byte seg[4] NOINIT; // von links nach rechts
register byte t0h asm("r2"); // High-Teil von Timer0 (2 Bit)
register char t1h asm("r3"); // High-Teil von Timer1
#if 1
register word captn asm("r4"); // Anzahl Flanken = Capture-Ereignisse
register word capt1 asm("r6"); // Letzter Capture-Wert
#else
static volatile word captn NOINIT; // Anzahl Flanken = Capture-Ereignisse
static volatile word capt1 NOINIT; // Letzter Capture-Wert
#endif
// Register R8 ff. wird stets(!) von __udivmod64 benutzt.
// Register R4..R8 werden in onT1O genutzt. Nur R2..R3 erscheinen frei.
// Multiplexen der Anzeige: Die Charlieplex-Verdrahtung ist hier regelmäßig:
// Das Katodenbit schiebt die Anodenbits ab da nach links.
// Bit: 8 7 6 5 4 3 2 1 0
// Segment D0: h g f e d c b a K
// D1: h g f e d c b K a
// D2: h g f e d c K b a
// D3: h g f e d K c b a
// Ausgang: B3 B2 A6 A5 A4 A3 A2 A1 A0
// Interruptfrequenz F_CPU/8/256 = 1,9 kHz, 488 Hz Multiplexfrequenz
// Um die Capture-ISR nicht zu beeinträchtigen mit freigegebenen Interrupts,
// Rekursion unwahrscheinlich und (falls doch) unschädlich
ISR(TIM0_OVF_vect,ISR_NOBLOCK) {
byte b = ++t0h&3; // Digit
byte c = seg[b]; // Segmente
word d = (word)c<<1; // nach links geschobene Segmente
byte k = 1<<b; // Katoden-Bit (1,2,4 oder 8)
c &= k-1; // rechts von Katodenbit behalten, sonst 0
d &= ~((k<<1)-1); // links von Katodenbit behalten, sonst 0
d |= c; // Bits (Anoden) zusammensetzen
b = (d>>5)&0x0C; // DDRB vorbereiten: Bit 8 und 7
byte a = d&0x7F; // DDRA, PORTA vorbereiten
DDRA = 0; // Alles aus: Katoden auf h oder z genügt
PORTB= b;
DDRB = b; // H oder z, niemals h (= Pullup)
PORTA= a|0x80; // Pullup an PA7 behalten
DDRA = a|k; // Jeweilige Katode auf L
wdt_reset();
}
// Bei zu hoher Interruptlast durch zu hohe Eingangsfrequenz an ICP
// bleibt der Anzeigemultiplex aus, und der Watchdog beißt zu.
// 200 kHz wurden im Versuch noch sicher verarbeitet,
// was einer Drehzahl von über 2.000.000 U/min entspräche!
// Der Wirbelstromsensor schafft nur garantiert 5 kHz,
// was immerhin (bei 9 Zähnen) 30.000 U/min entspräche.
static void numout(word n, char nk=0) {
// 10 Siebensegmentkodes 0..9
static const PROGMEM byte seg7[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
for (char i=4; --i>=0;--nk) {
byte r = n%10;
seg[i] = n || nk>=0 ? pgm_read_byte(seg7+r) | (nk?0:0x80) : 0;
n /= 10;
}
}
// Diese beiden ISRs begrenzen die maximale Eingangsfrequenz.
// Diese liegt bei 40 CPU-Takten = 100 kHz.
// Diese ISR braucht (vom sleep bis reti) gar 50 Takte.
// Mit Registervariablen 41 Takte.
// In Assembler 21 Takte.
// Bis 1 MHz könnte man mithilfe Zählereingang lösen, alles schnellere
// zusätzlich mit externem Vorteiler (74HC93 o.ä.)
ISR(TIM1_CAPT_vect,ISR_NAKED) {
#if 0
++captn;
capt1=ICR1;
if (TIFR1&1) GPIOR0|=2;
#else
asm(
" in r6,__SREG__\n" // capt1L zum Retten von SREG missbrauchen
" inc r4 \n"
" brne 1f \n"
" inc r5 \n" // captn inkrementiert
"1: out __SREG__,r6\n"
" in r6,%A0 \n"
" in r7,%B0 \n" // capt1 eingelesen
" sbic %1,0 \n" // TIM1_OVF anhängig?
" sbi %2,1 \n" // Gleichzeitigkeit erfassen
" reti \n"
::"I"(_SFR_IO_ADDR(ICR1)),"I"(_SFR_IO_ADDR(TIFR1)),"I"(_SFR_IO_ADDR(GPIOR0)));
#endif
}
// Folgende Fälle des zeitlichen Aufeinandertreffens von TIM1_CAPT und TIM1_OVF:
// (1) CAPT (weit) vor OVF: Sequenzielle Abarbeitung, R7:R6 != 0
// (2) CAPT kurz vor OFV: Bit 1 von GPIOR0 wird gesetzt aber R7:R6 != 0 (bspw. 0xFFFF)
// (3) Gleichzeitig: Bit 1 von GPIOR0 wird gesetzt und R7:R6 == 0 (als 0x10000 ansehen!)
// (4) CAPT kurz nach OFV: Bit 1 von GPIOR0 wird gesetzt und R7:R6 == 0 oder == 0x0001
// (da die CPU zum Interruptstart aus Sleep 8 Takte braucht kann sich CAPT vordrängeln)
// (5) CAPT nach OFV: Interrupts werden in der richtigen Reihenfolge abgearbeitet,
// Bit 1 von GPIOR0 wird (noch) nicht gesetzt, R7:R6 != 0
// (Interruptpriorität klärt nur den Interruptstart und ist unabhängig von
// gegenseitiger Unterbrechung. Die beiden ISRs unterbrechen sich nicht gegenseitig,
// aber die Anzeigemultiplex-ISR darf unterbrochen werden.)
// Damit erweist sich lückenlose Frequenzmessung mit AVR doch noch als schwieriges Unterfangen
word capt0, // Vorheriger Capture-Wert
savea,saveb,savec NOINIT;
// Wettlaufsituation: Capture kann zeitgleich passieren, weil in Hardware
// Deshalb wird ICR1 bei TIM1_CAPT_vect eingelesen und damit
// die beiden RAM-Variablen capt1 und captn synchron gehalten
// Assembler: 40 Takte (von sleep bis reti)
ISR(TIM1_OVF_vect,ISR_NAKED) {
#if 0
savec = captn;
savea = capt0;
capt0 = saveb = capt1;
GPIOR0= GPIOR0<<1|1;
captn = 0;
#else
asm(
" sts savec,r4 \n"
" sts savec+1,r5 \n"
" lds r4,capt0 \n"
" lds r5,capt0+1 \n"
" sts savea,r4 \n"
" sts savea+1,r5 \n"
" sts saveb,r6 \n"
" sts saveb+1,r7 \n"
" sts capt0,r6 \n"
" sts capt0+1,r7 \n"
" in r6,__SREG__ \n"
" in r4,%0 \n"
" sec \n"
" rol r4 \n"
" out %0,r4 \n"
" clr r4 \n"
" clr r5 \n"
" out __SREG__,r6 \n"
" reti \n"
::"I"(_SFR_IO_ADDR(GPIOR0)));
#endif
}
// Interruptfrequenz F_CPU/8/65536 = 7,5 Hz
// Einstieg mit gesperrten Interrupts
static void onT1O() {
word a=savea,b=saveb,c=savec;
// Je nachdem ob im gerade vergangenen Intervall 1 Puls festgestellt wurde
// verlangsamt sich entsprechend die Messfrequenz bis max. 8 Interrupts
// Hier leiste ich mir mal eine "unsigned long long"-Arithmetik
if (c) { // Puls(e) gekommen?
if (t1h>0) {// Gültige Zeitmessung?
if (GPIOR0&8 && a<0x8000) --t1h; // a muss als 0x1aaaa gesehen werden
if (GPIOR0&4 && b<0x8000) ++t1h; // b muss als 0x1bbbb gesehen werden
dword T = ((dword)t1h<<16) + b - a;
//Dauer zwischen erstem und letztem Capture in Zählertakten
qword F = (qword)F_CPU
*60 //Sekunden pro Minute (Anzeige in U/min, nicht U/s)
*10 //Zehntel-Anzeige
/8 //Zähler-Vorteiler
/9; //Zähne am Zählrad = 0x13DE4355
dword f = F * c / T;
char nk=1;
while (f>9999) {
f/=10; // Dezimalstelle verwerfen
if (--nk<0) nk+=3;
}
numout(f,nk);
}
t1h = 1; // Ganzperiodenzähler neu starten
}else if (t1h>0 && ++t1h==8) { // 7 Interrupts = knapp 1 s kein Puls?
t1h = 0; // stillsetzen, beim nächsten Puls ungültige Zeitmessung
numout(0); // 0. ausgeben
// Obwohl 0.00 naheliegender erscheint ist das unrichtig,
// denn Frequenzen unter 1 Hz kann diese Anzeige nicht verarbeiten,
// ist nicht dafür ausgelegt.
// Beispielsweise 0,50 Hz würde fälschlich mit 0.00 angezeigt werden.
// (Man könnte auch 0.-- anzeigen aber das dürfte mehr verwirren.)
// Hingegen Frequenzen über 1 Hz können mit über 16 Bit aufgelöst werden.
}else if (t1h<0 && !++t1h) { // Lampentest-Zeit
numout(0); // ebenfalls 0. ausgeben
}
}
int main() {
for (byte i=0; i<4; i++) seg[i]=0xFF; // Lampentest
t1h = -2; // Lampentest-Zeit sowie ungültige Zeitmessung
ACSR |= 0x80; // Analogvergleicher abschalten: –70µA
MCUCR = 0x20; // CPU-Schlafmodus
TCCR0B= 0x02; // ÷8 ⇒ 2 kHz ⇒ 500 Hz Bildwiederholfrequenz
TIMSK0= 0x01; // Überlauf-Interrupt: Anzeige-Multiplex
TCCR1B= 0x82; // Capture H-L-Flanke mit Störunterdrückung, ÷8 Taktfrequenz, CTC mit OCR1A
TIMSK1= 0x21; // Capture- und Überlauf-Interrupt
// Der Watchdog kümmert sich darum, dass das Display nicht einbrennt
WDTCSR= 0x18;
WDTCSR= 0x18; // Watchdog mit 16 ms
sei();
for(;;) {
sleep_cpu();
if (GPIOR0&0x01) {
GPIOR0&=~0x01;
onT1O(); // Rechenaufwendige Routine in Hauptschleife
}
}
}
Detected encoding: UTF-8 | 0
|