Zurück (C) Christof Ermer

Gästebuch


02.02.2011



Gefunden im Netz, gute Lösung: http://www.pololu.com/    http://www.pololu.com/catalog/product/207
Timerpräzise Multi PWM Erzeugung via Software und "einem" Hardwaretimer
am Beispiel der AVR µController  ATMega16 oder AtMega8    
Prinzip:
* PWM = Pulsweiten  Modulation.


Die Aufgabe.  Mehrere präzise,  jitterarme (wackelarme oder stabile) PWMs für Servos:
Eine präzise Zeitbasis  ist erforderlich. Und das ganze eben sehr sehr schnell.

Realisation:
1.)  16Mhz  ohne Vorteilung in einen  freien „8 Bit Timer“ des µControllers schieben, und damit Overflow Interrupts auslösen...
        merke: 8 Bit =  2^8 =256.

        Der Zähler läuft einfach von 0 bis 255, und wenn er wieder 0 wird, löst er einen Overflow Interrupt aus.
        Simpel und präzise !

Das hat eine Frequenzteilung zu Folge.
 16 MHZ / 256  =  62500     Interrupts / Sekunde !!

Software:
#define TCCR2_CLK_0               0x01
#define TIMSK_TOIE0_FLAG    0x01
    TCCR0 = TCCR2_CLK_0;                        CLOCK  ON. Quarz Clock direkt ohne Vorteilung an 8-Bit Timer 0 anlegen
    TIMSK  |=  TIMSK_TOIE0_FLAG;
        // Interrupt EIN

Pro Interrupt stehen also NUR 1/62500 = 0,000016 Sekunden = 16 ^-6  = 1Sekunden Zeit für die Verarbeitung zur Verfügung.
Das ist nicht viel Zeit. Also geht (diesmal) Zeitoptimierung vor Eleganz und Schönheit des Programmierstils.

2.)   
Was passiert im Interrupt ?
    a)  Sagen wir mal am Zeitpunkt  0  gehen alle PWM Kanäle  ohne Bedingung von
0 auf den Wert 1 ( steigende Flanke )
        ab jetzt tickt die Uhr !
    b) Bei JEDEM Interrupt wird ein  schneller keiner 8BitZähler (0..255)  um
eins hochgezählt
       
u8T0_Runs++;
          jetzt weiß man also immer , wo der Timer gerade ist.!
    c) gebraucht wird nun ein  Werte-speicher( Array etc ) der die  PWM ON-Zeit  für jeden PWM-Kanal einzeln Speichert.
        Jeder PWM Kanal hat also sein eigene Variable, die den Abschaltzeitpunkt ( fallende Flanke ) definiert. !!  

        #define PWM_CAHNNEL 7
        uint8_t gau8MultiPWM[ PWM_CHANNELS ]; // 7 PWMs.   gau8= global array unsigned  8bit       

Die Schrittweite des PWM Signal hat also NUR 255 Stufungen.
  

    d)  der Interrupt-Zähler
u8T0_Runs wird nun mit der PWM-Variable des zuständigen PWM-Kanals gau8MultiPWM[ PWMNummer ] verglichen.
          Bei Gleichstand ist der SollZeitpunkt erreicht und die zugehörige PWM Signalleitung (Pin) wird schnell auf  0 geschaltet

#define MULTI_SERVO_PORT        PORTB
if( gau8MultiPWM[0] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xFD};
if( gau8MultiPWM[1] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xFB;};
if( gau8MultiPWM[2] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xF7;};
usw...

Das zugehörige Bit wird gelöscht. Und zwar ohne die anderen Bits zu beeinflussen..
Das geht am besten mit  VerUNDen mit 0 des Portwertes  an der Position, wo das zu löschende Bit 0 ist.
0xFD = 0b11111101
0xFB = 0b11111011
0xF7 = 0b11110111
usw....
FERTIG !!!
Die Aufgabe.  Mehrere präzise,  jitterarme ( stabile) PWMs für Servos:
Eine präzise Zeitbasis  ist erforderlich. Und das ganze eben sehr sehr schnell.
Realisation:
1.)  16Mhz  ohne Vorteilung in einen  freien 8 Bit Timer des µControllers schieben, und damit Overflow Interrupts auslösen...
        merke: 8 Bit =  2^8 =256.

        Der Zähler läuft einfach von 0 bis 255, und wenn er wieder 0 wird, löst er einen Overflow Interrupt aus.
        Simpel und präzise !

