Diskussion:Soft-PWM

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Vielleicht wärs nicht schlecht auch noch drauf hinzuweise, dass man duch ein einfaches bit-reverse die Schaltfrequenz auf die Timer-Zykluszeit hochbringen kann... Damit ist dann das obligatorische Filtern viel einfacher...

Ich hab leider keinen passenden Link gefunden der das illustriert aber mann man ja recht einfach nachprüfen...

Den Zählerwert mit dieser Tabelle <c> static const unsigned char BitReverseTable256[] = {

 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 
 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 
 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 
 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 
 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 
 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 
 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 
 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 
 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 
 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF

}; </c>

und dann vergleicht man eigentlich BitReverseTable256[ZÄHLERWERT] mit dem eingestellten PWM-Wert...

Wenn man jetzt z.b 50% dutycycle betrachtet erkennt man schon ganz deutlich die Verbesserung: bei jedem Vergleich wird getoggelt!

Warum kein einziger Controller den ich kenne sowas macht versteh ich nicht...

Derzeit bastle ich an einer einfachen Motorsteuerung und da wird eben ein DC-Motor per PWM vergewaltigt. Software-PWM deshalb weil ich den Timer sowieso zur Zeitmessung verwende.. Mit dieser Methode hab' ich jetzt eine Schaltfrequenz am Motor von 14kHz :)

73


>Vielleicht wärs nicht schlecht auch noch drauf hinzuweise, dass man duch ein >einfaches bit-reverse die Schaltfrequenz auf die Timer-Zykluszeit hochbringen >kann... Damit ist dann das obligatorische Filtern viel einfacher...

Das nennt sich Puls-DICHTE-Modulation

>Wenn man jetzt z.b 50% dutycycle betrachtet erkennt man schon ganz deutlich die >Verbesserung: >bei jedem Vergleich wird getoggelt!

Fast.

>Warum kein einziger Controller den ich kenne sowas macht versteh ich nicht...

Für PWM/PDM zur Erzeugung von Referenzspannungen für ADC, whatever ist das ganz nützlich. Aber . . .

>Derzeit bastle ich an einer einfachen Motorsteuerung und da wird eben ein >DC-Motor per PWM vergewaltigt. >Software-PWM deshalb weil ich den Timer sowieso zur Zeitmessung verwende.. >Mit dieser Methode hab' ich jetzt eine Schaltfrequenz am Motor von 14kHz :)

Eben für Leistungsendstufen ist das quasi unbrauchbar, weil dann die Schaltverluste zu hoch werden, weil zu oft geschaltet wird.

MFG Falk


Die Schaltverluste sollten eigentlich mit guten FETs und relativ wenig Strom nicht gravierend sein...

Das einzige was bäse ist beim schnellen Schalten ist das EMV-Problem...

Auf jedenfall rennt bei mir der Motor so sauberer... also viel konstanteres Moment. Vor allem wenn er langsam über eine Rampe hochläuft merkt man das...

73 --Hans 16:58, 10. Jan. 2008 (CET)


Binratemultiplier Ich glaube da wird auch irgendwie Bitreverse gemacht, das höchstwertige Bit liegt an der niederwertigsten Stelle an 73 de DB1UQ

Intelligenter Lösungsansatz auf Atmega128

Guten Tag,

ich finde es wirklich toll, daß sich jemand mal den Kopf zerbricht und so eine leistungsstarke Software PWM programmiert. Nur wurde diese Software mal auf anderen µC getestet als dem Atmega32? Ich habe es jetzt auf einem 128er und nem 64er Versucht. Bei beiden zerbröselt es einem die PWM, wenn man die Kanäle mit bestimmten Werten fütter, wie z.B. 200,0,100,30,0 usw. Da kommt dann nichts sinnvolles mehr raus. Ist die Frage ob das eine Einstellungssache für die anderen µC ist, oder ein genereller Softwarefehler. Habe leider gerade keinen 32er zur Hand, sonst würde ich das mal selber testen.

Noch schnellere Implementierung ohne Compare Register

Hallo,

ich habe teils mit Hilfestellung dieses Artikels ein PWM für einen Timer ohne Compare Register implementiert. Das PWM wurde Trailing Edge implementiert. Ein Compare-Register habe ich bei dem verwendeten Timer schlichtweg nicht zur Verfügung. Ebenso verwende ich keine Multiplikationen und Divisionen, da ATtinys diese nicht vernünftiger Zeit berechnen.

Der ISR-Aufruf braucht bei mir nur 25-28 Taktzyklen (-75%). Die update-Funktion benötigt zwischen 34 (1 Kanal) und 1300 (max. 8 Kanäle) Taktzyklen (-28%). Insgesamt ist der Code mit 562 Bytes auch kompakter (-42%).

