Synopsis [Servos] Keywords Modellbau-Servo, Multiplexanzeige, PWM-Signal, Analog/Digitalwandung
Ein Modellbauservotester mit 3 stelliger Anzeige
![]() |
Ich wollte schon lange einen Servotester mit Anzeige der Pulsdauer bauen. Über ein Potentiometer wird der Winkel des Servos eingestellt. Eine 3 Farb-LED zeigt die Neutralstellung an. Bei der Wahl des Bausteins und dessen Pin-Belegung müssen die notwendigen Funktionen und die damit verbundenen Ausgänge bedacht werden:
Ich habe mich für einen [ATmega 8] entschieden (lag auch noch rum und die Pins sollten reichen). |
Features des Testers
- Einstellbereich von 0,30 bis 2,85 ms
- Farbled zeigt grün bei < 1 ms, grün blinkend von 1..1,47 ms, blau zwischen 1,48..1,52 ms, rot blinkend von 1,53..2,00 ms dann dauernd rot.
- Taster zum Umschalten der Anzeige und Betriebsmodus, Piezo gibt Beep aus. Betriebsmodi, Umschalten mit Taster:
- Anzeige ms
- Anzeige Versorgungsspannung
- Anzeige Servostrom
- Anzeige ms eines externen Signals
- Unterspannungswarnung durch Warnsignal
Ansteuerung von Modellbau Servos
![]() |
![]() |
Ein Modellbauservo sollte spätestens alle 20 ms einen Steuerimpuls bekommen. Die Impulslänge ist proportional zum Stellwinkel des Servos. Sie bewegt sich zwischen 0,3 und 2,7 ms. Für den Modellbauer interessant ist vor allem die Impulsdauer der Mittelstellung des Servos - hier 1,4 ms. Ich möchte eine Anzeige mit einer Auflösung von 10 µs also ein Anzeigebereich von 0,20 ms bis 3,00 ms.
Die erste Überlegung betrifft das Timing zur Erzeugung des PWM-Signals. |
Neutralstellung und Endausschläge verschiedener Hersteller
Hersteller | Max µs | Neutral µs | Min µs |
---|---|---|---|
robbe Futaba | 2120 | 1520 | 920 |
robbe alternativ | 2300 | 1600 | 1000 |
Modelcraft Servotester | 2000 | 1500 | 1000 |
Graupner | 2000 | 1500 | 1000 |
Timer und Pulsweitenmodulation
Die Pulsweite soll in 10 µs Servo-Schritten verstellbar sein. Hier die naheliegenden Kombinationen:
CPU Frequenz | Vorteiler | µs pro Zahl | Bewertung |
---|---|---|---|
1 MHz | 1:1 | 1 | Int. Oszillator ist vorkalibriert, Standard, Multiplikation mit 10 notwendig |
4 MHz | 1:8 | 2 | Mehraufwand bei Kalibrierung, Multiplikation mit 5 notwendig |
8 MHz | 1:8 | 1 | Mehraufwand bei Kalibrierung, Multiplikation mit 10 notwendig |
![]() |
Gewählt 1 MHz mit Vorteiler 1:1, zum Multiplizieren mit 10 gibt es ja beim Mega8 praktische Befehle.
Um die 20 ms = 20 000 µs zu zählen brauchen wir den 16 Bit Timer1 -Doku Seite 76ff. Betriebsart Fast PWM Mode siehe Seite 90ff. Der Timer zählt von 0 bis zu einem einstellbaren TOP Wert und springt dann wieder auf 0. Ein Ausgang kann beim Sprung von TOP auf 0 gesetzt und bei Erreichen eines einstellbaren Wertes OCR1A wieder rückgesetzt werden. ICR1 als TOP weil nicht verändert mit Wert 19 999 für 20 ms Wiederholungsintervall, OCR1A = Servo-Schritt * 10. Ausgang ist PB1 (OC1A). |
Einstellungen bei Initialisierung Servosignal ausgeben
Register | Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Beschreibung |
---|---|---|---|---|---|---|---|---|---|---|
TCCR1A | Bedeutung | COM1A1 | COM1A0 | COM1B1 | COM1B0 | FOC1A | FOC1B | WGM11 | WGM10 | Ausgang OC1A bei 0 setzen und bei OCR1A löschen |
Wert | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
Waveform Generation Mode: Fast PWM mit ICR1 als TOP |
|
TCCR1B | Bedeutung | ICNC1 | ICES1 | - | WGM13 | WGM12 | CS12 | CS11 | CS10 | |
Wert | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | Keine Vorteilung Timer mit CPU-CLK (No prescaling) |
A/D Wandler der ATmega Bausteine
Die Wandlung funktioniert nach dem Sukzessive-Approximations-Prinzip mit 10 Bit Auflösung. Es stehen im 28 Pol-PDIP-Gehäuse 5 Eingänge zur Verfügung. Man lese in der [Doku zu ATmega8] ab Seite 196 für genauere Details. Es müssen Entscheidungen getroffen werden:
- Vorteiler (Prescaler) für Takt an Wandler (soll im Bereich 50kHz..200kHz liegen) einstellen. Mögliche Teilungen 2,4,8,16,32,64,128. Gewählt 16 = > 1 MHz / 16 = 62,5 kHz
- Referenzspannung für Wandler ist AVCC = Spannung am Potentiometer
- Wandlung mit Interruptauslösung wird durch Anzeigentimer am Ende ausgelöst
- Zusätzliche Messung von Servostrom und Spannung
- Auflösung 10Bit oder 8Bit? - 10Bit bei C-Lösung
Einstellungen bei Initialisierung
Register | Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Beschreibung |
---|---|---|---|---|---|---|---|---|---|---|
ADMUX | Bedeutung | REFS1 | REFS0 | ADLAR | - | MUX3 | MUX2 | MUX1 | MUX0 | Interne Referenzspannung, Wert ist rechtsbündig in ADCH:ADCL |
Wert | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | Poti an PC5 (ADC5) | |
ADCSRA | Bedeutung | ADEN | ADSC | ADFR | ADIF | ADIE | ADPS2 | ADPS1 | ADPS0 | Wandler an, Start mit ADSCS, Interrupt frei |
Wert | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | Vorteiler auf CLK/16 |
Sieben Segmentanzeige mit gemeinsamer Anode
![]() |
![]() |
In der Bastelkiste fanden sich noch 7-Segmentanzeigen mit der Aufschrift [HP 5082-7730].
Die Segmente haben eine gemeinsame Anode und der Dezimalpunkt ist links. Die Anzeige ist gemultiplext, LED laufen auf gemeinsame Anode Dig1..Dig3 zusammen.
|
![]() |
Berechnung der Widerstände:Durch die 8 Segment-LEDs (incl. Dezimalpunkt) sollen 20 mA Strom (ISegment) fließen. Bestimmendes Bauteil ist hierbei Rseg. Die Spannung an Rseg ist 5V - Ueb - Ud = 5V - 0,1V - 1,8V = 3,1V. Rseg = 3,1V / 20 mA = 155 Ohm. Zur Verfügung stehen 150 Ohm oder 180 Ohm - gewählt Rseg = 150 Ohm. Durch den Transistor fließen maximal 8 * 20 mA = 160 mA. Ich habe noch [BC307B] Transitoren, deren minimaler Stromverstärkungsfaktor (Ic/Ib) Hfe liegt zwischen 222 und 455, sie können gerade eben den maximalen Strom aufnehmen. Da sie als Schalter fungieren sollen übersteuere ich sie 10 fach und gehe von einem Stom Ib = 160 mA / 20 = 8 mA aus. An Rb fällt eine Spannung von URb = 5V - Ube = 5V - 0,7V = 4,3V ab. Somit ist Rb = 4,3V / 8 mA = 537 Ohm - gewählt Rb = 560 Ohm. |
BCD nach 7 Segment umwandeln negative Logik
Binär | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Hex | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
BCD (Binary coded Dezimal) | HEX | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
r | |
Zahl | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | |
Pos. Log. | 3F | 06 | 5B | 4F | 66 | 6D | 7D | 07 | 7F | 6F | 77 | 7C | 39 | 5E | 79 | 71 | 50 |
Neg. Log | C0 | F9 | A4 | B0 | 99 | 92 | 82 | F8 | 80 | 90 | 88 | 83 | C6 | A1 | 86 | 8E | AF |
Multiplexen mit Timer0
Der 8 Bit Timer0 wird mit 1/8 Systemtakt betrieben und zählt damit von 0 bis 255. Mittels Interrupt wird so 488 mal pro Sekunde ein Anzeigenstellenwechsel durchgeführt.
3 Farb LED
Ansicht von Oben![]() |
Eine 3 Farb LED soll die Servostellung anzeigen Blau = Mitte, Grün < Mitte und Rot > Mitte.
Die Vorwiderstände der LEDs werden ermittelt: LEDBlau: 5V - 3,9V / 10 mA = 110 Ohm - gewählt 100 Ohm LEDRot: 5V - 2,0V / 10 mA = 300 Ohm - gewählt 270 Ohm LEDGrün: 5V - 2,3V / 10 mA = 270 Ohm - gewählt 270 Ohm |
ATmega 8
Belegung | Bedeutung | Pin | Pin | Bedeutung | Belegung | |
---|---|---|---|---|---|---|
Reset | (Reset) PC6 | 1 | A T M E G A 8 |
28 | PC5 (ADC5) | Poti |
a | (RXD) PD0 | 2 | 27 | PC4 (ADC4) | BattSpannung | |
b | (TXD) PD1 | 3 | 26 | PC3 (ADC3) | ServoStrom | |
c | (INT0) PD2 | 4 | 25 | PC2 (ADC2) | Dig1 | |
d | (INT1) PD3 | 5 | 24 | PC1 (ADC1) | Dig2 | |
e | (T0) PD4 | 6 | 23 | PC0 (ADC0) | Dig3 | |
VCC | 7 | 22 | GND | |||
GND | 8 | 21 | AREF | |||
LEDRot | (XTAL1) PB6 | 9 | 20 | AVCC | ||
LEDGrün | (XTAL2) PB7 | 10 | 19 | PB5 (SCK) | Taster | |
f | (T1) PD5 | 11 | 18 | PB4 (MISO) | ||
g | (AIN0) PD6 | 12 | 17 | PB3 (MOSI/OC2) | Piezopieper | |
DP | (AIN1) PD7 | 13 | 16 | PB2 (SS/OC1B) | LEDBlau | |
Eingang Empfänger | (ICP1) PB0 | 14 | 15 | PB1 (OC1A) | Servo |
Programmieradapter
Bedeutung | Pin | 6 Pol | Pin | Bedeutung |
---|---|---|---|---|
MISO | 1 | 2 | VTG (VCC) | |
SCK | 3 | 4 | MOSI | |
Reset | 5 | 6 | GND |
Prototyp
Schaltplan
Erweiterung zur Messung von Empfängersignalen
Ich wollte auch die Signale eines Empfängers messen und habe dazu den ICP-Eingang verwendet. Der Taster S1 dient nun zur Umschaltung zwischen Servoausgabe oder Empfängermessung.
Prinzip der Messung:
- Wenn innerhalb 25ms kein Signal vom Empfänger kommt, wird TIMER1_COMPA Interrupt ausgelöst und dadurch die Meldung "0FF" ausgegeben.
- Der ICP-Eingang kann je nach Einstellung einen TIMER1_CAPT Interrupt bei positiver oder negativer Flanke auslösen, zusätzlich wird der Wert des Timers1 in ICR1 gespeichert.
- Durch die beiden Interrupt-Service-Routinen (ISR) für Anzeige und ADC wird auf das ICP-Signal nicht immer sofort reagiert, daher wird bei steigender Flanke der Wert des Timers1 gespeichert und vom Wert bei fallender Flanke abgezogen -siehe C-Quellcode.
- Ist der gemessene Wert ausserhalb 200-2500ms wird die Meldung "Err" ausgegeben
Einstellungen bei Initialisierung Impuls-Messung des Empfängersignals
Register | Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Beschreibung |
---|---|---|---|---|---|---|---|---|---|---|
TCCR1A | Bedeutung | COM1A1 | COM1A0 | COM1B1 | COM1B0 | FOC1A | FOC1B | WGM11 | WGM10 | Ausgang OC1A ohne Funktion |
Wert | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | CTC Mode mit OCR1A als TOP | |
TCCR1B | Bedeutung | ICNC1 | ICES1 | - | WGM13 | WGM12 | CS12 | CS11 | CS10 | Input Capture pos Flanke |
Wert | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | Keine Vorteilung Timer mit CPU-CLK (No prescaling) |
Ausblick und ToDo
- Kontinuierliche automatische Servobewegung mit einstellbarer Geschwindigkeit.
- Erhöhung der Genauigkeit durch Kalibration.
- Die Anzeige könnte auf 4 Stellen erweitert werden.
- Statt LEDs eine mehrzeilige LCD-Anzeige.
- Schaltplan um 0,1Ohm Widerstand zwischen GND und Servo-GND ergänzen, Servo-GND ist an ADC3 (26) zwecks Strommessung angeschlossen.
Arduino-Quellcode
Der Servotester kann mit Arduino-Software programmiert werden, dazu muss ggfs. noch die Board-Erweiterung "MiniCore" für den Mega8 geladen werden, Projektseite:
https://github.com/MCUdude/MiniCore
Installationsanleitung (Englisch)
Unter Arduino -> Einstellungen bei Zusätzliche Boardverwalter-URLs dies hinzufügen:
https://mcudude.github.io/MiniCore/package_MCUdude_MiniCore_index.json
Dann unter Werkzeuge -> Board -> Boardverwalter MiniCore installieren.
Den Boardverwalter wieder schließen, unter Werkzeuge -> Board -> MiniCore -> ATMega8 wählen.
Einstellungen siehe Bild links, die Taktfrequenz wird in der Software für die delay-Funktion beim Compilieren beachtet, daher sollte die Clock mit dem verwendeten Takt auf dem Chip übereinstimmen.
Programmieren über z.B. USPasp, diese Warnung ignorieren:
Wichtig: Analog-EIngänge werden immer abgefragt, daher Spannungsteiler und Strommessung anschließen:
0,1Ohm Widerstand zwischen GND und Servo-GND ergänzen, Servo-GND ist an ADC3 (26) zwecks Strommessung angeschlossen.
Sonst piepst die Unterspannungswarnung ständig.
// Servotester V 2.1 (c) Oliver Mezger 29.1.2022
// CC-Lizenz: creativecommons.org/licenses/by-sa/3.0/de/
// ATMega8 Internal 1MHz
#include <avr/io.h> // Definitionen laden
#include <util/delay.h> // Delay-Bibliothek laden
#include <avr/interrupt.h>
// Zuordnungen zu Ports
#define SegmenteIn PIND
#define SegmenteOut PORTD
#define SegmenteDdr DDRD
#define SteuerungIn PINB
#define SteuerungOut PORTB
#define SteuerungDdr DDRB
#define StelleIn PINC
#define StelleOut PORTC
#define StelleDdr DDRC
#define LEDrot (1<<PB6)
#define LEDgn (1<<PB7)
#define LEDblau (1<<PB2)
#define LEDmask ~(LEDrot|LEDgn|LEDblau)
#define Taster PB5
#define Piepser PB3
const unsigned char bcd_7[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xaf};
const unsigned char SMIN = 100, mitte = 150, SMAX = 200;
const unsigned char mtoleranz = 3;
volatile unsigned char anzeige[3]={0,0,0};
typedef enum {servostrom,vcc,ms} adcArt;
volatile unsigned int adcWert[]={0,9000,0}; // Messwerte des ADC
adcArt ausgabeArt= ms;
typedef enum {initservo,servo,spannungsmessung,strommessung,initimpulsmessung,impulsmessung,initprog,prog} betriebsmode;
betriebsmode bmode = initservo;
ISR(TIMER0_OVF_vect){ // Timer fuer Anzeige wird 488 mal pro Sek aufgerufen
static unsigned char stelle=6,dzeiger=0;
StelleOut &= 0b11111000; // Stelle ausschalten
SegmenteOut = bcd_7[anzeige[dzeiger++]]; // Zeile laden
if (dzeiger == 2) SegmenteOut &= 0b01111111; // Dezimalpunkt setzen
StelleOut = (StelleOut & 0b11111000) | stelle; // Stelle anschalten
stelle = ((stelle << 1) + 1) & 0b111; // naechste Stelle neg. Logik
if (dzeiger >2){
stelle = 6; // 110 wegen neg. Logik
dzeiger = 0;
}
ADCSRA |= (1 << ADSC); // starte Messung
}
void ausgeben(){
static unsigned char rgb_led_dunkel = 0;
unsigned int n,k=adcWert[ms];
switch (ausgabeArt){ // auszugebender Wert
case servostrom:
n = adcWert[servostrom] *27 /10;
break;
case vcc:
n = adcWert[vcc] * 114 / 100;
break;
case ms:
n = adcWert[ms];
break;
}
rgb_led_dunkel = ~rgb_led_dunkel;
if (k <= mitte-mtoleranz) // Binaerer Baum fuer 5 Faelle
if (k < SMIN)
SteuerungOut = (SteuerungOut & LEDmask) | LEDgn; // unter Minimum
else
SteuerungOut = (SteuerungOut & LEDmask) | (LEDgn & rgb_led_dunkel); // zwischen Minimum und unterer Toleranz
else if (k < mitte+mtoleranz)
SteuerungOut = (SteuerungOut & LEDmask) | LEDblau; // in der Neutral-Tolleranz
else if (k <= SMAX)
SteuerungOut = (SteuerungOut & LEDmask) | (LEDrot & rgb_led_dunkel); // zwischen Toleranz und Maximum
else
SteuerungOut = (SteuerungOut & LEDmask) | LEDrot; // ueber Maximum
anzeige[0] = n % 10;
n /= 10;
anzeige[1] = n % 10;
anzeige[2] = n / 10;
}
void ausgebenMeldung(unsigned char n){
switch (n){
case 1: // OFF
anzeige[2]=0;
anzeige[1]=15;
anzeige[0]=15;
break;
default: // Err
anzeige[2]=14;
anzeige[1]=16;
anzeige[0]=16;
}
}
ISR(ADC_vect){ // Abfragen des ADC
static adcArt adcKanal= servostrom;
static unsigned int adcMess[]={0,0,0}; // Messungen mitteln
static unsigned char i = 0;
switch (adcKanal){
case servostrom:
if (adcMess[servostrom] < ADC) adcMess[servostrom] = ADC; // Maximum
if ((i&127)==127){ // 128 Messungen
adcWert[servostrom] = (adcMess[servostrom]);
adcMess[servostrom] = 0;
ausgeben();
}
adcKanal=vcc; // naechster Kanal ist dran
break;
case vcc:
if (adcMess[vcc] > ADC) adcMess[vcc] = ADC; // Minimum
if ((i&127)==127){ // 128 Messungen
adcWert[vcc] = (adcMess[vcc] >> 1);
adcMess[vcc] = 9000;
ausgeben();
}
adcKanal=ms;
break;
case ms:
adcMess[ms] += ADC; // Durchschnitt
adcKanal = servostrom;
if ((i&7)==7){ // 8 Messungen
adcWert[ms] = (adcMess[ms] >> 5) + 30;
adcMess[ms] = 0;
OCR1A = adcWert[ms] * 10; // Impulsdauer einstellen
ausgeben();
}
i++; // Eine Messrunde ist rum
}
ADMUX = (ADMUX & 0b11111000) | (adcKanal+3); // Kanal fuer nachste Messung einstellen
}
ISR(TIMER1_COMPA_vect){ // ISR fuer Timer-Ueberlauf wenn kein Impuls kommt
ausgebenMeldung(1);
}
ISR(TIMER1_CAPT_vect){ // ISR fuer Signalwechsel am Eingang
static unsigned int n = 0;
if (SteuerungIn & 1){ // War es eine steigende Flanke
TCCR1B &= ~(1<<6); // auf fallende Flanke reagieren
n = ICR1;
}
else {
TCCR1B |= (1<<6); // auf steigende Flanke reagieren
n = (ICR1-n)/10;
if (n>20 && n < 250){
adcWert[ms] = n;
ausgeben();
}
else
ausgebenMeldung(0);
TCNT1 = 0; // Setze den Zaehler auf 0
}
}
void peepdown(){
SteuerungDdr |= 1<<Piepser;
for (int i = 15;i<30;i++){
OCR2 = i;
_delay_ms(10);
}
SteuerungDdr &= ~(1<<Piepser);
OCR2 = 15; // Piepsfrequenz 4167Hz
}
int main(){
unsigned char taste = 0;
TCCR0 = 2; // AnzeigenTimer Systemtakt/8 / 256 = 488,3Hz
TIMSK |= (1<<TOIE0); // Timerinterrupt frei geben
TCCR2 = 0b00011010; // Piepser CTC,Toggle, Systemtakt / 8
OCR2 = 15; // Piepsfrequenz 4167Hz
SegmenteDdr=0xff; // Ausgang
StelleDdr=0b00000111; // Ausgang
SteuerungDdr = 0b11000110; // Ausgaenge LED und Servo
SteuerungOut = 0b00101001; // Pieperausgang auf 1
sei(); // globale Interruptfreigabe
while(1){ // Endlosschleife
if ((SteuerungIn & (1<<Taster)) == 0){
_delay_ms(10); // Prellen abwarten
SteuerungDdr |= 1<<Piepser;
while ((SteuerungIn & (1<<Taster)) == 0); // warten bis Taste wieder losgelassen
SteuerungDdr &= ~(1<<Piepser);
taste++;
_delay_ms(30); // Prellen abwarten
}
switch (bmode){
case initservo:
cli(); // Interrups sperren
ADMUX = 0b11000101; // ADWandler Interne Referenz rechtsbuendig, ADC5
ADCSRA = 0b10001100; // ADWandler Vorteiler/16, Interrupt frei
TCCR1A = 0b10000010; // Timer1 Ausgang OC1A, Fast PWM
TCCR1B = 0b00011001; // Keine Vorteilung Timer mit CPU-CLK
ICR1 = 19999; // Signal alle 20 ms
SteuerungDdr |= (1<<PB1); // Servoausgang an
TIMSK &= ~((1<<TICIE1) | (1<<OCIE1A)); // T1 Interrupts sperren
bmode=servo;
sei(); // Interrups frei geben
break;
case servo:
if (taste) bmode = spannungsmessung;
break;
case spannungsmessung:
ausgabeArt = vcc;
if (taste) bmode = strommessung;
break;
case strommessung:
ausgabeArt = servostrom;
if (taste) bmode = initimpulsmessung;
break;
case initimpulsmessung:
cli(); // Interrups sperren
ausgabeArt = ms;
ADCSRA = 0; // ADWandler Stopp
SteuerungDdr &= ~(1<<PB1); // Servoausgang aus
TCCR1A = 0b00000000; // Timer1 nicht auf Ausgang, CTC
TCCR1B = 0b01001001; // Input Capture pos Flanke, Timer mit CPU-CLK
OCR1A = 25000; // Nach 25ms Timer wieder auf 0
TIMSK |= (1<<TICIE1) | (1<<OCIE1A); // Interrupts frei geben
bmode=impulsmessung;
sei(); // Interrups frei geben
break;
case impulsmessung:
if (taste) bmode = initservo;
break;
}
taste = 0;
if (350>adcWert[vcc]) peepdown(); // Unterspannungswarnung
}
return 0;
}
Assembler: Nur zur Anschauung für meine Schüler, kann wesentlich weniger
; *** Servotester V 0.5 (c) Oliver Mezger 23.2.2010 ; CC-Lizenz: creativecommons.org/licenses/by-sa/3.0/de/ ; Assemblercode ist nicht vollständig und nur beispielhaft .include "m8def.inc" ;*** Zuordnungen zu den Ports *** .equ SegmenteIn = PIND .equ SegmenteOut = PORTD .equ SegmenteDdr = DDRD .equ SteuerungIn = PINB .equ SteuerungOut = PORTB .equ SteuerungDdr = DDRB .equ StelleIn = PINC .equ StelleOut = PORTC .equ StelleDdr = DDRC .def null = R23 ;Register mit 0 .def tmp = R16 ;tmp-Register zum Arbeiten .def tmp2 = R17 ;als lokale, temporaere Variablen .def stelle = R18 ;Stelle die dargestellt wird (neg. Logik 110,101,011) .def itmp = R21 ;tmp-Register fuer Interrupt Service Routine (ISR) ;*** Interrupttabelle bei Mega8 sind die Adressen im Wort-Abstand daher rjmp rjmp reset ;Einsprung nach Reset .org OVF0addr ;Einsprung nach Timer 0 Ueberlauf rjmp isrAnzeige ;fuer Anzeigen ISR .org ADCCaddr ;Einsprung AD-Wandler rjmp isrAdc ;fuer ADC ISR .org $2a ;Ende der Interruptvektoren nun Code isrAnzeige: ;ISR wird mit Systemtakt/8/256 mal aufgerufen (488 Hz) in itmp,SREG ;SREG retten push itmp cpi stelle,0b111 ;wenn nicht Stelle 4 brne nstelle ;dann naechste Stelle ldi stelle,0b110 ;Stelle1 initialisieren ldi XH, high(anzeige) ;Datenzeiger initialisieren ldi XL, low(anzeige) nstelle: ld itmp,x+ ldi ZH, high(bcd_7*2) ;Datenzeiger initialisieren ldi ZL, low(bcd_7*2) add ZL,itmp ;Addiere Position adc ZH,null lpm itmp, z ;lade Daten aus Flash cpi stelle,0b101 ;wenn nicht stelle 1 brne keindp ;dann kein Dezimalpunkt andi itmp,0b01111111 ;sonst setze Dezimalpunkt keindp: out SegmenteOut,itmp ;ausgeben Spalte in itmp, StelleOut ;lade StellenPort andi itmp,0b11111000 ;maskiere Stellenbits or itmp,stelle ;fuege Stellenmuster hinzu out StelleOut,itmp ;Stelle einschalten lsl stelle ;naechste Stelle kein rol verwenden! inc stelle andi stelle,0b111 ;die rausgeschobene 1 wegmaskieren pop itmp ;SREG wiederherstellen out SREG,itmp reti ;return from Interrupt isrAdc: ;ISR fuer AnalogDigital-Wandler reti reset: ; Initialisierung und Hauptprogramm ldi tmp, high(RAMEND) ;Stackpointer initialisieren out SPH, tmp ldi tmp, low(RAMEND) out SPL, tmp ldi tmp,2 ;Timer 0 mit Systemtakt/8 betreiben out TCCR0,tmp in tmp,TIMSK ;Timerinterrupt einschalten ori tmp, 1 << TOIE0 out TIMSK,tmp ldi null,0 ldi XH, high(anzeige) ;Datenzeiger initialisieren ldi XL, low(anzeige) st X+,null ;Feld mit 0 initialisieren st X+,null st X+,null ldi tmp,0xff out SegmenteDdr,tmp ldi tmp,7 out StelleDdr,tmp ldi stelle,6 sei ;globale Interruptfreigabe loop: rjmp loop bcd_7: .db 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90 .dseg; Datenbereich anzeige: .byte 3; Feld mit 3 Byte