Das hat eine Frequenzteilung zu Folge.
 16 MHZ / 256  =  62500     Interrupts / Sekunde !!

Software:
#define TCCR2_CLK_0               0x01
#define TIMSK_TOIE0_FLAG    0x01
    TCCR0 = TCCR2_CLK_0;                        CLOCK  ON.
Quarz_Clock Direkt ohne Vorteilung an 8Bit Timer 0 anlegen
    TIMSK  |=  TIMSK_TOIE0_FLAG;
        // Interrupt EIN

Pro Interrupt stehen also NUR 1/62500 = 0,000016 Sekunden = 16 ^-6  = 1Sekunden Zeit für die Verarbeitung zur Verfügung.
Das ist nicht viel Zeit. Also geht (diesmal) Zeitoptimierung vor Eleganz und Schönheit des Programmierstils.

2.)   
Was passiert im Interrupt:

Daraus ergibt sich als Konsequenz  für die PWM Frequenz eine weitere Teilung durch 256 der Quarz-Frequenz von z.B.16Mhz. ....Warum?..

Da der Interrupt-Zähler u8T0_Runs ebenfalls von 0 biss 255 zählt, und eben erst bei jeden NULL-Wert das PWM Signal wieder eingeschaltet wird, ergibt sich eine PWM Ausgabe-Frequenz von
16 MHz(Quarz) / 256  (Timeroverflow) / 256 ( Interrupt Zähler)  = 244,140625 Hz... PWM Frequenz..
Damit ist soweit alles OK.. Es funktioniert..
Prima. Ende soweit...  oder?



Schaltung der MultiPWM  Software. Mit einem Trick wird eine konstant eSpannung bei hohem Strom erreicht. Zudem können alle Servos definiert eingeschaltet werden.-

Die Eagle Dateien zum Download: 7Servo_mitUSw.sch     7Servo_mitUSw.brd




Nur
244 Hz sind z.B. für Servo Steuersignale zu schnell..... Die wollen 50..60Hz ...
kein Problem,,,,!!
jetzt kann man selbst noch erweitern, ergänzen, verändern, anpassen etc..

~244 / 5 sind ja etwa 50 Hz
Am besten verlängert man die OFFZEIT  einfach durch Pausieren um den entsprechenden Faktor/Betrag..
Also wird die Sequenz des Interrupt Vergleichs nur bei jedem Fünften Timer-Overflows wiederholt.

ein zweiter Zähler ist erforderlich..
static uint8_t u8PWMPause
und der wird  in der Software etwa so behandelt:

#define PWM_PAUSEVERLAENGUNGSFAKTOR    5
u8PWMPause %= PWM_PAUSEVERLAENGUNGSFAKTOR  // Dies ist der Modulo Operator

u8PWMPause wird also mit  Modulo Fünf behandel.
Wenn also u8PWMPause wieder 0 wird, dann wird die PWM  wieder eingeschaltet und unterliegt wieder dem Wertvergleich zum Ausschalten.
 
Mann kann jetzt also auch die Häufigkeit der PWM ON Signale erhöhen, und so die Leistung eines Servos erhöhen...
Dazu kann per Software eben diese Pausen-Zeit von
5 eben auch auf 4 oder 3 etc verringert werden...
Es erhöht sich also die PWM Frequenz, jedoch nicht die  PWM ON Zeit.

es folgt noch die HEADER  und die .c Datei




Im folgendem Beispiel ist die PWM Steuerung noch erweitert zur langsamen Sollwertannäherung für Servos
und auch zur Leistungserhöhung, in dem die Pausenzeit verkürzt wird
.
Siehe dazu die Text Mnemonic Commandos  die mit MCA_XX_CMD  syntaktisch geschrieben sind


MulitPWM.h


#ifndef MULTI_PWM_HEADER    //1. Headereinbindung
#define MULTI_PWM_HEADER

#include <inttypes.h>

//" Grosse CPU % Auslastung! ~80%
#define MULTIPWM_USED // Interrupt getrieben.

//Diese Werte selbst definieren nach Bedarf
//BSP Chanel 0..6
#define PREDEF_PWM_0 75
#define PREDEF_PWM_1 75
#define PREDEF_PWM_2 75
#define PREDEF_PWM_3 75
#define PREDEF_PWM_4 75
#define PREDEF_PWM_5 75
#define PREDEF_PWM_6 75