Mein Controller läuft dabei mit 8 MHz und generiert mit einem 8-Bit Timer eine 8-Bit PWM mit einer Frequenz von 122 Hz auf einem Port (8 Kanäle). Zu Testzwecken fade ich die LEDs im main-Loop.

Ich habe auch noch eine den Code für einen zusätzlichen Port integriert, aber der skaliert in der update-Funktion sehr schlecht und müsste daher ggf. überarbeitet werden.

   /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    * 8-Bit Trailing Edge Soft PWM for AVR2 Architecture                        *
    * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    * This Soft-PWM implementation uses one 8 bit timer without the need of a   *
    * compare mode. It controls up to 8 channels on one port or 16 channels on  *
    * two ports and can easily be extended to more output ports.                */
   #define F_CPU                  8000000         // 8 MHz
   #include <avr/io.h>
   #include <avr/interrupt.h>
   #include <util/delay.h>
   #include <avr/pgmspace.h>
   /* PWM Timer Settings                                                        *
    * timer = timer1;  mode = 8 bit normal;  clock = clk/256 (31.25kHz);        *
    * overflow interrupt enabled (approx. 122/s)                                */
   #define PWM_TIMER_OVF_vect     TIMER1_OVF_vect
   #define PWM_TCNT               TCNT1
   #define PWM_TIMER_SETUP        { TCCR1A = 0; \
                                    TCCR1B = 1<<CS13 | 1<<CS10 | 1<<PSR1; \
                                    TIMSK |= 1<<TOIE1; }
   /* PWM Output Port Settings                                                  *
    * The first `PWM_CHANNEL_COUNT` lines of `PWM_PORT` are used.               */
   #define PWM_PORT               PORTA
   #define PWM_DDR                DDRA
   #define PWM_CHANNEL_COUNT      8
   //#define PWM_TWO_PORTS                        // use a second PWM port
   #define PWM_PORT_2             PORTB
   #define PWM_DDR_2              PORTB
   #define PWM_CHANNEL_COUNT_2    8


   #define PWM_FULL_CHANNEL_COUNT  PWM_CHANNEL_COUNT
   #ifdef PWM_TWO_PORTS
     #define PWM_FULL_CHANNEL_COUNT  ( PWM_CHANNEL_COUNT + PWM_CHANNEL_COUNT_2 )
   #endif
   typedef struct {
     uint8_t mask;                // port mask
     #ifdef PWM_TWO_PORTS
       uint8_t mask_2;
     #endif
     uint8_t value;               // delay or pulse width
   } PWM_Slot;
   // PWM timer value vector
   // two vectors and pointers for seamless reconfiguration
   PWM_Slot pwm_slots_a[ PWM_FULL_CHANNEL_COUNT + 1 ];
   PWM_Slot pwm_slots_b[ PWM_FULL_CHANNEL_COUNT + 1 ];
   PWM_Slot *pwm_slots_isr = pwm_slots_a;
   PWM_Slot *pwm_slots_config = pwm_slots_b;
   volatile uint8_t pwm_current_isr_slot;
   ISR( PWM_TIMER_OVF_vect )
   {/* This is the interrupt routine for the PWM timer overflow.  It generates  *
     * trailing edge Soft PWM.  All channels are set to zero at the start of a  *
     * cycle.  The `value` property of a pwm_slots_isr item contains the timer  *
     * start value for delaying the next slot.  The `mask` property holds the   *
     * port mask.                                                               *
     * Size optimized code executes this function in the following ammount of   *
     * clock cycles (port mask change)/(restart): 28/25 for a single port and   *
     * 58/57 for two ports. 2-3 additional clock cycles per port could be saved *
     * by assigning the whole port to mask resp. 0 and precalculating the full  *
     * mask. That way all other output lines would be assigned as well!         */
     uint8_t local_number = pwm_current_isr_slot;
     PWM_Slot current_slot = pwm_slots_isr[local_number];
     PWM_TCNT = current_slot.value;
     if( current_slot.mask ){
       PWM_PORT |= current_slot.mask;
       #ifdef PWM_TWO_PORTS
         PWM_PORT_2 |= current_slot.mask_2;
       #endif
       pwm_current_isr_slot = local_number + 1;
     }
     else {
       PWM_PORT &= 0xFF<<PWM_CHANNEL_COUNT;
       #ifdef PWM_TWO_PORTS
         PWM_PORT_2 &= 0xFF<<PWM_CHANNEL_COUNT_2;
       #endif
       pwm_current_isr_slot = 0;
     }
   }
   void update_pwm( void )
   {/* This function replaces the PWM ISR parameters at the start of a PWM      *
     * cycle.                                                                   */
     PWM_Slot *pwm_slots_ptr = pwm_slots_isr;
     // wait for pwm_current_isr_slot to switch to zero
     if(pwm_slots_isr[0].mask)
       while(pwm_current_isr_slot == 0)
         {}
     while(pwm_current_isr_slot)
       {}
     // shift pwm_slots_isr pointer;
     cli();
     pwm_slots_isr = pwm_slots_config;
     sei();
     pwm_slots_config = pwm_slots_ptr;
   }
   void set_pwm( PWM_Slot *channel )
   {/* This function writes propper PWM isr parameters into pwm_slots_config.   *
     * For a single port it scales with a about 160 clock cycles per channel.   *
     * For two ports it scales worse (maybe 500 clock cycles per channel). IT   *
     * should be possible to improove this by using some pointer arithmetic and *
     * a smarter sorting alogrithm in that case.                                */
     uint8_t pwmslots = PWM_FULL_CHANNEL_COUNT;
     
     // sort channels from max to min PWM width
     for( uint8_t i = 0; i < pwmslots; i++ ){
       // search channel of maximal value
       uint8_t max_channel = i;
       uint8_t max_value = channel[i].value;
       for( uint8_t j = i; j < pwmslots; j++ ){
         if( max_value < channel[j].value){
           max_value = channel[j].value;
           max_channel = j;
         }
       }
       // Merge channels if value is equal to previous.
       if( i && ( max_value == channel[ i - 1 ].value )){
         pwmslots--;
         channel[i - 1].mask |= channel[max_channel].mask;
         #ifdef PWM_TWO_PORTS 
           channel[i - 1].mask_2 |= channel[max_channel].mask_2;
         #endif
         channel[max_channel] = channel[pwmslots];
         i--;
       }
       // Else move channel of maximal value to index `i`.
       else if( max_channel != i ){
         PWM_Slot tmp = channel[max_channel];
         channel[max_channel] = channel[i];
         channel[i] = tmp;
       }
     }
     // skip channels with a PWM width of zero
     if( channel[ pwmslots - 1 ].value == 0)
       pwmslots--;
     
     // write values into pwm_slots_config
     // pwm_slot[pwmslots] is black time before first/longest slot
     pwm_slots_config[pwmslots].mask = 0;
     #ifdef PWM_TWO_PORTS
       pwm_slots_config[pwmslots].mask_2 = 0;
     #endif
     pwm_slots_config[pwmslots].value = channel[0].value;
     uint8_t following = 0;
     for(uint8_t i = pwmslots; i;){
       i--;
       // pwm_slot[n].mask: port mask for the n^th PWM width
       pwm_slots_config[i].mask = channel[i].mask;
       #ifdef PWM_TWO_PORTS
         pwm_slots_config[i].mask_2 = channel[i].mask_2;
       #endif
       // pwm_slot[n].value: delay between n^th and n+1^st up-flank
       pwm_slots_config[i].value = following - channel[i].value;
       following = channel[i].value;
     }
   }
   void initialize_pwm( void )
   {/*  This function initialized the Soft-PWM.                                 */
     // set data direction register for PWM port
     PWM_DDR |= ~( 0xFF<<PWM_CHANNEL_COUNT );
     #ifdef PWM_TWO_PORTS
       PWM_DDR_2 |= ~( 0xFF<<PWM_CHANNEL_COUNT_2 );
     #endif
     // setup timer and activate overflow interrupt
     PWM_TIMER_SETUP
     // initialize PWM timer value vector
     pwm_slots_a[0].mask = 0;
     #ifdef PWM_TWO_PORTS
       pwm_slots_a[0].mask_2 = 0;
     #endif
     pwm_slots_a[0].value = -1;
   }
   int main( void )
   { /* Main program function                                                   */
     uint8_t val; // fading counter
     initialize_pwm();
     while(1){
       // fade over the PWM channels
       PWM_Slot channel[PWM_FULL_CHANNEL_COUNT];
       for( uint8_t i=0; i < PWM_FULL_CHANNEL_COUNT; i++ ){
         #ifdef PWM_TWO_PORTS
           if( i < PWM_CHANNEL_COUNT){
             channel[i].mask = 1<<i;
             channel[i].mask_2 = 0;
           }
           else{
             channel[i].mask = 0;
             channel[i].mask_2 = 1<<(i - PWM_CHANNEL_COUNT);
           }
         #else
           channel[i].mask = 1<<i;
         #endif
         // shift phase each channel
         channel[i].value = val + i * ((1<<8) / PWM_FULL_CHANNEL_COUNT - 1);
         // fade up-down (0, .., 127, 127, .., 0)
         if( channel[i].value & 1<<7 )
           channel[i].value = 255 - channel[i].value;
       }
       set_pwm(channel);
       update_pwm();


       // increase fading counter (0, 1, .., 255, 0, ..)
       val++;
       _delay_ms(10);
     }
   }

Fußnoten