//#define EINSTELLBAR // Erzeugt eine Version, die Tasten und LCD ansteuert, um Parameter zu veraendern //#define MESSBAR // Erzeugt eine Version, die die Tastenbits als Ausgang fuer Zeitmessungen verwendet #if defined(EINSTELLBAR) && defined(MESSBAR) #error ERROR: Entweder EINSTELLBAR oder MESSBAR benutzen. Nicht beide gleichzeitig! #endif #ifdef MESSBAR #warning INFO: MESSBAR eingestellt #endif #ifdef EINSTELLBAR #warning INFO: EINSTELLBAR eingestellt #endif #include "hardware.h" #include #include /* Hier werden die grundlegenden Systemeinstellungen gemacht */ /* Systemfrequenz in Hz */ #define SYSFREQ 16000000l /* Vorteiler des Timers */ #define TIMEDIVISOR 2l #define TIMERRELOAD (0x400) #define INTFREQUENZ (SYSFREQ/TIMEDIVISOR/TIMERRELOAD) #if TIMERRELOAD > 65535 #error Gewuenschte Timerfrequenz zu niedrig! #endif /****************************************************************************/ /* Einstellungen fuer Systemtakt */ #if SYSFREQ == 16000000l /* 16MHz Takt */ #define CALDCO_XMHZ CALDCO_16MHZ #define CALBC1_XMHZ CALBC1_16MHZ #elif SYSFREQ == 12000000l /* 12MHz Takt */ #define CALDCO_XMHZ CALDCO_12MHZ #define CALBC1_XMHZ CALBC1_12MHZ #elif SYSFREQ == 8000000l /* 8MHz Takt */ #define CALDCO_XMHZ CALDCO_8MHZ #define CALBC1_XMHZ CALBC1_8MHZ #elif SYSFREQ == 1000000l /* 1MHz Takt */ #define CALDCO_XMHZ CALDCO_1MHZ #define CALBC1_XMHZ CALBC1_1MHZ #else #error Keine gueltige Taktfrequenz gesetzt! (16, 12, 8 oder 1 MHz) #endif /****************************************************************************/ /* Einstellungen fuer Timer */ #if TIMEDIVISOR == 1l /* Teiler durch 1 */ #define ID_DIVX ID_DIV1 #elif TIMEDIVISOR == 2l /* Teiler durch 2 */ #define ID_DIVX ID_DIV2 #elif TIMEDIVISOR == 4l /* Teiler durch 4 */ #define ID_DIVX ID_DIV4 #elif TIMEDIVISOR == 8l /* Teiler durch 8 */ #define ID_DIVX ID_DIV8 #else #error Kein gueltiger Timer-Teiler gesetzt! (1, 2, 4 oder 8) #endif /****************************************************************************/ #define CONTROLWORD 1 #define DATAWORD 0 #define LCD_CLEAR_DISPLAY 0x01 #define LCD_RETURN_HOME 0x02 #define LCD_SHIFT_LEFT 0x07 #define LCD_SHIFT_RIGHT 0x05 #define LCD_CURSOR_LEFT 0x04 #define LCD_CURSOR_RIGHT 0x06 #define LCD_DISPLAY_DISP_ON 0x0C #define LCD_DISPLAY_DISP_CURS_ON 0x0E #define LCD_DISPLAY_DISP_BLINK_ON 0x0D #define LCD_DISPLAY_ALL_ON 0x0F #define LCD_DISPLAY_OFF 0x08 #define LCD_FUNCTION_SET_0 0x30 #define LCD_FUNCTION_SET_1 0x31 #define LCD_FUNCTION_SET_2 0x32 #define LCD_ZEILE2 40 #define LCD_SET_CURSOR(x) (0x80 + (x)) #define LCD_CONTRAST(x) (0x70 + (x)) #define ADMITTEL 32 #define TIME_2MS ((2L*INTFREQUENZ)/1000) #define TIME_10MS ((10L*INTFREQUENZ)/1000) #define TIME_100MS ((100L*INTFREQUENZ)/1000) #define TIME_1S (INTFREQUENZ) #define TIME_256US 2 #define SCHATTENSPANNE 200 #define SCHATTENGRENZE 20 volatile unsigned long countmax, countmin; volatile unsigned int timeslot; volatile unsigned int delaytimer; volatile signed int schatten, schatten_alt; volatile signed int timer_reload, to; volatile signed int i_abweichung; volatile unsigned char regelung_an; signed int schattenmin; signed int schattenmax; signed int schattenmitte; char buf[10]; volatile signed int p_anteil, i_anteil, d_anteil; const char * n[] = {"P ", "I ", "D ", "Min ", "Max "}; // Moegliche Parameter die angezeigt werden koennen /* Warteroutine die in Verbindung mit dem Interrupt wartet */ void delay(unsigned int d) { delaytimer = d; while (delaytimer) { } } #ifdef EINSTELLBAR /* Diese Routine gibt ein Byte ans LCD aus. Es wird unterschieden zwischen Daten- und Controlbytes */ /* Die SPI-Schnittstelle wird per Bitbanging in SW gemacht, dadurch ich man vom HW-Port unabhaengig. */ void lcd_out(unsigned char d, unsigned char control) { int i; if (control==CONTROLWORD) { RS_LOW; } else { RS_HIGH; } CS_LOW; for (i=0; i<8; i++) { if ((d & (unsigned char)0x80)==0) { SI_LOW; } else { SI_HIGH; } SCL_HIGH; SCL_LOW; d=d<<1; } CS_HIGH; if (((d & 0xFC) == 0) && (control == CONTROLWORD)) { delay(TIME_2MS); } else { delay(TIME_256US); } } /* Mittels lcd_out wird ein String aufs LCD ausgegeben */ void lcd_string(char * cp) { while (*cp != 0) { lcd_out(*cp, DATAWORD); cp++; } } /* Mittels lcd_out wird eine Zahl aufs LCD ausgegeben */ void lcd_dezimal(unsigned int zahl) { itoa(zahl, buf, 10); lcd_string(buf); } /* Je nach index wird ein Parameter auf dem LCD angezeigt. */ void lcd_var(unsigned char index) { lcd_out(LCD_CLEAR_DISPLAY, CONTROLWORD); // Links in der Zeile anfangen lcd_string((char*)n[index]); // Den entsprechenden String ausgeben switch (index) { // Anhand des Index den Wert der Variable anzeigen case 0: lcd_dezimal(p_anteil); break; case 1: lcd_dezimal(i_anteil); break; case 2: lcd_dezimal(d_anteil); break; case 3: lcd_dezimal(schattenmin); break; case 4: lcd_dezimal(schattenmax); break; } } #endif /* Der Interrupt wird bei jedem Durchlauf der PWM, also mit ca. 7800Hz aufgerufen. Der Durchlauf einer kompletten Mess- und Regelaufgabe dauert aber laenger als die 128us. Daher wird die Messung und die Regelung jeweils alternierend durchgefuehrt. Am Anfang gibt es noch einen gemeinsamen Teil fuer Timer. */ interrupt (TIMERA0_VECTOR) Timer_A0(void) { int z; #ifdef MESSBAR DIAG1_LOW; #endif if (delaytimer) { // Timer runterzaehlen delaytimer--; } timeslot++; /* Nur bei jedem 2-ten Durchlauf des Interrupts wird geregelt */ if (timeslot >= 2) { #ifdef MESSBAR DIAG2_LOW; #endif /* Hier ist der Teil mit der Regelung */ timeslot = 0; /* Teiler wieder bei 0 anfangen lassen */ i_abweichung = i_abweichung + schatten/8; /* Fuer die Integration wird die Abweichung abgeschwaecht. Ansonsten haette ich fuer Werte von i_anteil ueber ca. 65 long Werte nehmen muessen, was aber durch die zustaetzlichen Bibliotheksroutinen viel ROM braucht. Mit jedem Interrupt wird i_abweichung um die momentane Abweichung zur Sollposition erweitert. */ /* Der Integratorwert wird nach oben und unten so begrenzt, dass er alleine hoechstens fuer einen Maximalstellwert verantwortlich sein kann. Da in der Regelung durch i_anteil geteilt wird, wird der Grenzwert damit multipliziert.*/ if (i_abweichung > i_anteil*(TIMERRELOAD/2)) { i_abweichung = i_anteil*(TIMERRELOAD /2); } if (i_abweichung < -i_anteil*(TIMERRELOAD /2)) { i_abweichung = -i_anteil*(TIMERRELOAD /2); } /* Hier ist die eigentliche Regelung: Die vorzeichenbehafteten Regelwerte werden zum Offset TIMERRELOAD/2 addiert, damit der PWM-Wert im Normalfall bei 50% liegt, also in beide Richtungen maximal aussteuerbar ist. Addieren heisst hier Subtrahieren, da groessere Reglerwerte kleinere PWM-Werte erfordern. Mit anderen Worten: Mehr Schatten braucht weniger PWM-Verhaeltnis und damit weniger Magnetkraft. i_abweichung/i_anteil ist der I-Anteil, der recht klein ausfaellt. Daher wird die kumulierte Abweichung durch i_anteil geteilt. Ansonsten waere die Regelung viel zu unruhig. Dieser Parameter sorg dafuer, dass die Kugel ueber laengere Zeit an der exakt gewuenschten Position haengt. (p_anteil* schatten)/10 ist der P-Anteil. Abhaengig vom Schatten wird direkt ein Korrekturwert erzeugt, der sofort eine Reaktion des Magneten ergibt. d_anteil * (schatten - schatten_alt) ist der D-Anteil. Wenn die Kugel absolut still stehen wuerde, waere die Differenz in der Klammer 0 und der Parameter haette keine Wirkung. Jede Aenderung wirkt sich aber durch den relativ grossen Wert von d_anteil stark auf das PWM-Verhaeltnis aus. */ timer_reload = TIMERRELOAD/2 - i_abweichung/i_anteil - (p_anteil* schatten)/10 - d_anteil * (schatten - schatten_alt); /* Die ermittelten PWM-Werte auf 0 bis 100% begrenzen. */ if (timer_reload > TIMERRELOAD) { timer_reload = TIMERRELOAD; } if (timer_reload < 1) { timer_reload = 0; } /* Wenn die Regelung laeuft, wird der PWM-Wert aktualisiert */ if (regelung_an) { TACCR1 = timer_reload; } else { /* Bei ausgeschalteter Regelung ist die PWM auf 0% und der Integrator wird auf Maximalwert eingestellt, damit das Einhaengen der Kugel sanft erfolgt. */ TACCR1 = 0; i_abweichung = +i_anteil*(TIMERRELOAD/2); } /* Fuer D-Regler wird sich der jetzige Schattenwert fuer den naechsten Durchlauf gemerkt */ schatten_alt = schatten; /* Waehrend des Betriebs wird ueberprueft ob die gemessenen Schattenwerte einige Zeit am Rand der Messwerte liegen. Dann ist die Kugel entweder oben gegen den Magneten gezogen worden, oder sie ist nicht mehr da. In beiden Faellen wird die Reglung und der Magnet ausgeschaltet und man wartet bis die Schattenwerte wieder im Regelbereich liegen. */ if (schattenmax - (schatten + schattenmitte) < SCHATTENGRENZE) { /* Bei starker Abdeckung, Kugel oben */ if (countmax) { countmax--; } else { regelung_an = 0; } } else { if((schatten+schattenmitte) - schattenmin < SCHATTENGRENZE) { /* Bei keinem Schatten, Kugel weg */ if (countmin) { countmin--; } else { regelung_an = 0; } } else { countmax = TIME_10MS; countmin = TIME_100MS; regelung_an = 1; } } #ifdef MESSBAR DIAG2_HIGH; #endif } else { /* Hier ist der Teil mit der Messung. */ /* Es hat sich gezeigt, dass ein ruhiger Messwert fuer den Schatten erheblichen Einfluss auf die Qualitaet der Regelung hat. Daher wird mehrfach gemessen und dann gemittelt. */ schatten = 0; /* in schatten wird der gemittelte Helligkeitswert gespeichert. Vor dem Messen wird er auf 0 gesetzt. */ for (z = 0; z < ADMITTEL; z++) { /* Mitteln ueber x Werte */ ADC10CTL0 = ADC10CTL0 | ADC10SC; while (ADC10CTL1 & ADC10BUSY) { } schatten += ADC10MEM; /* AD-Werte aufsummieren */ } schatten = schatten / ADMITTEL - schattenmitte; /* Und durch die Anzahl der Summenterme teilen. Durch Subtrahieren von schattenmitte wird ein vorzeichenbehafteter Wert erzeugt. */ } #ifdef MESSBAR DIAG1_HIGH; #endif } /********************************************************************/ int main(void) { #ifdef EINSTELLBAR unsigned long lcdcounter; unsigned char lcdindex; #endif unsigned int schatten_init; unsigned char z; WDTCTL = WDTCTL_INIT; /*Init watchdog timer */ P1OUT = P1OUT_INIT; /* Init output data of port1*/ P2OUT = P2OUT_INIT; /* Init output data of port2*/ P1SEL = P1SEL_INIT; /*Select port or module -function on port1 */ P2SEL = P2SEL_INIT; /*Select port or module -function on port2 */ P1DIR = P1DIR_INIT; /* Init port direction register of port1*/ P2DIR = P2DIR_INIT; /*Init port direction register of port2*/ P1REN = P1REN_INIT; P1IES = P1IES_INIT; /*init port interrupts*/ P2IES = P2IES_INIT; P1IE = P1IE_INIT; P2IE = P2IE_INIT; DCOCTL = CALDCO_XMHZ; BCSCTL1 = XT2OFF | CALBC1_XMHZ; TACTL = TASSEL_SMCLK | ID_DIVX | MC_UPTO_CCR0; TAR = 0; TACCR0 = TIMERRELOAD; TACCR1 = TIMERRELOAD; TACCTL0 = CM_DISABLE | CCIS_GND | SCS_ASYNC | CAP_COMP | OUTMOD_OUT | OUT_LOW | CCIE; TACCTL1 = CM_DISABLE | CCIS_GND | SCS_ASYNC | CAP_COMP | OUTMOD_SET_RESET | OUT_LOW ; timeslot = 0; /* Der AD-Wandler wird auf interne Referenz von 2,5V eingestellt, Kanal 0 und interne Takterzeugung */ ADC10CTL0 = SREF_1 | ADC10SHT_DIV4 | REF2_5V | REFON | ADC10ON; ADC10CTL1 = INCH_0 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0 | CONSEQ_0; ADC10AE = 0x01; ADC10CTL0 = ADC10CTL0 | ENC; /* Vor dem Freigeben des Interrupts wird erst mal die Regleung ausgeschaltet */ regelung_an = 0; eint(); #ifdef EINSTELLBAR /* Status auf dem Display asgeben */ delay(TIME_100MS); SCL_LOW; delay(TIME_100MS); lcd_out(LCD_FUNCTION_SET_1, CONTROLWORD); lcd_out(0x14, CONTROLWORD); lcd_out(LCD_CONTRAST(5), CONTROLWORD); lcd_out(0x55, CONTROLWORD); lcd_out(0x6d, CONTROLWORD); delay(TIME_100MS); lcd_out(LCD_FUNCTION_SET_0 + 0x01, CONTROLWORD); lcd_out(LCD_DISPLAY_DISP_ON, CONTROLWORD); lcd_out(LCD_CLEAR_DISPLAY, CONTROLWORD); lcd_out(LCD_CURSOR_RIGHT, CONTROLWORD); lcd_string("MinMax"); // zunaechst werden die Extremwerte fuer die Fotodiode ermittelt #endif /* ADC10CTL0 = ADC10CTL0 | ADC10SC; while (ADC10CTL1 & ADC10BUSY) { } */ schattenmin = 0x7FFF; schattenmax = 0; i_anteil = 60; p_anteil = 10; d_anteil = 230; /* Zu Beginn wird der maximale Aussteuerbereich der Fotodiode ermittelt. Dazu wird der Wert der Fotodiode fortlaufen gemessen und die Extrema in schattenmax und schattenmin gespeichert. Nachdem die Werte erstmals um SCHATTENSPANNE auseinandergelegen haben, wird noch fuer eine Sekunde weitergemessen um dem Anwender Zeit zu geben die Kalibrationsbewegung fertig zu machen. Diese besteht darin nach dem Einschalten einmalig den Strahlengang komplett zu unterbrechen. */ delaytimer = TIME_1S; while (delaytimer) { schatten_init = 0; for (z = 0; z < ADMITTEL; z++) { /* Mitteln ueber x Werte, wie bei normaler Messung auch */ dint(); // Interrupts sollen die Messung nicht erneut starten ADC10CTL0 = ADC10CTL0 | ADC10SC; while (ADC10CTL1 & ADC10BUSY) { } schatten_init += ADC10MEM; /* AD-Werte aufsummieren */ eint(); } schatten_init = schatten_init /ADMITTEL; /* Und durch die Anzahl der Summenterme teilen */ if (schatten_init < schattenmin) { /* schattenmin bezieht sich auf die Spannung. Das bedeutet aber maximale Helligkeit, Kugel unten */ schattenmin = schatten_init; } if (schatten_init > schattenmax) { /* schattenmax bezieht sich auf die Spannung. Das bedeutet aber minimale Helligkeit, Kugel oben */ schattenmax = schatten_init; } /* Wenn der Unterschied noch nicht gross genug ist, wird weiter auf die Initialisierung gewartet */ if ((schattenmax - schattenmin) < SCHATTENSPANNE) { delaytimer = TIME_1S; } } schattenmitte = (schattenmax + schattenmin) / 2; countmax = 0; countmin = 0; regelung_an = 1; #ifdef EINSTELLBAR /* Jetzt wird angezeigt, dass man im normalen Schwebemodus ist. */ lcd_out(LCD_CLEAR_DISPLAY, CONTROLWORD); lcd_string("Schwebe"); lcdcounter = 0; lcdindex = 2; #endif /* In der folgenden Endlosschleife werden hoechstens noch Tasten abgefragt. Die Regelung erfolgt komplett im Interrupt */ while (1) { #ifdef EINSTELLBAR /* Im folgenden wird ganz primitv, ohne Entprellung, auf drei Tasten reagiert. Taste 1 schaltet den anzuzeigenden Parameter zyklisch weiter. Taste 2 oder 3 reduziert, bzw. erhoeht den jeweiligen Parameter, falls das Sinn macht. Die Inkremente sind den jeweiligen Werten angepasst. */ if TASTE1 { lcdindex++; if (lcdindex > 4) { lcdindex = 0; } lcd_var(lcdindex); while TASTE1; } if TASTE2 { switch (lcdindex) { case 0: p_anteil--; break; case 1: i_anteil-=5; break; case 2: d_anteil-=10; break; default:; } lcd_var(lcdindex); while TASTE2; } if TASTE3 { switch (lcdindex) { case 0: p_anteil++; break; case 1: i_anteil+=5; break; case 2: d_anteil+=10; break; default:; } lcd_var(lcdindex); while TASTE3; } #endif } }