//*******************************
//MULTI PWM
//*******************************


#define PWM_CHANNELS                7    //sieben PWMS
#define PWM_PAUSEVERLAENGUNGSFAKTOR    5
#define PWRPIN_OFFSET            1    //Offset der PWMPins vom PWR_ON PIN
#define MULTI_SERVO_PORT        PORTB
#define MULTI_SERVO_DIRREG        DDRB
#define MULTI_SERVO_DIRREG_VAL    0xFF

#define MULTI_SERVO_DIRREG_Init() (MULTI_SERVO_DIRREG = MULTI_SERVO_DIRREG_VAL)
#define MULTI_SERVO_PWR        0x01    //BUZ11
#define MULTI_SERVO_1        0x02
#define MULTI_SERVO_2        0x04
#define MULTI_SERVO_3        0x08
#define MULTI_SERVO_4        0x10
#define MULTI_SERVO_5        0x20
#define MULTI_SERVO_6        0x40
#define MULTI_SERVO_7        0x80

#define MULTI_SERVOS_MASK_1        0xFE
#define MULTI_SERVOS_CLRMASK    0x01 // wegen PWR

/*
1mS=1000Hz * 180
*/
#define  PWM_PERIODE_MS                 0.005    //20ms = 50HZ 5mS=200Hz
#define  PWM_LINKS_ANSCHLAG_MS        1
#define  PWM_RECHTS_SANSCHLAG_MS    2

#define MULTI_SERVO_PWR_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_PWR)
#define MULTI_SERVO_PWR_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_PWR)

#define MULTI_SERVO_1_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_1)
#define MULTI_SERVO_1_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_1)

#define MULTI_SERVO_2_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_2)
#define MULTI_SERVO_2_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_2)

#define MULTI_SERVO_3_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_3)
#define MULTI_SERVO_3_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_3)

#define MULTI_SERVO_41_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_4)
#define MULTI_SERVO_4_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_4)

#define MULTI_SERVO_5_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_5)
#define MULTI_SERVO_5_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_5)

#define MULTI_SERVO_6_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_6)
#define MULTI_SERVO_6_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_6)

#define MULTI_SERVO_7_ON()  (MULTI_SERVO_PORT |= MULTI_SERVO_7)
#define MULTI_SERVO_7_OFF()  (MULTI_SERVO_PORT &= ~MULTI_SERVO_7)

//Diese Funktion VOR!! Mainloop einfügen
extern void ToggleTEST_BIT(uint8_t ucCnt);

extern uint8_t gu8PausenFaktor;

extern void Init_MultiPWM_StartValues(uint32_t u32Time);
extern void StartTimer0(void);

//Diese Funktion IN!! Mainloop einfügen
extern void SlowMotion_PWM_Check( uint32_t u32Time, uint16_t u16PWMDelay);

//Extern Globals
extern uint8_t gau8MultiPWM[ PWM_CHANNELS ]; // 7 PWMs
extern uint8_t gau8MultiPWM_Compare[ PWM_CHANNELS ]; // 7 PWMs

//gu16PWM_DT = PWM_STEIGUNG ;
#define PWM_STEIGUNG  10; // STEIGUNG = Fahrtgeschwindigkeit des PWM, 244 = 1Sekunde pro Schritt

#endif //MULTI_PWM_HEADER



MultiPWM.c

/*
Insert in MainPrgramm bevor Mainloop:

#ifdef MULTIPWM_USED
Init_MultiPWM_StartValues();
StartTimer0();
MULTI_SERVO_PWR_ON();
#endif
*/


#include <avr/io.h>
#include <avr\wdt.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "main.h"
#include "MultiPwm.h"

#ifdef MULTIPWM_USED
//GLOBALS
uint8_t gau8MultiPWM[ PWM_CHANNELS ]; // 7 PWMs
uint8_t gau8MultiPWM_Compare[ PWM_CHANNELS ]; // 7 PWMs
static uint32_t gu32_PWM_MotionTime;
uint8_t gu8PausenFaktor =  PWM_PAUSEVERLAENGUNGSFAKTOR;

void StartTimer0(void);    //0.1 Seconds  interrupt
void SlowMotion_PWM_Check( uint32_t u32Time, uint16_t u16PWMDelay);
#endif

// In Menmonic Orderliste ist folgendes hilfreich
// aber das je nach Geschmack des Programmierers
/*
#ifdef MULTIPWM_USED
//u8aMultiPWM[ (((uint8_t)gsCmd.fCmdVal_1)  % PWM_CHANNELS_MODULO) ] = (uint8_t)gsCmd.fCmdVal_2;
const char MCA_MPWM_CMD[]PROGMEM = "MPWM";  //MPWM, CHANNEL 0..6, 0..255
const char MCA_MPON_CMD[]PROGMEM = "MPON";  //MPWM, POWER ON
const char MCA_MPOFF_CMD[]PROGMEM = "MPOFF";  //MPWM, POWER OFF
#endif
#ifdef MULTIPWM_USED
if( !strcasecmp_P( gcaStr, MCA_MPWM_CMD)  )
    {   
    gu8aMultiPWM[ (((uint8_t)gsCmd.fCmdVal_1)  % PWM_CHANNELS_MODULO) ] = (uint8_t)gsCmd.fCmdVal_2;
    return;
    };
   
   
if( !strcasecmp_P( gcaStr, MCA_MPON_CMD)  )
    {   
    MULTI_SERVO_PWR_ON();
    return;
    };
   
if( !strcasecmp_P( gcaStr, MCA_MPOFF_CMD)  )
    {   
    MULTI_SERVO_PWR_OFF();
    return;
    };
#endif   
*/


#ifdef MULTIPWM_USED
/*
^------|         ^-- PWM
|      |         |
|       |---------| 
0--------------->255                   
||||||||||||||||||  ---> Interrupt u8T0_Runs++
*/
// ****************************************************************
SIGNAL( TIMER0_OVF_vect ) //TIMER0_COMP_vect oder SIGNAL--INTERRUPT(SIG_OVERFLOW0) = mit SEI am Anfang
// *******************************************************************
{ // 62500 Hz  interrupt
/*
TCNT0=0 --> 16Mhz / 256 = 62500´Interrupts /Sekunde !! 
Alle 20mS = 50Hz wir ein neuer PWM AUF H-pegel gelegt.
Timer läuf ohne Vorteiler am Quarz
Davon wird wieder ein
*/
static uint8_t u8PWMPause;
static uint8_t u8T0_Runs;
//uint8_t u8PWMs;

// weil die Peridoe 4.096 MS beträgt, jeoch 20Ms=50Hz erforderlich..
// TRick: uu8T0_Runs

//ToggleTEST_BIT(1);

//Diese Construction, damit nur alle 5 Timersdurchläufe der PWM ausgegeben wird
if( !(u8T0_Runs++) ) // 5 * bei 20mS. WENN 0, Transportiert /Das passiert 244,140625/Sekunde
    { // BEI !0   
    ++u8PWMPause;
    //u8PWMPause %=PWM_PAUSEVERLAENGUNGSFAKTOR;
    if( !(u8PWMPause %= gu8PausenFaktor) )
        {
        MULTI_SERVO_PORT |= MULTI_SERVOS_MASK_1; //Alles ein
        }
    };

if(!u8PWMPause)    //WENN 00 Modulo 5
    {
//SO ist es das schnellste ausführungsvariante
    if( gau8MultiPWM[0] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xFD;};
    if( gau8MultiPWM[1] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xFB;};
    if( gau8MultiPWM[2] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xF7;};
    if( gau8MultiPWM[3] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xEF;};
    if( gau8MultiPWM[4] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xDF;};
    if( gau8MultiPWM[5] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0xBF;};
    if( gau8MultiPWM[6] == u8T0_Runs)    {MULTI_SERVO_PORT &= 0x7F;};
    };
   
//Spätes Interrupt freigeben, sonst pfuscht alles mögliche rein
sei();   
/*

    else
        {
        MULTI_SERVO_PORT &= MULTI_SERVOS_CLRMASK;
        };       
*/
   
/*
//Diese Form brauch Doppelt so lang, ist aber universel
//MCA_PWM_Clear_MASK[] = {0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7f};
//Remember , an BIT0 liegt der Transistor, der die Spannung auf Masse freischaltet.
    for(u8PWMs= 0; u8PWMs < PWM_CHANNELS; u8PWMs++)
        {
        if( gau8MultiPWM[u8PWMs] == u8T0_Runs )
            {
            MULTI_SERVO_PORT &= ~(1 << (u8PWMs + 1));  // Offset 1, weil 0 ist der Powertransistor
            };
        };

    };   
*/
};
#endif


#ifdef MULTIPWM_USED
void StartTimer0(void)
// *************************************
{
unsigned char sreg;
gu8PausenFaktor =  PWM_PAUSEVERLAENGUNGSFAKTOR;  // Sondereuinstellung... um den Ablauf zu garantieren
cli();
sreg = SREG; //loakaler Speicher
TIMSK &= ~(TIMSK_TOIE0_FLAG | TIMSK_OCIE0_FLAG);
OCR0 = TCCR0 = TCNT0 = 0;  //Timer MAX =
// CTC löst OCRx int aus
TCCR0 = TCCR2_CLK_0; // TCCR0_CLK_0; //
TIMSK |= TIMSK_TOIE0_FLAG;// TIMSK_OCIE0_FLAG ;
SREG = sreg;
sei();
};
#endif


#ifdef MULTIPWM_USED
// *************************************
void Init_MultiPWM_StartValues(uint32_t u32Time)
// *************************************
{
gu32_PWM_MotionTime = u32Time;

*gau8MultiPWM_Compare = *gau8MultiPWM = PREDEF_PWM_0; //gau8MultiPWM[0] = PREDEF_PWM_0;
gau8MultiPWM_Compare[1] = gau8MultiPWM[1] = PREDEF_PWM_1;
gau8MultiPWM_Compare[2] = gau8MultiPWM[2] = PREDEF_PWM_2;
gau8MultiPWM_Compare[3] = gau8MultiPWM[3] = PREDEF_PWM_3;
gau8MultiPWM_Compare[4] = gau8MultiPWM[4] = PREDEF_PWM_4;
gau8MultiPWM_Compare[5] = gau8MultiPWM[5] = PREDEF_PWM_5;
gau8MultiPWM_Compare[6] = gau8MultiPWM[6] = PREDEF_PWM_6;
MULTI_SERVO_DIRREG_Init();
};
#endif


#ifdef MULTIPWM_USED
// *************************************
void SlowMotion_PWM_Check( uint32_t u32Time, uint16_t u16PWMDelay)
// *************************************
{
if( (u32Time - gu32_PWM_MotionTime ) > u16PWMDelay )
    {
    gu32_PWM_MotionTime = u32Time;
   
    uint8_t u8NN;
    for(u8NN=0;u8NN  < PWM_CHANNELS; u8NN++)
        {
        if( gau8MultiPWM_Compare[u8NN] != gau8MultiPWM[u8NN] )
            {
//            ToggleTEST_BIT(1);
            ( gau8MultiPWM_Compare[u8NN] > gau8MultiPWM[u8NN] ? gau8MultiPWM[u8NN]++ : gau8MultiPWM[u8NN]-- );
            };   
        };

    };
};
#endif



in main.c

BEISPIELHAFT
gsCmd.fCmdVal_1 = PREDEF_PWM_0;
gsCmd.fCmdVal_2 = PREDEF_PWM_1;
gsCmd.fCmdVal_3 = PREDEF_PWM_2;
gsCmd.fCmdVal_4 = PREDEF_PWM_3;
gsCmd.fCmdVal_5 = PREDEF_PWM_4;
gsCmd.fCmdVal_6 = PREDEF_PWM_5;
gsCmd.fCmdVal_7 = PREDEF_PWM_6;

Init_MultiPWM_StartValues(gu32T2_Ticks);

MULTI_SERVO_PWR_ON();

//in  mainloop
while(1)  /* loop forever */
    {   
    sei();
     wdt_reset();   //WATCHDOG!
   
    SlowMotion_PWM_Check( gu32T2_Ticks, gu16PWM_DT ); // 100 ist motion time
   
#ifdef ADC_POTI_CHECK
    if( (gu16Last_ADC_VAL[0] - ADC_10Bit( 0 ) > ADC_POTI_CHECK_DETECTDIFF ) )
        {
        gu16Last_ADC_VAL[0] = ADC_10Bit( 0 );
        PrintiLongCR( gu16Last_ADC_VAL[0] );
        };
#endif

    u32MainCountsPerSecond++;   
    if( ((uint32_t)(gu32T2_Ticks - u32Now)) > PULS_PER_SEcONDTick_T2 ) //PULS_250MS_TICK_T2
        {
        u32Now = gu32T2_Ticks;
        PrintiLongCR( u32MainCountsPerSecond );
        u32MainCountsPerSecond = 0;       
        };