<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="de">
	<id>https://www.mikrocontroller.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=91.19.247.16</id>
	<title>Mikrocontroller.net - Benutzerbeiträge [de]</title>
	<link rel="self" type="application/atom+xml" href="https://www.mikrocontroller.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=91.19.247.16"/>
	<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/articles/Spezial:Beitr%C3%A4ge/91.19.247.16"/>
	<updated>2026-04-11T03:16:05Z</updated>
	<subtitle>Benutzerbeiträge</subtitle>
	<generator>MediaWiki 1.39.7</generator>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=LED-Fading&amp;diff=47586</id>
		<title>LED-Fading</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=LED-Fading&amp;diff=47586"/>
		<updated>2010-05-16T20:30:11Z</updated>

		<summary type="html">&lt;p&gt;91.19.247.16: /* Das Demoprogramm */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Das Problem ==&lt;br /&gt;
&lt;br /&gt;
Die Aufgabe klingt eigentlich recht einfach. Eine [[LED]] soll mittels [[PWM]] in ihrer Helligkeit gesteuert werden. Und weils so schön ist, möchte man sie geheimnisvoll aufleuchten lassen, sprich langsam heller und dunkler werden lassen. Der Fachmann nennt das Fading. Das Problem zeigt sich allerdings recht schnell. Wenn man eine 8-Bit PWM linear zwischen 0..255 laufen lässt, dann scheint die LED nicht linear gedimmt zu werden. Sie wird relativ schnell hell und bleibt lange hell.&lt;br /&gt;
&lt;br /&gt;
== Die Erklärung ==&lt;br /&gt;
&lt;br /&gt;
Des Rätsels Lösung liegt in der Kennline des menschlichen Auges. Diese ist nichtlinear, genauer gesagt: sie ist nahezu logarithmisch. Das ermöglicht die Wahrnehmung eines sehr großen Helligkeitsbereichs, angefangen von Vollmond mit ~1/4 [http://de.wikipedia.org/wiki/Lux_%28Einheit%29 Lux] über eine normale Schreibtischbeleuchtung mit ca. 750 Lux bis zu einem hellen Sommertag mit bis zu 100.000 Lux. Solche hochdynamischen Signale sind nur mit einer logarithmischen Kennline in den Griff zu kriegen, auch von Mutter Natur und Erfinder Papa.&lt;br /&gt;
&lt;br /&gt;
=== Die Kennlinie des Auges genau betrachtet ===&lt;br /&gt;
&lt;br /&gt;
Die Kennlinie des menschlichen Auges ist annähernd logarithmisch. Das wurde vor langer Zeit duch das [http://de.wikipedia.org/wiki/Weber-Fechner-Gesetz Weber-Fechner-Gesetz] beschrieben. Genauere Untersuchungen zur [http://de.wikipedia.org/wiki/Gamma-Korrektur Gammakorrektur] führten jedoch zur [http://de.wikipedia.org/wiki/Stevenssche_Potenzfunktion Stevenschen Potenzfunktion]. Diese beschreibt das menschliche Auge etwas besser. Die Unterschiede sind jedoch marginal. Vorsicht! Der Artikel Gamma-Korrektur in Wikipedia verweist fälschlicherweise auf das Weber-Fechner Gesetz.&lt;br /&gt;
&lt;br /&gt;
Praktisch heißt das, daß wir unserem Auge große physikalische Helligkeitsunterschiede präsentieren müssen, damit es das als lineare Helligkeitsteigerung erkennt. Etwas wissenschaftlicher formuliert heißt das, wir müssen durch Verkettung der logarithmischen Kennlinie des Auges mit einer exponentiellen Kennlinie eine physiologisch lineare Helligkeitsverteilung erzielen.&lt;br /&gt;
&lt;br /&gt;
Berechnet werden kann eine passende Tabelle beispielsweise mit folgender Funktion:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;y=\frac{b^{\frac{x}{r_x}}-1}{b-1}\cdot r_y&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dabei sind x und y die Ein-, bzw. Ausgabewerte der Funktion, jeweils im Bereich von 0 bis r-1. b ist die Basis der Exponentialfunktion und bestimmt, wann und wie stark die Kurve ansteigen soll. Hier ist etwas ausprobieren erforderlich, gute Ergebnisse liefern Werte im Bereich 10-100.&lt;br /&gt;
&lt;br /&gt;
== Das Demoprogramm ==&lt;br /&gt;
&lt;br /&gt;
Das wird in dem unten gezeigten Beispielprogramm gemacht. Dabei wird die Wirkung verschiedener PWM-Auflösungen demonstriert. Eine 8-Bit PWM wird mit 4/8/16 und 32 nichtlinearen Stufen betrieben, welche über eine Exponentialfunktion berechnet wurden. Dazu dient die [[media:pwm_table.zip|Exceltabelle]] (Anmerkung: Bitte die Exceltabelle nochmal erklären, die Werte in der Tabelle stimmen nicht mit denen im Programm überein). Die einzelnen benachbarten Werte haben zueinander ein konstantes Verhältnis, das in der Exceltabelle als &#039;Factor&#039; berechnet wird. Ausserdem werden eine 10-Bit PWM mit 64 Stufen sowie, als die Königsklasse, eine 16-Bit PWM mit 256 Stufen betrieben.&lt;br /&gt;
&lt;br /&gt;
Das Programm ist ursprünglich auf einem [[AVR]] vom Typ &#039;&#039;&#039;ATmega32&#039;&#039;&#039; entwickelt und getestet. Aber es ist leicht auf jeden AVR portierbar, welcher eine PWM zur Verfügung hat. Der AVR muss mit etwa 8 MHz getaktet werden, egal ob mit internem Oszillator oder von aussen mit Quarz. Man muss nur noch eine LED mittels Vorwiderstand von ca. 1 kOhm an Pin PD5 anschliessen und los gehts. Es sollte hier noch erwähnt werden, dass das Programm mit eingeschalteter Optimierung compiliert werden muss, sonst stimmen die Zeiten der Warteschleifen nicht.&lt;br /&gt;
&lt;br /&gt;
Bei Verwendung der LEDs auf dem STK500 bzw. bei der Verwendung von invertierenden Treiberstufen ist das &amp;quot;#define STK500 false&amp;quot; durch &amp;quot;#define STK500 true&amp;quot; zu ersetzen.&lt;br /&gt;
&lt;br /&gt;
Das Programm durchläuft alle 6 PWMs und lässt dabei die LED jeweils 3 mal glimmen. Mit 4 Schritten Auflösung ist das natürlich ruckelig, mit 8 schon wesentlich besser. Mit 16 Stufen sieht man bei langsamen Änderungen noch Stufen, dreht man die Ein- und Ausblendzeiten runter ist der Übergang schon recht flüssig. Die 8-Bit PWM mit 32 Stufen unterscheidet sich praktisch nicht von der 10-Bit PWM mit 64 Stufen, es sei denn, man macht extrem langsame Einblendungen. Hier schlägt die Stunde der 16-Bit PWM. Diese wird bewußt sehr langsam ausgeführt um zu demonstrieren, daß hiermit praktisch keine Stufen mehr sichtbar sind, egal wie langsam gedimmt wird. Wie man auch sieht sind die drei höherauflösenden PWMs im unteren Bereich an ihrer Auflösungsgrenze, da einige PWM-Werte mehrfach vorkommen. Da heißt gleichzeitig, daß eine Steigerung der Stufenanzahl relativ sinnlos ist.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
//*****************************************************************************&lt;br /&gt;
//*&lt;br /&gt;
//*  LED fading test&lt;br /&gt;
//*  uses exponential PWM settings to achive visual linear brightness&lt;br /&gt;
//*&lt;br /&gt;
//*  ATmega32 @ 8 MHz&lt;br /&gt;
//*  &lt;br /&gt;
//*                  &lt;br /&gt;
//*****************************************************************************&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L&lt;br /&gt;
&lt;br /&gt;
#define true 1&lt;br /&gt;
#define false 0&lt;br /&gt;
&lt;br /&gt;
#define STK500 false&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;inttypes.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/pgmspace.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// global variables&lt;br /&gt;
&lt;br /&gt;
uint16_t pwmtable_8A[4]   PROGMEM = {0, 16, 64, 255};&lt;br /&gt;
uint16_t pwmtable_8B[8]   PROGMEM = {0, 4,  8, 16, 32, 64,  128, 255};&lt;br /&gt;
uint16_t pwmtable_8C[16]  PROGMEM = {0, 2, 3, 4, 6, 8, 11, 16, 23, 32, 45, 64,&lt;br /&gt;
                                    90, 128, 181, 255};&lt;br /&gt;
uint16_t pwmtable_8D[32]  PROGMEM = {0, 1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 8, 10, 11,&lt;br /&gt;
                                    13, 16, 19, 23, 27, 32, 38, 45, 54, 64, 76,&lt;br /&gt;
                                    91, 108, 128, 152, 181, 215, 255};&lt;br /&gt;
&lt;br /&gt;
uint16_t pwmtable_10[64]  PROGMEM = {0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5,&lt;br /&gt;
                                    5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17,&lt;br /&gt;
                                    19, 21, 23, 26, 29, 32, 36, 40, 44, 49, 55,&lt;br /&gt;
                                    61, 68, 76, 85, 94, 105, 117, 131, 146, 162,&lt;br /&gt;
                                    181, 202, 225, 250, 279, 311, 346, 386, 430,&lt;br /&gt;
                                    479, 534, 595, 663, 739, 824, 918, 1023};&lt;br /&gt;
&lt;br /&gt;
uint16_t pwmtable_16[256] PROGMEM = {0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,&lt;br /&gt;
                                     2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,&lt;br /&gt;
                                     4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6,&lt;br /&gt;
                                     6, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11,&lt;br /&gt;
                                     11, 12, 12, 13, 13, 14, 15, 15, 16, 17, 17,&lt;br /&gt;
                                     18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,&lt;br /&gt;
                                     29, 31, 32, 33, 35, 36, 38, 40, 41, 43, 45,&lt;br /&gt;
                                     47, 49, 52, 54, 56, 59, 61, 64, 67, 70, 73,&lt;br /&gt;
                                     76, 79, 83, 87, 91, 95, 99, 103, 108, 112,&lt;br /&gt;
                                     117, 123, 128, 134, 140, 146, 152, 159, 166,&lt;br /&gt;
                                     173, 181, 189, 197, 206, 215, 225, 235, 245,&lt;br /&gt;
                                     256, 267, 279, 292, 304, 318, 332, 347, 362,&lt;br /&gt;
                                     378, 395, 412, 431, 450, 470, 490, 512, 535,&lt;br /&gt;
                                     558, 583, 609, 636, 664, 693, 724, 756, 790,&lt;br /&gt;
                                     825, 861, 899, 939, 981, 1024, 1069, 1117,&lt;br /&gt;
                                     1166, 1218, 1272, 1328, 1387, 1448, 1512,&lt;br /&gt;
                                     1579, 1649, 1722, 1798, 1878, 1961, 2048,&lt;br /&gt;
                                     2139, 2233, 2332, 2435, 2543, 2656, 2773,&lt;br /&gt;
                                     2896, 3025, 3158, 3298, 3444, 3597, 3756,&lt;br /&gt;
                                     3922, 4096, 4277, 4467, 4664, 4871, 5087,&lt;br /&gt;
                                     5312, 5547, 5793, 6049, 6317, 6596, 6889,&lt;br /&gt;
                                     7194, 7512, 7845, 8192, 8555, 8933, 9329,&lt;br /&gt;
                                     9742, 10173, 10624, 11094, 11585, 12098,&lt;br /&gt;
                                     12634, 13193, 13777, 14387, 15024, 15689,&lt;br /&gt;
                                     16384, 17109, 17867, 18658, 19484, 20346,&lt;br /&gt;
                                     21247, 22188, 23170, 24196, 25267, 26386,&lt;br /&gt;
                                     27554, 28774, 30048, 31378, 32768, 34218,&lt;br /&gt;
                                     35733, 37315, 38967, 40693, 42494, 44376,&lt;br /&gt;
                                     46340, 48392, 50534, 52772, 55108, 57548,&lt;br /&gt;
                                     60096, 62757, 65535};&lt;br /&gt;
&lt;br /&gt;
// long delays&lt;br /&gt;
&lt;br /&gt;
void my_delay(uint16_t milliseconds) {&lt;br /&gt;
    for(; milliseconds&amp;gt;0; milliseconds--) _delay_ms(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 8-Bit PWM with only 4 different settings&lt;br /&gt;
&lt;br /&gt;
void pwm_8_4(uint16_t delay){&lt;br /&gt;
&lt;br /&gt;
    int16_t tmp;&lt;br /&gt;
&lt;br /&gt;
#if STK500&lt;br /&gt;
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#else&lt;br /&gt;
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#endif&lt;br /&gt;
    TCCR1B = 0x08;&lt;br /&gt;
&lt;br /&gt;
    TCCR1B &amp;amp;= ~0x7;         // clear clk setting&lt;br /&gt;
    TCCR1B |= 4;            // precaler 256 -&amp;gt; ~122 Hz PWM frequency&lt;br /&gt;
&lt;br /&gt;
    for(tmp=0; tmp&amp;lt;=3; tmp++){&lt;br /&gt;
      OCR1A =  pgm_read_word(pwmtable_8A+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(tmp=3; tmp&amp;gt;=0; tmp--){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_8A+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 8-Bit PWM with 8 different settings&lt;br /&gt;
&lt;br /&gt;
void pwm_8_8(uint16_t delay){&lt;br /&gt;
&lt;br /&gt;
    int16_t tmp;&lt;br /&gt;
&lt;br /&gt;
#if STK500&lt;br /&gt;
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#else&lt;br /&gt;
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#endif&lt;br /&gt;
    TCCR1B = 0x08;&lt;br /&gt;
&lt;br /&gt;
    TCCR1B &amp;amp;= ~0x7;         // clear clk setting&lt;br /&gt;
    TCCR1B |= 4;            // precaler 256 -&amp;gt; ~122 Hz PWM frequency&lt;br /&gt;
&lt;br /&gt;
    for(tmp=0; tmp&amp;lt;=7; tmp++){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_8B+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    for(tmp=7; tmp&amp;gt;=0; tmp--){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_8B+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 8-Bit PWM with 16 different settings&lt;br /&gt;
&lt;br /&gt;
void pwm_8_16(uint16_t delay){&lt;br /&gt;
&lt;br /&gt;
    int16_t tmp;&lt;br /&gt;
&lt;br /&gt;
#if STK500&lt;br /&gt;
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#else&lt;br /&gt;
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#endif&lt;br /&gt;
    TCCR1B = 0x08;&lt;br /&gt;
&lt;br /&gt;
    TCCR1B &amp;amp;= ~0x7;         // clear clk setting&lt;br /&gt;
    TCCR1B |= 4;            // precaler 256 -&amp;gt; ~122 Hz PWM frequency&lt;br /&gt;
&lt;br /&gt;
    for(tmp=0; tmp&amp;lt;=15; tmp++){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_8C+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(tmp=15; tmp&amp;gt;=0; tmp--){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_8C+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 8-Bit PWM with 32 different settings&lt;br /&gt;
&lt;br /&gt;
void pwm_8_32(uint16_t delay){&lt;br /&gt;
&lt;br /&gt;
    int16_t tmp;&lt;br /&gt;
&lt;br /&gt;
#if STK500&lt;br /&gt;
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#else&lt;br /&gt;
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM&lt;br /&gt;
#endif&lt;br /&gt;
    TCCR1B = 0x08;&lt;br /&gt;
&lt;br /&gt;
    TCCR1B &amp;amp;= ~0x7;         // clear clk setting&lt;br /&gt;
    TCCR1B |= 4;            // precaler 256 -&amp;gt; ~122 Hz PWM frequency &lt;br /&gt;
&lt;br /&gt;
    for(tmp=0; tmp&amp;lt;=31; tmp++){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_8D+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(tmp=31; tmp&amp;gt;=0; tmp--){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_8D+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 10-Bit PWM with 64 different settings&lt;br /&gt;
&lt;br /&gt;
void pwm_10_64(uint16_t delay){&lt;br /&gt;
&lt;br /&gt;
    int16_t tmp;&lt;br /&gt;
&lt;br /&gt;
#if STK500&lt;br /&gt;
    TCCR1A = 0xC3;          // inverted PWM on OC1A, 10 Bit Fast PWM&lt;br /&gt;
#else&lt;br /&gt;
    TCCR1A = 0x83;          // non-inverted PWM on OC1A, 10 Bit Fast PWM&lt;br /&gt;
#endif&lt;br /&gt;
    TCCR1B = 0x08;&lt;br /&gt;
&lt;br /&gt;
    TCCR1B &amp;amp;= ~0x7;         // clear clk setting&lt;br /&gt;
    TCCR1B |= 3;            // precaler 64 -&amp;gt; ~122 Hz PWM frequency&lt;br /&gt;
&lt;br /&gt;
    for(tmp=0; tmp&amp;lt;=63; tmp++){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_10+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(tmp=63; tmp&amp;gt;=0; tmp--){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_10+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 16-Bit PWM with 256 different settings&lt;br /&gt;
&lt;br /&gt;
void pwm_16_256(uint16_t delay){&lt;br /&gt;
&lt;br /&gt;
    int16_t tmp;&lt;br /&gt;
&lt;br /&gt;
#if STK500&lt;br /&gt;
    TCCR1A = 0xC2;          // inverted PWM on OC1A, 16 Bit Fast PWM&lt;br /&gt;
#else&lt;br /&gt;
    TCCR1A = 0x82;          // non-inverted PWM on OC1A, 16 Bit Fast PWM&lt;br /&gt;
#endif&lt;br /&gt;
    TCCR1B = 0x18;&lt;br /&gt;
    ICR1 = 0xFFFF;          // TOP for PWM, full 16 Bit&lt;br /&gt;
&lt;br /&gt;
    TCCR1B &amp;amp;= ~0x7;         // clear clk setting&lt;br /&gt;
    TCCR1B |= 1;            // precaler 1 -&amp;gt; ~122 Hz PWM frequency&lt;br /&gt;
&lt;br /&gt;
    for(tmp=0; tmp&amp;lt;=255; tmp++){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_16+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    for(tmp=255; tmp&amp;gt;=0; tmp--){&lt;br /&gt;
      OCR1A = pgm_read_word(pwmtable_16+tmp);&lt;br /&gt;
      my_delay(delay);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    int16_t i;&lt;br /&gt;
    int16_t step_time=400;      // delay in millisecond for one fading step&lt;br /&gt;
&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD5);           // LED uses OC1A&lt;br /&gt;
&lt;br /&gt;
    // test all fading routines&lt;br /&gt;
&lt;br /&gt;
    while(1) {&lt;br /&gt;
&lt;br /&gt;
      for(i=0; i&amp;lt;3; i++) pwm_8_4(step_time);&lt;br /&gt;
      my_delay(1000);&lt;br /&gt;
      for(i=0; i&amp;lt;3; i++) pwm_8_8(step_time/2);&lt;br /&gt;
      my_delay(1000);    &lt;br /&gt;
      for(i=0; i&amp;lt;3; i++) pwm_8_16(step_time/4);&lt;br /&gt;
      my_delay(1000);&lt;br /&gt;
      for(i=0; i&amp;lt;3; i++) pwm_8_32(step_time/8);&lt;br /&gt;
      my_delay(1000);&lt;br /&gt;
      for(i=0; i&amp;lt;3; i++) pwm_10_64(step_time/16);&lt;br /&gt;
      my_delay(1000);&lt;br /&gt;
      for(i=0; i&amp;lt;3; i++) pwm_16_256(step_time/16);&lt;br /&gt;
      my_delay(1000);&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Siehe auch ==&lt;br /&gt;
* [[PWM]]&lt;br /&gt;
* [[AVR-GCC-Tutorial#PWM (Pulsweitenmodulation)|AVR-GCC-Tutorial: PWM]]&lt;br /&gt;
* [[Soft-PWM]] - optimierte Software-PWM in C&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:AVR-Projekte]]&lt;br /&gt;
[[Category:Displays und Anzeigen]]&lt;/div&gt;</summary>
		<author><name>91.19.247.16</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47585</id>
		<title>Soft-PWM</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47585"/>
		<updated>2010-05-16T20:29:03Z</updated>

		<summary type="html">&lt;p&gt;91.19.247.16: /* Siehe auch */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einleitung ==&lt;br /&gt;
[[PWM]] ist eine oft verwendete Funktion auf dem Gebiet der Mikrocontroller. Damit lassen sich vielfältige Aufgaben lösen, wie beispielsweise die Leistungssteuerung von Motoren, Helligkeitssteuerung von [[LED-Fading | LEDs]], [[DA-Wandler | Digital-Analog Wandlung]] und vieles mehr. Die meisten Mikrocontroller haben ein oder mehrere PWM-Module eingebaut, womit ohne CPU-Belastung eine PWM generiert werden kann. Jedoch kommt es bisweilen vor, daß die Anzahl der verfügbaren PWM-Kanäle nicht ausreicht. Dann muß eine Softwarelösung gefunden werden, bei der die CPU die PWM-Generierung vornimmt, allgemein Soft-PWM genannt.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Achtung:&#039;&#039;&#039; Wenn man für eine PWM aussschliesslich 8 Bit breite Datentypen verwendet, dann steht für den Parameter für die Pulsbreite nur der Bereich 0..255 zur Verfügung. Da bei einer vollständigen PWM mit N Schritten aber N+1 mögliche Fälle auftreten können (0/N bis N/0), ist mit dem hier gezeigten Code eine solche PWM nur für N &amp;lt;= 255 realisierbar.&lt;br /&gt;
&lt;br /&gt;
== Einfacher Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Ein sehr einfacher Lösungsansatz findet sich im [[AVR-Tutorial: PWM]]. Hier wird schon ein [[AVR-Tutorial: Timer | Timer]] benutzt, um in regelmäßigen Abständen die PWM-Generierung durchzuführen. Damit verbleibt noch Rechenzeit für andere Aufgaben, außerdem wird die Programmierung wesentlich vereinfacht. Allerdings ist das Beispiel in ASM, hier soll das ganze in [[C]] gemacht werden.&lt;br /&gt;
&lt;br /&gt;
Es soll nun eine 8 Bit PWM mit 100 Hz (PWM-Zyklus 10ms) und acht Kanälen generiert werden. Der verwendete Controller ist ein [[AVR]] vom Typ ATmega32, welcher mit dem internen RC-Oszillator auf 8 MHz getaktet wird. Das Programm kann jedoch problemlos an so ziemlich jeden anderen AVR angepaßt werden. Die Programme wurden mit dem Optimierungsgrad -Os compiliert.&lt;br /&gt;
&lt;br /&gt;
=== Erster Versuch ===&lt;br /&gt;
&lt;br /&gt;
Das Programm ist recht kurz und übersichtlich. Im Hauptprogramm wird der Timer 1 initialisiert und der Output Compare 1A als variabler Timer verwendet, wobei die Output Compare Funktion nicht mit dem IO-Pin verbunden ist. Im Interrupt, welcher regelmäßig aufgerufen wird, werden nun in einer Schleife alle acht Kanäle geprüft. Alle Kanäle werden auf HIGH gesetzt, welche eine PWM-Einstellung größer als der aktuelle Zykluszähler haben. Sinnvollerweise werden erst alle Kanäle geprüft und das Ergebnis zwischengespeichert, am Ende erfolgt nur ein Zugriff auf den Port.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit einfachem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 255                   // PWM-Schritte pro Zyklus(1..255)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(152+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if PWM_STEPS &amp;gt; 255&lt;br /&gt;
    #error PWM_STEPS zu gross&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0, i=0, j=1;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
&lt;br /&gt;
    for (; i&amp;lt;8; i++) {    &lt;br /&gt;
    	if (pwm_setting[i] &amp;gt; pwm_cnt) tmp |= j;&lt;br /&gt;
            j&amp;lt;&amp;lt;=1;&lt;br /&gt;
	}&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Im [[AVR-Studio]] kann man den Code simulieren. Wichtig ist hier vor allem die Ausführungszeit des Interrupts. Bei 100 Hz PWM-Frequenz und 256 Schritten pro PWM-Zyklus wird diese Funktion immerhin 25600 mal pro Sekunde aufgerufen (PWM-Takt 25,6 kHz),  bei 8MHz Taktfrequenz stehen damit maximal 312 Takte zur Verfügung. Glücklicherweise ist die Funktion relativ kurz und der GCC leistet gute Arbeit. Der Interrupt benötigt hier 152 Takte, es verbleiben also jeweils 160 Takte zur Bearbeitung anderer Aufgaben. Das entspricht einer CPU-Belastung von ~49%. Das Programm benötigt 284 Byte Programmspeicher. Nicht schlecht für den Anfang.&lt;br /&gt;
&lt;br /&gt;
=== Zweiter Versuch ===&lt;br /&gt;
&lt;br /&gt;
Wo gibt es in diesem Programm noch Optimierungsmöglichkeiten? Nur im Interrupt, denn das ganze Programm besteht ja praktisch nur aus der Interruptroutine. Betrachten wir die Schleifen genauer müssen wir feststellen, daß die Indizierung von pwm_setting[] etwas Rechenzeit benötigt. Ebenso die Schiebeoperation von tmp, auch wenn das nur acht mal ein Takt ist. Wir können jetzt per Hand das machen, was der Compiler auch manchmal macht. Die Rede ist vom Loop-Unrolling. Dabei wird die Schleife durch mehrere diskrete Befehle ersetzt (entrollt). Der Vorteil dabei ist, daß die Befehle zur Berechnung und Prüfung der Zählvariable entfallen, außerdem können ggf. Werte im Voraus berechnet werden. Als Ergebnis hat man zwar ein etwas größeres Programm, doch das wird schneller ausgeführt! Ausserdem orientiert sich diese Version mehr am Original der Assemblerversion. Dadurch wird sie zusätzlich ein wenig kürzer und schneller.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit verbessertem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 256                   // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(93+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
        &lt;br /&gt;
    if (pwm_setting[0] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;0);&lt;br /&gt;
    if (pwm_setting[1] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;1);&lt;br /&gt;
    if (pwm_setting[2] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;2);&lt;br /&gt;
    if (pwm_setting[3] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;3);&lt;br /&gt;
    if (pwm_setting[4] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;4);&lt;br /&gt;
    if (pwm_setting[5] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;5);&lt;br /&gt;
    if (pwm_setting[6] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;6);&lt;br /&gt;
    if (pwm_setting[7] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;7);&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mit dieser Interruptroutine werden nur noch 93 Takte benötigt, die CPU-Belastung verringert sich auf  ~30%. Nicht schlecht. Der Programmcode steigt auf 324 Byte, aber das ist im Angesicht der Leistungsverbesserung zu verschmerzen. Weiter verringern kann man die CPU-Belastung durch eine niedrigere PWM-Frequenz oder eine geringere Anzahl Stufen der PWM. Wenn man beispielsweise mit 64 (6 Bit) statt 256 (8 Bit) Stufen auskommt verringert sich die Belastung um den Faktor 4.&lt;br /&gt;
&lt;br /&gt;
== Intelligenter Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Wenn auch eine CPU-Last von 30% recht akzeptabel erscheint, so hat man doch noch irgendwie das Gefühl, daß es noch besser geht. Aber wie? Mein Mathematikprofessor pflegte in so einem Fall die Anwendung der „Methode des scharfen Blicks“ ™. Was passiert eigentlich während eines gesamten PWM-Zykluses?&lt;br /&gt;
&lt;br /&gt;
*Zu Beginn werden alle IO-Pins gesetzt, deren PWM-Einstellung nicht Null ist.&lt;br /&gt;
*Die jeweiligen IO-Pins werden gelöscht, wenn der PWM-Zähler mit der PWM-Einstellung übereinstimmt&lt;br /&gt;
&lt;br /&gt;
Ja klar, aber da wir nur acht Kanäle haben, gibt es maximal 8 Zeitpunkte, an denen ein Pin gelöscht werden muss. Wenn mehrere Kanäle die gleiche PWM-Einstellung haben sind es sogar noch weniger. Alle anderen Interrupts verursachen keinerlei Änderung der IO-Pins und verbrauchen eigentlich nur sinnlos Rechenzeit. Ein Skandal!&lt;br /&gt;
&lt;br /&gt;
Was ist also zu tun? Wir wissen nun, daß es maximal 9 Ereignisse pro PWM-Zyklus gibt. Was ist damit zu machen?&lt;br /&gt;
&lt;br /&gt;
*Die PWM-Kanäle müssen in aufsteigender Folge sortiert werden.&lt;br /&gt;
*Wenn mehrere PWM-Kanäle den gleichen PWM-Wert haben müssen sie zusammengefaßt werden&lt;br /&gt;
*Die Zeitdifferenzen zwischen den einzelnen Ereignissen müssen berechnet werden&lt;br /&gt;
&lt;br /&gt;
Das ist eigentlich schon alles. Praktisch ist das mit einigen Kniffligkeiten verbunden, aber die sind lösbar. Am Ende steht eine Interruptroutine, welche maximal 9 mal pro PWM-Zyklus aufgerufen wird und die jeweiligen IO-Pins setzt bzw. löscht. Eine normale Funktion wird benutzt, um die PWM-Einstellungen der acht Kanäle in die notwendigen Informationen für die Interruptroutine umzuwandeln. Das hat unter anderem den Vorteil, daß nur dann zusätzlich Rechenzeit benötigt wird, wenn sich die PWM-Einstellungen ändern.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit intelligentem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU         8000000L           // Systemtakt in Hz&lt;br /&gt;
#define F_PWM         100L               // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_PRESCALER 8                  // Vorteiler für den Timer&lt;br /&gt;
#define PWM_STEPS     256                // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT      PORTB              // Port für PWM&lt;br /&gt;
#define PWM_DDR       DDRB               // Datenrichtungsregister für PWM&lt;br /&gt;
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
//#define T_PWM 1   //TEST&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_PRESCALER)&amp;lt;(111+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_STEPS)&amp;gt;65535)&lt;br /&gt;
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   &lt;br /&gt;
#endif&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte&lt;br /&gt;
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      &lt;br /&gt;
uint8_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen&lt;br /&gt;
uint8_t  pwm_mask_tmp[PWM_CHANNELS+1];        &lt;br /&gt;
uint8_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
uint8_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!&lt;br /&gt;
volatile uint8_t pwm_sync;                    // Update jetzt möglich&lt;br /&gt;
&lt;br /&gt;
// Pointer für wechselseitigen Datenzugriff&lt;br /&gt;
&lt;br /&gt;
uint16_t * isr_ptr_time  = pwm_timing;&lt;br /&gt;
uint16_t * main_ptr_time = pwm_timing_tmp;&lt;br /&gt;
uint8_t *  isr_ptr_mask  = pwm_mask;&lt;br /&gt;
uint8_t *  main_ptr_mask = pwm_mask_tmp;&lt;br /&gt;
&lt;br /&gt;
// Zeiger austauschen&lt;br /&gt;
// das muss in einem Unterprogramm erfolgen,&lt;br /&gt;
// um eine Zwischenspeicherung durch den Compiler zu verhindern&lt;br /&gt;
&lt;br /&gt;
void tausche_zeiger(void) {&lt;br /&gt;
    uint16_t * tmp_ptr16;&lt;br /&gt;
    uint8_t * tmp_ptr8;&lt;br /&gt;
&lt;br /&gt;
    tmp_ptr16 = isr_ptr_time;&lt;br /&gt;
    isr_ptr_time = main_ptr_time;&lt;br /&gt;
    main_ptr_time = tmp_ptr16;&lt;br /&gt;
    tmp_ptr8 = isr_ptr_mask;&lt;br /&gt;
    isr_ptr_mask = main_ptr_mask;&lt;br /&gt;
    main_ptr_mask = tmp_ptr8;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// PWM Update, berechnet aus den PWM Einstellungen&lt;br /&gt;
// die neuen Werte für die Interruptroutine&lt;br /&gt;
&lt;br /&gt;
void pwm_update(void) {&lt;br /&gt;
    uint8_t i, j, k, min;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    // PWM Maske für Start berechnen&lt;br /&gt;
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren&lt;br /&gt;
&lt;br /&gt;
    tmp=0;&lt;br /&gt;
    j = 1;&lt;br /&gt;
    for(i=1; i&amp;lt;=(PWM_CHANNELS); i++) {&lt;br /&gt;
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge&lt;br /&gt;
        pwm_setting_tmp[i] = pwm_setting[i-1];&lt;br /&gt;
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start&lt;br /&gt;
        j &amp;lt;&amp;lt;= 1;&lt;br /&gt;
    }&lt;br /&gt;
    main_ptr_mask[0]=tmp;                           // PWM Start Daten &lt;br /&gt;
&lt;br /&gt;
    // PWM settings sortieren; Einfügesortieren&lt;br /&gt;
&lt;br /&gt;
    for(i=1; i&amp;lt;=PWM_CHANNELS; i++) {&lt;br /&gt;
        min=255;&lt;br /&gt;
        k=i;&lt;br /&gt;
        for(j=i; j&amp;lt;=PWM_CHANNELS; j++) {&lt;br /&gt;
            if (pwm_setting_tmp[j]&amp;lt;min) {&lt;br /&gt;
                k=j;                                // Index und PWM-setting merken&lt;br /&gt;
                min = pwm_setting_tmp[j];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (k!=i) {&lt;br /&gt;
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen&lt;br /&gt;
            tmp = pwm_setting_tmp[k];&lt;br /&gt;
            pwm_setting_tmp[k] = pwm_setting_tmp[i];&lt;br /&gt;
            pwm_setting_tmp[i] = tmp;&lt;br /&gt;
            tmp = main_ptr_mask[k];&lt;br /&gt;
            main_ptr_mask[k] = main_ptr_mask[i];&lt;br /&gt;
            main_ptr_mask[i] = tmp;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden&lt;br /&gt;
&lt;br /&gt;
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze&lt;br /&gt;
    i=1;                        // Startindex&lt;br /&gt;
&lt;br /&gt;
    while(k&amp;gt;i) {&lt;br /&gt;
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  &amp;amp;&amp;amp; (k&amp;gt;i) ) {&lt;br /&gt;
&lt;br /&gt;
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden&lt;br /&gt;
            // oder PWM Wert ist Null&lt;br /&gt;
            if (pwm_setting_tmp[i]!=0)&lt;br /&gt;
                main_ptr_mask[i+1] &amp;amp;= main_ptr_mask[i];        // Masken vereinigen&lt;br /&gt;
&lt;br /&gt;
            // Datensatz entfernen,&lt;br /&gt;
            // Nachfolger alle eine Stufe hochschieben&lt;br /&gt;
            for(j=i; j&amp;lt;k; j++) {&lt;br /&gt;
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];&lt;br /&gt;
                main_ptr_mask[j] = main_ptr_mask[j+1];&lt;br /&gt;
            }&lt;br /&gt;
            k--;&lt;br /&gt;
        }&lt;br /&gt;
        i++;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // letzten Datensatz extra behandeln&lt;br /&gt;
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen&lt;br /&gt;
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
    if (pwm_setting_tmp[i]==0) k--;&lt;br /&gt;
&lt;br /&gt;
    // Zeitdifferenzen berechnen&lt;br /&gt;
    &lt;br /&gt;
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        k=1;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        i=k;&lt;br /&gt;
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);&lt;br /&gt;
        j=pwm_setting_tmp[i];&lt;br /&gt;
        i--;&lt;br /&gt;
        for (; i&amp;gt;0; i--) {&lt;br /&gt;
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);&lt;br /&gt;
            j=pwm_setting_tmp[i];&lt;br /&gt;
        }&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*j;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // auf Sync warten&lt;br /&gt;
&lt;br /&gt;
    pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
    while(pwm_sync==0);&lt;br /&gt;
&lt;br /&gt;
    // Zeiger tauschen&lt;br /&gt;
    cli();&lt;br /&gt;
    tausche_zeiger();&lt;br /&gt;
    pwm_cnt_max = k;&lt;br /&gt;
    sei();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += isr_ptr_time[pwm_cnt];&lt;br /&gt;
    tmp    = isr_ptr_mask[pwm_cnt];&lt;br /&gt;
    &lt;br /&gt;
    if (pwm_cnt == 0) {&lt;br /&gt;
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        PWM_PORT &amp;amp;= tmp;                        // Ports löschen&lt;br /&gt;
        if (pwm_cnt == pwm_cnt_max) {&lt;br /&gt;
            pwm_sync = 1;                       // Update jetzt möglich&lt;br /&gt;
            pwm_cnt  = 0;&lt;br /&gt;
        }&lt;br /&gt;
        else pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM Port einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablen Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 2;             // Timer läuft mit Prescaler 8&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
// nur zum testen, in der Anwendung entfernen&lt;br /&gt;
/*&lt;br /&gt;
// Test values&lt;br /&gt;
volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={255, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={9, 1, 1, 1, 1, 1, 1, 1};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
const uint8_t t7[8]={0, 0, 0, 0, 0, 0, 0, 88};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =1;&lt;br /&gt;
    tmp =2;&lt;br /&gt;
    tmp =3;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t7, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
*/&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
&lt;br /&gt;
    while(1);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Programm ist schon um einiges länger (968 Byte). Die Interruptroutine benötigt maximal 111 Takte und wird zwischen 2 bis 9 mal pro PWM-Zyklus aufgerufen. Zweimal, wenn alle PWM-Einstellungen gleich sind, 9 mal, wenn alle PWM-Einstellungen verschieden sind. Damit werden zwischen 222 bis 999 Takte benötigt, pro &#039;&#039;&#039;PWM-Zyklus&#039;&#039;&#039;, nicht pro PWM-Takt! Das entspricht einer &#039;&#039;&#039;CPU-Belastung von 0,3..1,2%&#039;&#039;&#039;! [http://de.wikipedia.org/wiki/Beifall Standing Ovations]! Die Funktion pwm_update() benötigt ca. 1500 bis 1800 Takte, das ist geringfügig abhängig von den PWM-Einstellungen, je nach dem ob die Daten schon sortiert sind und ob PWM-Werte mehrfach vorkommen. Bei einer Updaterate von 100 Hz (mehr ist physikalisch sinnlos) entspricht das einer CPU-Belastung von 2,3%, praktisch wird es wahrscheinlich weniger sein. Taktet man den AVR mit vollen 16 MHz halbiert sich die CPU-Belastung noch einmal. Beachtet werden sollte hier die Datenübergabe von der Funktion pwm_update() zur Interruptroutine. Hier werden jeweils zwei Zeiger verwendet, um auf Arrays zu zeigen. In zwei Arrays werden durch die Funktion die Berechnungen der neuen Daten vorgenommen. In den beiden anderen Arrays stehen die aktuellen Daten, mit welchen die ISR arbeitet. Um am Ende der Berechung ein relativ aufwändiges Kopieren der Daten zu vermeiden werden einfach die Zeiger vertauscht. Das ist wesentlich schneller als das Kopieren der Arrays! Im englischen spricht man hier von double buffering, also doppelter Pufferung. Dieses Prinzip wird oft angewendet. Würde man allerdings einfach am Ende die Zeiger tauschen käme es zu einem Crash! Der Interrupt kann jederzeit aktiv werden. Wenn dann die Zeiger nur halb kopiert sind greift die Interruptroutine auf zerstückelte Daten zu und macht Müll. Ebenso würde es zu Fehlfunktionen kommen, wenn während es PWM-Zyklus neue Daten in die Arrays kopiert werden. Das muß verhindert werden. Und zwar dadurch, daß über eine Variable eine Synchronisation durchgeführt wird. Diese wird am Ende des PWM-Zyklus gesetzt und signalisiert, daß neue Daten für den nächsten Zyklus kopiert werden können. Deshalb muss die Funktion pwm_update ggf. bis zu 1 vollen PWM-Zyklus warten, bis die Zeiger getauscht werden können. Wichtig ist dabei, daß die Variable pwm_sync, welche sowohl in der Funktion als auch im Interrupt geschrieben wird, als &#039;&#039;&#039;volatile&#039;&#039;&#039; deklariert wird. Denn sonst würde die Sequenz&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
while(pwm_sync==0);&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
zum Stehenbleiben der CPU führen, weil der Compiler erkennt, daß die Variable nie ungleich Null sein kann und damit die Schleife endlos ausgeführt wird. Der Compiler kann prinzipbedingt nicht automatisch erkennen, daß die Variable im Interrupt auf 1 gesetzt wird.&lt;br /&gt;
&lt;br /&gt;
Bei dem schon recht hohen Prozessortakt von 8MHz und der relativ niedrigen PWM Frequenz von 100 Hz haben wir allerdings ein kleines Problem. Wenn beispielsweise nur ein Kanal den PWM-Wert 10 hat, alle anderen aber den Wert Null, dann passiert folgendes. Zum Begin eines PWM-Zyklus wird der eine Kanal aktiviert. Jetzt wird per Timer für 10xT_PWM = 3120 Takte gewartet. Jetzt wird dieser Kanal wieder gelöscht. Bis zum Begin des nächsten PWM-Zyklus muss jedoch noch (256-10)*T_PWM = 76752 Takte gewartet werden. Doch diese Zahl passt nicht mehr in eine 16 Bit Variable! Und damit kann sie auch nicht mit dem Timer verwendet werden. Der Ausweg heisst Vorteiler (engl. Prescaler). Damit kann der Timer langsamer getaktet werden und somit wird die gleiche Wartezeit mit weniger Timertakten erzielt. Zu beachten ist, dass die Einstellung im #define PWM_PRESCALER mit der realen Einstellung in TCCR1B übereinstimmen muss.&lt;br /&gt;
&lt;br /&gt;
Eine Einschränkung gilt allerdings für alle Soft-PWMs. Die PWM-Frequenz muss niedrig genug sein, damit sich die Interrupts nicht überschneiden. D.h. der Wert T_PWM muß immer größer sein als die Anzahl Takte der Interruptroutine. Das wird im Quelltext mit Hilfe von #if  . . #endif geprüft. Die +5 Takte sind eine Reserve. Dazu muß aber die Optimierung -Os eingeschaltet sein, sonst stimmen die Zahlen nicht!&lt;br /&gt;
&lt;br /&gt;
== Zusammenfassung ==&lt;br /&gt;
&lt;br /&gt;
Durch kritische Analyse ist es möglich, eine Software-PWM drastisch zu verbessern und die CPU-Belastung auf ein verschwindend geringes Maß zu reduzieren. Zudem zeigt dieses Beispiel ein oft vorkommendes Muster auf, welches gemeinhin als &#039;Time for Space&#039; (Zeit für Platz) bezeichnet wird. Man meint damit, dass es oft möglich ist, dramatische Einsparungen in der Laufzeit zu erreichen, wenn man gewillt ist dafür Speicherplatz zu opfern.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Version  || Programmspeicher [Byte] || CPU-Belastung [%] &lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 1 || 284   || 49&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 2 || 324   || 30&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 3 || 968   || 0,3..1,2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Siehe auch ==&lt;br /&gt;
&lt;br /&gt;
* [[AVR-Tutorial: PWM]]&lt;br /&gt;
* [[AVR-GCC-Tutorial#PWM (Pulsweitenmodulation)|AVR-GCC-Tutorial: PWM]]&lt;br /&gt;
* [[LED-Fading]] - LED dimmen mit PWM&lt;br /&gt;
&lt;br /&gt;
[[Category:AVR]]&lt;/div&gt;</summary>
		<author><name>91.19.247.16</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47584</id>
		<title>Soft-PWM</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47584"/>
		<updated>2010-05-16T20:28:54Z</updated>

		<summary type="html">&lt;p&gt;91.19.247.16: /* Zusammenfassung */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einleitung ==&lt;br /&gt;
[[PWM]] ist eine oft verwendete Funktion auf dem Gebiet der Mikrocontroller. Damit lassen sich vielfältige Aufgaben lösen, wie beispielsweise die Leistungssteuerung von Motoren, Helligkeitssteuerung von [[LED-Fading | LEDs]], [[DA-Wandler | Digital-Analog Wandlung]] und vieles mehr. Die meisten Mikrocontroller haben ein oder mehrere PWM-Module eingebaut, womit ohne CPU-Belastung eine PWM generiert werden kann. Jedoch kommt es bisweilen vor, daß die Anzahl der verfügbaren PWM-Kanäle nicht ausreicht. Dann muß eine Softwarelösung gefunden werden, bei der die CPU die PWM-Generierung vornimmt, allgemein Soft-PWM genannt.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Achtung:&#039;&#039;&#039; Wenn man für eine PWM aussschliesslich 8 Bit breite Datentypen verwendet, dann steht für den Parameter für die Pulsbreite nur der Bereich 0..255 zur Verfügung. Da bei einer vollständigen PWM mit N Schritten aber N+1 mögliche Fälle auftreten können (0/N bis N/0), ist mit dem hier gezeigten Code eine solche PWM nur für N &amp;lt;= 255 realisierbar.&lt;br /&gt;
&lt;br /&gt;
== Einfacher Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Ein sehr einfacher Lösungsansatz findet sich im [[AVR-Tutorial: PWM]]. Hier wird schon ein [[AVR-Tutorial: Timer | Timer]] benutzt, um in regelmäßigen Abständen die PWM-Generierung durchzuführen. Damit verbleibt noch Rechenzeit für andere Aufgaben, außerdem wird die Programmierung wesentlich vereinfacht. Allerdings ist das Beispiel in ASM, hier soll das ganze in [[C]] gemacht werden.&lt;br /&gt;
&lt;br /&gt;
Es soll nun eine 8 Bit PWM mit 100 Hz (PWM-Zyklus 10ms) und acht Kanälen generiert werden. Der verwendete Controller ist ein [[AVR]] vom Typ ATmega32, welcher mit dem internen RC-Oszillator auf 8 MHz getaktet wird. Das Programm kann jedoch problemlos an so ziemlich jeden anderen AVR angepaßt werden. Die Programme wurden mit dem Optimierungsgrad -Os compiliert.&lt;br /&gt;
&lt;br /&gt;
=== Erster Versuch ===&lt;br /&gt;
&lt;br /&gt;
Das Programm ist recht kurz und übersichtlich. Im Hauptprogramm wird der Timer 1 initialisiert und der Output Compare 1A als variabler Timer verwendet, wobei die Output Compare Funktion nicht mit dem IO-Pin verbunden ist. Im Interrupt, welcher regelmäßig aufgerufen wird, werden nun in einer Schleife alle acht Kanäle geprüft. Alle Kanäle werden auf HIGH gesetzt, welche eine PWM-Einstellung größer als der aktuelle Zykluszähler haben. Sinnvollerweise werden erst alle Kanäle geprüft und das Ergebnis zwischengespeichert, am Ende erfolgt nur ein Zugriff auf den Port.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit einfachem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 255                   // PWM-Schritte pro Zyklus(1..255)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(152+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if PWM_STEPS &amp;gt; 255&lt;br /&gt;
    #error PWM_STEPS zu gross&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0, i=0, j=1;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
&lt;br /&gt;
    for (; i&amp;lt;8; i++) {    &lt;br /&gt;
    	if (pwm_setting[i] &amp;gt; pwm_cnt) tmp |= j;&lt;br /&gt;
            j&amp;lt;&amp;lt;=1;&lt;br /&gt;
	}&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Im [[AVR-Studio]] kann man den Code simulieren. Wichtig ist hier vor allem die Ausführungszeit des Interrupts. Bei 100 Hz PWM-Frequenz und 256 Schritten pro PWM-Zyklus wird diese Funktion immerhin 25600 mal pro Sekunde aufgerufen (PWM-Takt 25,6 kHz),  bei 8MHz Taktfrequenz stehen damit maximal 312 Takte zur Verfügung. Glücklicherweise ist die Funktion relativ kurz und der GCC leistet gute Arbeit. Der Interrupt benötigt hier 152 Takte, es verbleiben also jeweils 160 Takte zur Bearbeitung anderer Aufgaben. Das entspricht einer CPU-Belastung von ~49%. Das Programm benötigt 284 Byte Programmspeicher. Nicht schlecht für den Anfang.&lt;br /&gt;
&lt;br /&gt;
=== Zweiter Versuch ===&lt;br /&gt;
&lt;br /&gt;
Wo gibt es in diesem Programm noch Optimierungsmöglichkeiten? Nur im Interrupt, denn das ganze Programm besteht ja praktisch nur aus der Interruptroutine. Betrachten wir die Schleifen genauer müssen wir feststellen, daß die Indizierung von pwm_setting[] etwas Rechenzeit benötigt. Ebenso die Schiebeoperation von tmp, auch wenn das nur acht mal ein Takt ist. Wir können jetzt per Hand das machen, was der Compiler auch manchmal macht. Die Rede ist vom Loop-Unrolling. Dabei wird die Schleife durch mehrere diskrete Befehle ersetzt (entrollt). Der Vorteil dabei ist, daß die Befehle zur Berechnung und Prüfung der Zählvariable entfallen, außerdem können ggf. Werte im Voraus berechnet werden. Als Ergebnis hat man zwar ein etwas größeres Programm, doch das wird schneller ausgeführt! Ausserdem orientiert sich diese Version mehr am Original der Assemblerversion. Dadurch wird sie zusätzlich ein wenig kürzer und schneller.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit verbessertem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 256                   // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(93+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
        &lt;br /&gt;
    if (pwm_setting[0] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;0);&lt;br /&gt;
    if (pwm_setting[1] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;1);&lt;br /&gt;
    if (pwm_setting[2] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;2);&lt;br /&gt;
    if (pwm_setting[3] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;3);&lt;br /&gt;
    if (pwm_setting[4] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;4);&lt;br /&gt;
    if (pwm_setting[5] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;5);&lt;br /&gt;
    if (pwm_setting[6] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;6);&lt;br /&gt;
    if (pwm_setting[7] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;7);&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mit dieser Interruptroutine werden nur noch 93 Takte benötigt, die CPU-Belastung verringert sich auf  ~30%. Nicht schlecht. Der Programmcode steigt auf 324 Byte, aber das ist im Angesicht der Leistungsverbesserung zu verschmerzen. Weiter verringern kann man die CPU-Belastung durch eine niedrigere PWM-Frequenz oder eine geringere Anzahl Stufen der PWM. Wenn man beispielsweise mit 64 (6 Bit) statt 256 (8 Bit) Stufen auskommt verringert sich die Belastung um den Faktor 4.&lt;br /&gt;
&lt;br /&gt;
== Intelligenter Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Wenn auch eine CPU-Last von 30% recht akzeptabel erscheint, so hat man doch noch irgendwie das Gefühl, daß es noch besser geht. Aber wie? Mein Mathematikprofessor pflegte in so einem Fall die Anwendung der „Methode des scharfen Blicks“ ™. Was passiert eigentlich während eines gesamten PWM-Zykluses?&lt;br /&gt;
&lt;br /&gt;
*Zu Beginn werden alle IO-Pins gesetzt, deren PWM-Einstellung nicht Null ist.&lt;br /&gt;
*Die jeweiligen IO-Pins werden gelöscht, wenn der PWM-Zähler mit der PWM-Einstellung übereinstimmt&lt;br /&gt;
&lt;br /&gt;
Ja klar, aber da wir nur acht Kanäle haben, gibt es maximal 8 Zeitpunkte, an denen ein Pin gelöscht werden muss. Wenn mehrere Kanäle die gleiche PWM-Einstellung haben sind es sogar noch weniger. Alle anderen Interrupts verursachen keinerlei Änderung der IO-Pins und verbrauchen eigentlich nur sinnlos Rechenzeit. Ein Skandal!&lt;br /&gt;
&lt;br /&gt;
Was ist also zu tun? Wir wissen nun, daß es maximal 9 Ereignisse pro PWM-Zyklus gibt. Was ist damit zu machen?&lt;br /&gt;
&lt;br /&gt;
*Die PWM-Kanäle müssen in aufsteigender Folge sortiert werden.&lt;br /&gt;
*Wenn mehrere PWM-Kanäle den gleichen PWM-Wert haben müssen sie zusammengefaßt werden&lt;br /&gt;
*Die Zeitdifferenzen zwischen den einzelnen Ereignissen müssen berechnet werden&lt;br /&gt;
&lt;br /&gt;
Das ist eigentlich schon alles. Praktisch ist das mit einigen Kniffligkeiten verbunden, aber die sind lösbar. Am Ende steht eine Interruptroutine, welche maximal 9 mal pro PWM-Zyklus aufgerufen wird und die jeweiligen IO-Pins setzt bzw. löscht. Eine normale Funktion wird benutzt, um die PWM-Einstellungen der acht Kanäle in die notwendigen Informationen für die Interruptroutine umzuwandeln. Das hat unter anderem den Vorteil, daß nur dann zusätzlich Rechenzeit benötigt wird, wenn sich die PWM-Einstellungen ändern.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit intelligentem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU         8000000L           // Systemtakt in Hz&lt;br /&gt;
#define F_PWM         100L               // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_PRESCALER 8                  // Vorteiler für den Timer&lt;br /&gt;
#define PWM_STEPS     256                // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT      PORTB              // Port für PWM&lt;br /&gt;
#define PWM_DDR       DDRB               // Datenrichtungsregister für PWM&lt;br /&gt;
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
//#define T_PWM 1   //TEST&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_PRESCALER)&amp;lt;(111+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_STEPS)&amp;gt;65535)&lt;br /&gt;
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   &lt;br /&gt;
#endif&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte&lt;br /&gt;
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      &lt;br /&gt;
uint8_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen&lt;br /&gt;
uint8_t  pwm_mask_tmp[PWM_CHANNELS+1];        &lt;br /&gt;
uint8_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
uint8_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!&lt;br /&gt;
volatile uint8_t pwm_sync;                    // Update jetzt möglich&lt;br /&gt;
&lt;br /&gt;
// Pointer für wechselseitigen Datenzugriff&lt;br /&gt;
&lt;br /&gt;
uint16_t * isr_ptr_time  = pwm_timing;&lt;br /&gt;
uint16_t * main_ptr_time = pwm_timing_tmp;&lt;br /&gt;
uint8_t *  isr_ptr_mask  = pwm_mask;&lt;br /&gt;
uint8_t *  main_ptr_mask = pwm_mask_tmp;&lt;br /&gt;
&lt;br /&gt;
// Zeiger austauschen&lt;br /&gt;
// das muss in einem Unterprogramm erfolgen,&lt;br /&gt;
// um eine Zwischenspeicherung durch den Compiler zu verhindern&lt;br /&gt;
&lt;br /&gt;
void tausche_zeiger(void) {&lt;br /&gt;
    uint16_t * tmp_ptr16;&lt;br /&gt;
    uint8_t * tmp_ptr8;&lt;br /&gt;
&lt;br /&gt;
    tmp_ptr16 = isr_ptr_time;&lt;br /&gt;
    isr_ptr_time = main_ptr_time;&lt;br /&gt;
    main_ptr_time = tmp_ptr16;&lt;br /&gt;
    tmp_ptr8 = isr_ptr_mask;&lt;br /&gt;
    isr_ptr_mask = main_ptr_mask;&lt;br /&gt;
    main_ptr_mask = tmp_ptr8;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// PWM Update, berechnet aus den PWM Einstellungen&lt;br /&gt;
// die neuen Werte für die Interruptroutine&lt;br /&gt;
&lt;br /&gt;
void pwm_update(void) {&lt;br /&gt;
    uint8_t i, j, k, min;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    // PWM Maske für Start berechnen&lt;br /&gt;
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren&lt;br /&gt;
&lt;br /&gt;
    tmp=0;&lt;br /&gt;
    j = 1;&lt;br /&gt;
    for(i=1; i&amp;lt;=(PWM_CHANNELS); i++) {&lt;br /&gt;
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge&lt;br /&gt;
        pwm_setting_tmp[i] = pwm_setting[i-1];&lt;br /&gt;
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start&lt;br /&gt;
        j &amp;lt;&amp;lt;= 1;&lt;br /&gt;
    }&lt;br /&gt;
    main_ptr_mask[0]=tmp;                           // PWM Start Daten &lt;br /&gt;
&lt;br /&gt;
    // PWM settings sortieren; Einfügesortieren&lt;br /&gt;
&lt;br /&gt;
    for(i=1; i&amp;lt;=PWM_CHANNELS; i++) {&lt;br /&gt;
        min=255;&lt;br /&gt;
        k=i;&lt;br /&gt;
        for(j=i; j&amp;lt;=PWM_CHANNELS; j++) {&lt;br /&gt;
            if (pwm_setting_tmp[j]&amp;lt;min) {&lt;br /&gt;
                k=j;                                // Index und PWM-setting merken&lt;br /&gt;
                min = pwm_setting_tmp[j];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (k!=i) {&lt;br /&gt;
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen&lt;br /&gt;
            tmp = pwm_setting_tmp[k];&lt;br /&gt;
            pwm_setting_tmp[k] = pwm_setting_tmp[i];&lt;br /&gt;
            pwm_setting_tmp[i] = tmp;&lt;br /&gt;
            tmp = main_ptr_mask[k];&lt;br /&gt;
            main_ptr_mask[k] = main_ptr_mask[i];&lt;br /&gt;
            main_ptr_mask[i] = tmp;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden&lt;br /&gt;
&lt;br /&gt;
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze&lt;br /&gt;
    i=1;                        // Startindex&lt;br /&gt;
&lt;br /&gt;
    while(k&amp;gt;i) {&lt;br /&gt;
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  &amp;amp;&amp;amp; (k&amp;gt;i) ) {&lt;br /&gt;
&lt;br /&gt;
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden&lt;br /&gt;
            // oder PWM Wert ist Null&lt;br /&gt;
            if (pwm_setting_tmp[i]!=0)&lt;br /&gt;
                main_ptr_mask[i+1] &amp;amp;= main_ptr_mask[i];        // Masken vereinigen&lt;br /&gt;
&lt;br /&gt;
            // Datensatz entfernen,&lt;br /&gt;
            // Nachfolger alle eine Stufe hochschieben&lt;br /&gt;
            for(j=i; j&amp;lt;k; j++) {&lt;br /&gt;
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];&lt;br /&gt;
                main_ptr_mask[j] = main_ptr_mask[j+1];&lt;br /&gt;
            }&lt;br /&gt;
            k--;&lt;br /&gt;
        }&lt;br /&gt;
        i++;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // letzten Datensatz extra behandeln&lt;br /&gt;
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen&lt;br /&gt;
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
    if (pwm_setting_tmp[i]==0) k--;&lt;br /&gt;
&lt;br /&gt;
    // Zeitdifferenzen berechnen&lt;br /&gt;
    &lt;br /&gt;
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        k=1;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        i=k;&lt;br /&gt;
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);&lt;br /&gt;
        j=pwm_setting_tmp[i];&lt;br /&gt;
        i--;&lt;br /&gt;
        for (; i&amp;gt;0; i--) {&lt;br /&gt;
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);&lt;br /&gt;
            j=pwm_setting_tmp[i];&lt;br /&gt;
        }&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*j;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // auf Sync warten&lt;br /&gt;
&lt;br /&gt;
    pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
    while(pwm_sync==0);&lt;br /&gt;
&lt;br /&gt;
    // Zeiger tauschen&lt;br /&gt;
    cli();&lt;br /&gt;
    tausche_zeiger();&lt;br /&gt;
    pwm_cnt_max = k;&lt;br /&gt;
    sei();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += isr_ptr_time[pwm_cnt];&lt;br /&gt;
    tmp    = isr_ptr_mask[pwm_cnt];&lt;br /&gt;
    &lt;br /&gt;
    if (pwm_cnt == 0) {&lt;br /&gt;
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        PWM_PORT &amp;amp;= tmp;                        // Ports löschen&lt;br /&gt;
        if (pwm_cnt == pwm_cnt_max) {&lt;br /&gt;
            pwm_sync = 1;                       // Update jetzt möglich&lt;br /&gt;
            pwm_cnt  = 0;&lt;br /&gt;
        }&lt;br /&gt;
        else pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM Port einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablen Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 2;             // Timer läuft mit Prescaler 8&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
// nur zum testen, in der Anwendung entfernen&lt;br /&gt;
/*&lt;br /&gt;
// Test values&lt;br /&gt;
volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={255, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={9, 1, 1, 1, 1, 1, 1, 1};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
const uint8_t t7[8]={0, 0, 0, 0, 0, 0, 0, 88};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =1;&lt;br /&gt;
    tmp =2;&lt;br /&gt;
    tmp =3;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t7, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
*/&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
&lt;br /&gt;
    while(1);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Programm ist schon um einiges länger (968 Byte). Die Interruptroutine benötigt maximal 111 Takte und wird zwischen 2 bis 9 mal pro PWM-Zyklus aufgerufen. Zweimal, wenn alle PWM-Einstellungen gleich sind, 9 mal, wenn alle PWM-Einstellungen verschieden sind. Damit werden zwischen 222 bis 999 Takte benötigt, pro &#039;&#039;&#039;PWM-Zyklus&#039;&#039;&#039;, nicht pro PWM-Takt! Das entspricht einer &#039;&#039;&#039;CPU-Belastung von 0,3..1,2%&#039;&#039;&#039;! [http://de.wikipedia.org/wiki/Beifall Standing Ovations]! Die Funktion pwm_update() benötigt ca. 1500 bis 1800 Takte, das ist geringfügig abhängig von den PWM-Einstellungen, je nach dem ob die Daten schon sortiert sind und ob PWM-Werte mehrfach vorkommen. Bei einer Updaterate von 100 Hz (mehr ist physikalisch sinnlos) entspricht das einer CPU-Belastung von 2,3%, praktisch wird es wahrscheinlich weniger sein. Taktet man den AVR mit vollen 16 MHz halbiert sich die CPU-Belastung noch einmal. Beachtet werden sollte hier die Datenübergabe von der Funktion pwm_update() zur Interruptroutine. Hier werden jeweils zwei Zeiger verwendet, um auf Arrays zu zeigen. In zwei Arrays werden durch die Funktion die Berechnungen der neuen Daten vorgenommen. In den beiden anderen Arrays stehen die aktuellen Daten, mit welchen die ISR arbeitet. Um am Ende der Berechung ein relativ aufwändiges Kopieren der Daten zu vermeiden werden einfach die Zeiger vertauscht. Das ist wesentlich schneller als das Kopieren der Arrays! Im englischen spricht man hier von double buffering, also doppelter Pufferung. Dieses Prinzip wird oft angewendet. Würde man allerdings einfach am Ende die Zeiger tauschen käme es zu einem Crash! Der Interrupt kann jederzeit aktiv werden. Wenn dann die Zeiger nur halb kopiert sind greift die Interruptroutine auf zerstückelte Daten zu und macht Müll. Ebenso würde es zu Fehlfunktionen kommen, wenn während es PWM-Zyklus neue Daten in die Arrays kopiert werden. Das muß verhindert werden. Und zwar dadurch, daß über eine Variable eine Synchronisation durchgeführt wird. Diese wird am Ende des PWM-Zyklus gesetzt und signalisiert, daß neue Daten für den nächsten Zyklus kopiert werden können. Deshalb muss die Funktion pwm_update ggf. bis zu 1 vollen PWM-Zyklus warten, bis die Zeiger getauscht werden können. Wichtig ist dabei, daß die Variable pwm_sync, welche sowohl in der Funktion als auch im Interrupt geschrieben wird, als &#039;&#039;&#039;volatile&#039;&#039;&#039; deklariert wird. Denn sonst würde die Sequenz&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
while(pwm_sync==0);&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
zum Stehenbleiben der CPU führen, weil der Compiler erkennt, daß die Variable nie ungleich Null sein kann und damit die Schleife endlos ausgeführt wird. Der Compiler kann prinzipbedingt nicht automatisch erkennen, daß die Variable im Interrupt auf 1 gesetzt wird.&lt;br /&gt;
&lt;br /&gt;
Bei dem schon recht hohen Prozessortakt von 8MHz und der relativ niedrigen PWM Frequenz von 100 Hz haben wir allerdings ein kleines Problem. Wenn beispielsweise nur ein Kanal den PWM-Wert 10 hat, alle anderen aber den Wert Null, dann passiert folgendes. Zum Begin eines PWM-Zyklus wird der eine Kanal aktiviert. Jetzt wird per Timer für 10xT_PWM = 3120 Takte gewartet. Jetzt wird dieser Kanal wieder gelöscht. Bis zum Begin des nächsten PWM-Zyklus muss jedoch noch (256-10)*T_PWM = 76752 Takte gewartet werden. Doch diese Zahl passt nicht mehr in eine 16 Bit Variable! Und damit kann sie auch nicht mit dem Timer verwendet werden. Der Ausweg heisst Vorteiler (engl. Prescaler). Damit kann der Timer langsamer getaktet werden und somit wird die gleiche Wartezeit mit weniger Timertakten erzielt. Zu beachten ist, dass die Einstellung im #define PWM_PRESCALER mit der realen Einstellung in TCCR1B übereinstimmen muss.&lt;br /&gt;
&lt;br /&gt;
Eine Einschränkung gilt allerdings für alle Soft-PWMs. Die PWM-Frequenz muss niedrig genug sein, damit sich die Interrupts nicht überschneiden. D.h. der Wert T_PWM muß immer größer sein als die Anzahl Takte der Interruptroutine. Das wird im Quelltext mit Hilfe von #if  . . #endif geprüft. Die +5 Takte sind eine Reserve. Dazu muß aber die Optimierung -Os eingeschaltet sein, sonst stimmen die Zahlen nicht!&lt;br /&gt;
&lt;br /&gt;
== Zusammenfassung ==&lt;br /&gt;
&lt;br /&gt;
Durch kritische Analyse ist es möglich, eine Software-PWM drastisch zu verbessern und die CPU-Belastung auf ein verschwindend geringes Maß zu reduzieren. Zudem zeigt dieses Beispiel ein oft vorkommendes Muster auf, welches gemeinhin als &#039;Time for Space&#039; (Zeit für Platz) bezeichnet wird. Man meint damit, dass es oft möglich ist, dramatische Einsparungen in der Laufzeit zu erreichen, wenn man gewillt ist dafür Speicherplatz zu opfern.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Version  || Programmspeicher [Byte] || CPU-Belastung [%] &lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 1 || 284   || 49&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 2 || 324   || 30&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 3 || 968   || 0,3..1,2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Siehe auch ==&lt;br /&gt;
&lt;br /&gt;
* [[AVR-Tutorial: PWM]]&lt;br /&gt;
* [[AVR-GCC-Tutorial#PWM (Pulsweitenmodulation)|AVR-GCC-Tutorial: PWM]]&lt;br /&gt;
* [[LED-Fading]] - LED dimmen mit PWM&lt;/div&gt;</summary>
		<author><name>91.19.247.16</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47583</id>
		<title>Soft-PWM</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47583"/>
		<updated>2010-05-16T20:26:18Z</updated>

		<summary type="html">&lt;p&gt;91.19.247.16: /* Siehe auch */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einleitung ==&lt;br /&gt;
[[PWM]] ist eine oft verwendete Funktion auf dem Gebiet der Mikrocontroller. Damit lassen sich vielfältige Aufgaben lösen, wie beispielsweise die Leistungssteuerung von Motoren, Helligkeitssteuerung von [[LED-Fading | LEDs]], [[DA-Wandler | Digital-Analog Wandlung]] und vieles mehr. Die meisten Mikrocontroller haben ein oder mehrere PWM-Module eingebaut, womit ohne CPU-Belastung eine PWM generiert werden kann. Jedoch kommt es bisweilen vor, daß die Anzahl der verfügbaren PWM-Kanäle nicht ausreicht. Dann muß eine Softwarelösung gefunden werden, bei der die CPU die PWM-Generierung vornimmt, allgemein Soft-PWM genannt.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Achtung:&#039;&#039;&#039; Wenn man für eine PWM aussschliesslich 8 Bit breite Datentypen verwendet, dann steht für den Parameter für die Pulsbreite nur der Bereich 0..255 zur Verfügung. Da bei einer vollständigen PWM mit N Schritten aber N+1 mögliche Fälle auftreten können (0/N bis N/0), ist mit dem hier gezeigten Code eine solche PWM nur für N &amp;lt;= 255 realisierbar.&lt;br /&gt;
&lt;br /&gt;
== Einfacher Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Ein sehr einfacher Lösungsansatz findet sich im [[AVR-Tutorial: PWM]]. Hier wird schon ein [[AVR-Tutorial: Timer | Timer]] benutzt, um in regelmäßigen Abständen die PWM-Generierung durchzuführen. Damit verbleibt noch Rechenzeit für andere Aufgaben, außerdem wird die Programmierung wesentlich vereinfacht. Allerdings ist das Beispiel in ASM, hier soll das ganze in [[C]] gemacht werden.&lt;br /&gt;
&lt;br /&gt;
Es soll nun eine 8 Bit PWM mit 100 Hz (PWM-Zyklus 10ms) und acht Kanälen generiert werden. Der verwendete Controller ist ein [[AVR]] vom Typ ATmega32, welcher mit dem internen RC-Oszillator auf 8 MHz getaktet wird. Das Programm kann jedoch problemlos an so ziemlich jeden anderen AVR angepaßt werden. Die Programme wurden mit dem Optimierungsgrad -Os compiliert.&lt;br /&gt;
&lt;br /&gt;
=== Erster Versuch ===&lt;br /&gt;
&lt;br /&gt;
Das Programm ist recht kurz und übersichtlich. Im Hauptprogramm wird der Timer 1 initialisiert und der Output Compare 1A als variabler Timer verwendet, wobei die Output Compare Funktion nicht mit dem IO-Pin verbunden ist. Im Interrupt, welcher regelmäßig aufgerufen wird, werden nun in einer Schleife alle acht Kanäle geprüft. Alle Kanäle werden auf HIGH gesetzt, welche eine PWM-Einstellung größer als der aktuelle Zykluszähler haben. Sinnvollerweise werden erst alle Kanäle geprüft und das Ergebnis zwischengespeichert, am Ende erfolgt nur ein Zugriff auf den Port.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit einfachem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 255                   // PWM-Schritte pro Zyklus(1..255)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(152+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if PWM_STEPS &amp;gt; 255&lt;br /&gt;
    #error PWM_STEPS zu gross&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0, i=0, j=1;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
&lt;br /&gt;
    for (; i&amp;lt;8; i++) {    &lt;br /&gt;
    	if (pwm_setting[i] &amp;gt; pwm_cnt) tmp |= j;&lt;br /&gt;
            j&amp;lt;&amp;lt;=1;&lt;br /&gt;
	}&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Im [[AVR-Studio]] kann man den Code simulieren. Wichtig ist hier vor allem die Ausführungszeit des Interrupts. Bei 100 Hz PWM-Frequenz und 256 Schritten pro PWM-Zyklus wird diese Funktion immerhin 25600 mal pro Sekunde aufgerufen (PWM-Takt 25,6 kHz),  bei 8MHz Taktfrequenz stehen damit maximal 312 Takte zur Verfügung. Glücklicherweise ist die Funktion relativ kurz und der GCC leistet gute Arbeit. Der Interrupt benötigt hier 152 Takte, es verbleiben also jeweils 160 Takte zur Bearbeitung anderer Aufgaben. Das entspricht einer CPU-Belastung von ~49%. Das Programm benötigt 284 Byte Programmspeicher. Nicht schlecht für den Anfang.&lt;br /&gt;
&lt;br /&gt;
=== Zweiter Versuch ===&lt;br /&gt;
&lt;br /&gt;
Wo gibt es in diesem Programm noch Optimierungsmöglichkeiten? Nur im Interrupt, denn das ganze Programm besteht ja praktisch nur aus der Interruptroutine. Betrachten wir die Schleifen genauer müssen wir feststellen, daß die Indizierung von pwm_setting[] etwas Rechenzeit benötigt. Ebenso die Schiebeoperation von tmp, auch wenn das nur acht mal ein Takt ist. Wir können jetzt per Hand das machen, was der Compiler auch manchmal macht. Die Rede ist vom Loop-Unrolling. Dabei wird die Schleife durch mehrere diskrete Befehle ersetzt (entrollt). Der Vorteil dabei ist, daß die Befehle zur Berechnung und Prüfung der Zählvariable entfallen, außerdem können ggf. Werte im Voraus berechnet werden. Als Ergebnis hat man zwar ein etwas größeres Programm, doch das wird schneller ausgeführt! Ausserdem orientiert sich diese Version mehr am Original der Assemblerversion. Dadurch wird sie zusätzlich ein wenig kürzer und schneller.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit verbessertem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 256                   // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(93+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
        &lt;br /&gt;
    if (pwm_setting[0] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;0);&lt;br /&gt;
    if (pwm_setting[1] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;1);&lt;br /&gt;
    if (pwm_setting[2] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;2);&lt;br /&gt;
    if (pwm_setting[3] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;3);&lt;br /&gt;
    if (pwm_setting[4] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;4);&lt;br /&gt;
    if (pwm_setting[5] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;5);&lt;br /&gt;
    if (pwm_setting[6] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;6);&lt;br /&gt;
    if (pwm_setting[7] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;7);&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mit dieser Interruptroutine werden nur noch 93 Takte benötigt, die CPU-Belastung verringert sich auf  ~30%. Nicht schlecht. Der Programmcode steigt auf 324 Byte, aber das ist im Angesicht der Leistungsverbesserung zu verschmerzen. Weiter verringern kann man die CPU-Belastung durch eine niedrigere PWM-Frequenz oder eine geringere Anzahl Stufen der PWM. Wenn man beispielsweise mit 64 (6 Bit) statt 256 (8 Bit) Stufen auskommt verringert sich die Belastung um den Faktor 4.&lt;br /&gt;
&lt;br /&gt;
== Intelligenter Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Wenn auch eine CPU-Last von 30% recht akzeptabel erscheint, so hat man doch noch irgendwie das Gefühl, daß es noch besser geht. Aber wie? Mein Mathematikprofessor pflegte in so einem Fall die Anwendung der „Methode des scharfen Blicks“ ™. Was passiert eigentlich während eines gesamten PWM-Zykluses?&lt;br /&gt;
&lt;br /&gt;
*Zu Beginn werden alle IO-Pins gesetzt, deren PWM-Einstellung nicht Null ist.&lt;br /&gt;
*Die jeweiligen IO-Pins werden gelöscht, wenn der PWM-Zähler mit der PWM-Einstellung übereinstimmt&lt;br /&gt;
&lt;br /&gt;
Ja klar, aber da wir nur acht Kanäle haben, gibt es maximal 8 Zeitpunkte, an denen ein Pin gelöscht werden muss. Wenn mehrere Kanäle die gleiche PWM-Einstellung haben sind es sogar noch weniger. Alle anderen Interrupts verursachen keinerlei Änderung der IO-Pins und verbrauchen eigentlich nur sinnlos Rechenzeit. Ein Skandal!&lt;br /&gt;
&lt;br /&gt;
Was ist also zu tun? Wir wissen nun, daß es maximal 9 Ereignisse pro PWM-Zyklus gibt. Was ist damit zu machen?&lt;br /&gt;
&lt;br /&gt;
*Die PWM-Kanäle müssen in aufsteigender Folge sortiert werden.&lt;br /&gt;
*Wenn mehrere PWM-Kanäle den gleichen PWM-Wert haben müssen sie zusammengefaßt werden&lt;br /&gt;
*Die Zeitdifferenzen zwischen den einzelnen Ereignissen müssen berechnet werden&lt;br /&gt;
&lt;br /&gt;
Das ist eigentlich schon alles. Praktisch ist das mit einigen Kniffligkeiten verbunden, aber die sind lösbar. Am Ende steht eine Interruptroutine, welche maximal 9 mal pro PWM-Zyklus aufgerufen wird und die jeweiligen IO-Pins setzt bzw. löscht. Eine normale Funktion wird benutzt, um die PWM-Einstellungen der acht Kanäle in die notwendigen Informationen für die Interruptroutine umzuwandeln. Das hat unter anderem den Vorteil, daß nur dann zusätzlich Rechenzeit benötigt wird, wenn sich die PWM-Einstellungen ändern.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit intelligentem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU         8000000L           // Systemtakt in Hz&lt;br /&gt;
#define F_PWM         100L               // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_PRESCALER 8                  // Vorteiler für den Timer&lt;br /&gt;
#define PWM_STEPS     256                // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT      PORTB              // Port für PWM&lt;br /&gt;
#define PWM_DDR       DDRB               // Datenrichtungsregister für PWM&lt;br /&gt;
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
//#define T_PWM 1   //TEST&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_PRESCALER)&amp;lt;(111+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_STEPS)&amp;gt;65535)&lt;br /&gt;
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   &lt;br /&gt;
#endif&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte&lt;br /&gt;
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      &lt;br /&gt;
uint8_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen&lt;br /&gt;
uint8_t  pwm_mask_tmp[PWM_CHANNELS+1];        &lt;br /&gt;
uint8_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
uint8_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!&lt;br /&gt;
volatile uint8_t pwm_sync;                    // Update jetzt möglich&lt;br /&gt;
&lt;br /&gt;
// Pointer für wechselseitigen Datenzugriff&lt;br /&gt;
&lt;br /&gt;
uint16_t * isr_ptr_time  = pwm_timing;&lt;br /&gt;
uint16_t * main_ptr_time = pwm_timing_tmp;&lt;br /&gt;
uint8_t *  isr_ptr_mask  = pwm_mask;&lt;br /&gt;
uint8_t *  main_ptr_mask = pwm_mask_tmp;&lt;br /&gt;
&lt;br /&gt;
// Zeiger austauschen&lt;br /&gt;
// das muss in einem Unterprogramm erfolgen,&lt;br /&gt;
// um eine Zwischenspeicherung durch den Compiler zu verhindern&lt;br /&gt;
&lt;br /&gt;
void tausche_zeiger(void) {&lt;br /&gt;
    uint16_t * tmp_ptr16;&lt;br /&gt;
    uint8_t * tmp_ptr8;&lt;br /&gt;
&lt;br /&gt;
    tmp_ptr16 = isr_ptr_time;&lt;br /&gt;
    isr_ptr_time = main_ptr_time;&lt;br /&gt;
    main_ptr_time = tmp_ptr16;&lt;br /&gt;
    tmp_ptr8 = isr_ptr_mask;&lt;br /&gt;
    isr_ptr_mask = main_ptr_mask;&lt;br /&gt;
    main_ptr_mask = tmp_ptr8;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// PWM Update, berechnet aus den PWM Einstellungen&lt;br /&gt;
// die neuen Werte für die Interruptroutine&lt;br /&gt;
&lt;br /&gt;
void pwm_update(void) {&lt;br /&gt;
    uint8_t i, j, k, min;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    // PWM Maske für Start berechnen&lt;br /&gt;
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren&lt;br /&gt;
&lt;br /&gt;
    tmp=0;&lt;br /&gt;
    j = 1;&lt;br /&gt;
    for(i=1; i&amp;lt;=(PWM_CHANNELS); i++) {&lt;br /&gt;
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge&lt;br /&gt;
        pwm_setting_tmp[i] = pwm_setting[i-1];&lt;br /&gt;
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start&lt;br /&gt;
        j &amp;lt;&amp;lt;= 1;&lt;br /&gt;
    }&lt;br /&gt;
    main_ptr_mask[0]=tmp;                           // PWM Start Daten &lt;br /&gt;
&lt;br /&gt;
    // PWM settings sortieren; Einfügesortieren&lt;br /&gt;
&lt;br /&gt;
    for(i=1; i&amp;lt;=PWM_CHANNELS; i++) {&lt;br /&gt;
        min=255;&lt;br /&gt;
        k=i;&lt;br /&gt;
        for(j=i; j&amp;lt;=PWM_CHANNELS; j++) {&lt;br /&gt;
            if (pwm_setting_tmp[j]&amp;lt;min) {&lt;br /&gt;
                k=j;                                // Index und PWM-setting merken&lt;br /&gt;
                min = pwm_setting_tmp[j];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (k!=i) {&lt;br /&gt;
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen&lt;br /&gt;
            tmp = pwm_setting_tmp[k];&lt;br /&gt;
            pwm_setting_tmp[k] = pwm_setting_tmp[i];&lt;br /&gt;
            pwm_setting_tmp[i] = tmp;&lt;br /&gt;
            tmp = main_ptr_mask[k];&lt;br /&gt;
            main_ptr_mask[k] = main_ptr_mask[i];&lt;br /&gt;
            main_ptr_mask[i] = tmp;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden&lt;br /&gt;
&lt;br /&gt;
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze&lt;br /&gt;
    i=1;                        // Startindex&lt;br /&gt;
&lt;br /&gt;
    while(k&amp;gt;i) {&lt;br /&gt;
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  &amp;amp;&amp;amp; (k&amp;gt;i) ) {&lt;br /&gt;
&lt;br /&gt;
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden&lt;br /&gt;
            // oder PWM Wert ist Null&lt;br /&gt;
            if (pwm_setting_tmp[i]!=0)&lt;br /&gt;
                main_ptr_mask[i+1] &amp;amp;= main_ptr_mask[i];        // Masken vereinigen&lt;br /&gt;
&lt;br /&gt;
            // Datensatz entfernen,&lt;br /&gt;
            // Nachfolger alle eine Stufe hochschieben&lt;br /&gt;
            for(j=i; j&amp;lt;k; j++) {&lt;br /&gt;
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];&lt;br /&gt;
                main_ptr_mask[j] = main_ptr_mask[j+1];&lt;br /&gt;
            }&lt;br /&gt;
            k--;&lt;br /&gt;
        }&lt;br /&gt;
        i++;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // letzten Datensatz extra behandeln&lt;br /&gt;
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen&lt;br /&gt;
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
    if (pwm_setting_tmp[i]==0) k--;&lt;br /&gt;
&lt;br /&gt;
    // Zeitdifferenzen berechnen&lt;br /&gt;
    &lt;br /&gt;
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        k=1;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        i=k;&lt;br /&gt;
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);&lt;br /&gt;
        j=pwm_setting_tmp[i];&lt;br /&gt;
        i--;&lt;br /&gt;
        for (; i&amp;gt;0; i--) {&lt;br /&gt;
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);&lt;br /&gt;
            j=pwm_setting_tmp[i];&lt;br /&gt;
        }&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*j;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // auf Sync warten&lt;br /&gt;
&lt;br /&gt;
    pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
    while(pwm_sync==0);&lt;br /&gt;
&lt;br /&gt;
    // Zeiger tauschen&lt;br /&gt;
    cli();&lt;br /&gt;
    tausche_zeiger();&lt;br /&gt;
    pwm_cnt_max = k;&lt;br /&gt;
    sei();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += isr_ptr_time[pwm_cnt];&lt;br /&gt;
    tmp    = isr_ptr_mask[pwm_cnt];&lt;br /&gt;
    &lt;br /&gt;
    if (pwm_cnt == 0) {&lt;br /&gt;
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        PWM_PORT &amp;amp;= tmp;                        // Ports löschen&lt;br /&gt;
        if (pwm_cnt == pwm_cnt_max) {&lt;br /&gt;
            pwm_sync = 1;                       // Update jetzt möglich&lt;br /&gt;
            pwm_cnt  = 0;&lt;br /&gt;
        }&lt;br /&gt;
        else pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM Port einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablen Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 2;             // Timer läuft mit Prescaler 8&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
// nur zum testen, in der Anwendung entfernen&lt;br /&gt;
/*&lt;br /&gt;
// Test values&lt;br /&gt;
volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={255, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={9, 1, 1, 1, 1, 1, 1, 1};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
const uint8_t t7[8]={0, 0, 0, 0, 0, 0, 0, 88};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =1;&lt;br /&gt;
    tmp =2;&lt;br /&gt;
    tmp =3;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t7, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
*/&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
&lt;br /&gt;
    while(1);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Programm ist schon um einiges länger (968 Byte). Die Interruptroutine benötigt maximal 111 Takte und wird zwischen 2 bis 9 mal pro PWM-Zyklus aufgerufen. Zweimal, wenn alle PWM-Einstellungen gleich sind, 9 mal, wenn alle PWM-Einstellungen verschieden sind. Damit werden zwischen 222 bis 999 Takte benötigt, pro &#039;&#039;&#039;PWM-Zyklus&#039;&#039;&#039;, nicht pro PWM-Takt! Das entspricht einer &#039;&#039;&#039;CPU-Belastung von 0,3..1,2%&#039;&#039;&#039;! [http://de.wikipedia.org/wiki/Beifall Standing Ovations]! Die Funktion pwm_update() benötigt ca. 1500 bis 1800 Takte, das ist geringfügig abhängig von den PWM-Einstellungen, je nach dem ob die Daten schon sortiert sind und ob PWM-Werte mehrfach vorkommen. Bei einer Updaterate von 100 Hz (mehr ist physikalisch sinnlos) entspricht das einer CPU-Belastung von 2,3%, praktisch wird es wahrscheinlich weniger sein. Taktet man den AVR mit vollen 16 MHz halbiert sich die CPU-Belastung noch einmal. Beachtet werden sollte hier die Datenübergabe von der Funktion pwm_update() zur Interruptroutine. Hier werden jeweils zwei Zeiger verwendet, um auf Arrays zu zeigen. In zwei Arrays werden durch die Funktion die Berechnungen der neuen Daten vorgenommen. In den beiden anderen Arrays stehen die aktuellen Daten, mit welchen die ISR arbeitet. Um am Ende der Berechung ein relativ aufwändiges Kopieren der Daten zu vermeiden werden einfach die Zeiger vertauscht. Das ist wesentlich schneller als das Kopieren der Arrays! Im englischen spricht man hier von double buffering, also doppelter Pufferung. Dieses Prinzip wird oft angewendet. Würde man allerdings einfach am Ende die Zeiger tauschen käme es zu einem Crash! Der Interrupt kann jederzeit aktiv werden. Wenn dann die Zeiger nur halb kopiert sind greift die Interruptroutine auf zerstückelte Daten zu und macht Müll. Ebenso würde es zu Fehlfunktionen kommen, wenn während es PWM-Zyklus neue Daten in die Arrays kopiert werden. Das muß verhindert werden. Und zwar dadurch, daß über eine Variable eine Synchronisation durchgeführt wird. Diese wird am Ende des PWM-Zyklus gesetzt und signalisiert, daß neue Daten für den nächsten Zyklus kopiert werden können. Deshalb muss die Funktion pwm_update ggf. bis zu 1 vollen PWM-Zyklus warten, bis die Zeiger getauscht werden können. Wichtig ist dabei, daß die Variable pwm_sync, welche sowohl in der Funktion als auch im Interrupt geschrieben wird, als &#039;&#039;&#039;volatile&#039;&#039;&#039; deklariert wird. Denn sonst würde die Sequenz&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
while(pwm_sync==0);&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
zum Stehenbleiben der CPU führen, weil der Compiler erkennt, daß die Variable nie ungleich Null sein kann und damit die Schleife endlos ausgeführt wird. Der Compiler kann prinzipbedingt nicht automatisch erkennen, daß die Variable im Interrupt auf 1 gesetzt wird.&lt;br /&gt;
&lt;br /&gt;
Bei dem schon recht hohen Prozessortakt von 8MHz und der relativ niedrigen PWM Frequenz von 100 Hz haben wir allerdings ein kleines Problem. Wenn beispielsweise nur ein Kanal den PWM-Wert 10 hat, alle anderen aber den Wert Null, dann passiert folgendes. Zum Begin eines PWM-Zyklus wird der eine Kanal aktiviert. Jetzt wird per Timer für 10xT_PWM = 3120 Takte gewartet. Jetzt wird dieser Kanal wieder gelöscht. Bis zum Begin des nächsten PWM-Zyklus muss jedoch noch (256-10)*T_PWM = 76752 Takte gewartet werden. Doch diese Zahl passt nicht mehr in eine 16 Bit Variable! Und damit kann sie auch nicht mit dem Timer verwendet werden. Der Ausweg heisst Vorteiler (engl. Prescaler). Damit kann der Timer langsamer getaktet werden und somit wird die gleiche Wartezeit mit weniger Timertakten erzielt. Zu beachten ist, dass die Einstellung im #define PWM_PRESCALER mit der realen Einstellung in TCCR1B übereinstimmen muss.&lt;br /&gt;
&lt;br /&gt;
Eine Einschränkung gilt allerdings für alle Soft-PWMs. Die PWM-Frequenz muss niedrig genug sein, damit sich die Interrupts nicht überschneiden. D.h. der Wert T_PWM muß immer größer sein als die Anzahl Takte der Interruptroutine. Das wird im Quelltext mit Hilfe von #if  . . #endif geprüft. Die +5 Takte sind eine Reserve. Dazu muß aber die Optimierung -Os eingeschaltet sein, sonst stimmen die Zahlen nicht!&lt;br /&gt;
&lt;br /&gt;
== Zusammenfassung ==&lt;br /&gt;
&lt;br /&gt;
Durch kritische Analyse ist es möglich, eine Software-PWM drastisch zu verbessern und die CPU-Belastung auf ein verschwindend geringes Maß zu reduzieren. Zudem zeigt dieses Beispiel ein oft vorkommendes Muster auf, welches gemeinhin als &#039;Time for Space&#039; (Zeit für Platz) bezeichnet wird. Man meint damit, dass es oft möglich ist, dramatische Einsparungen in der Laufzeit zu erreichen, wenn man gewillt ist dafür Speicherplatz zu opfern.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Version  || Programmspeicher [Byte] || CPU-Belastung [%] &lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 1 || 284   || 49&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 2 || 324   || 30&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 3 || 968   || 0,3..1,2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
== Siehe auch ==&lt;br /&gt;
&lt;br /&gt;
* [[AVR-Tutorial: PWM]]&lt;br /&gt;
* [[AVR-GCC-Tutorial#PWM (Pulsweitenmodulation)|AVR-GCC-Tutorial: PWM]]&lt;br /&gt;
* [[LED-Fading]] - LED dimmen mit PWM&lt;/div&gt;</summary>
		<author><name>91.19.247.16</name></author>
	</entry>
	<entry>
		<id>https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47582</id>
		<title>Soft-PWM</title>
		<link rel="alternate" type="text/html" href="https://www.mikrocontroller.net/index.php?title=Soft-PWM&amp;diff=47582"/>
		<updated>2010-05-16T20:25:53Z</updated>

		<summary type="html">&lt;p&gt;91.19.247.16: /* Zusammenfassung */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Einleitung ==&lt;br /&gt;
[[PWM]] ist eine oft verwendete Funktion auf dem Gebiet der Mikrocontroller. Damit lassen sich vielfältige Aufgaben lösen, wie beispielsweise die Leistungssteuerung von Motoren, Helligkeitssteuerung von [[LED-Fading | LEDs]], [[DA-Wandler | Digital-Analog Wandlung]] und vieles mehr. Die meisten Mikrocontroller haben ein oder mehrere PWM-Module eingebaut, womit ohne CPU-Belastung eine PWM generiert werden kann. Jedoch kommt es bisweilen vor, daß die Anzahl der verfügbaren PWM-Kanäle nicht ausreicht. Dann muß eine Softwarelösung gefunden werden, bei der die CPU die PWM-Generierung vornimmt, allgemein Soft-PWM genannt.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Achtung:&#039;&#039;&#039; Wenn man für eine PWM aussschliesslich 8 Bit breite Datentypen verwendet, dann steht für den Parameter für die Pulsbreite nur der Bereich 0..255 zur Verfügung. Da bei einer vollständigen PWM mit N Schritten aber N+1 mögliche Fälle auftreten können (0/N bis N/0), ist mit dem hier gezeigten Code eine solche PWM nur für N &amp;lt;= 255 realisierbar.&lt;br /&gt;
&lt;br /&gt;
== Einfacher Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Ein sehr einfacher Lösungsansatz findet sich im [[AVR-Tutorial: PWM]]. Hier wird schon ein [[AVR-Tutorial: Timer | Timer]] benutzt, um in regelmäßigen Abständen die PWM-Generierung durchzuführen. Damit verbleibt noch Rechenzeit für andere Aufgaben, außerdem wird die Programmierung wesentlich vereinfacht. Allerdings ist das Beispiel in ASM, hier soll das ganze in [[C]] gemacht werden.&lt;br /&gt;
&lt;br /&gt;
Es soll nun eine 8 Bit PWM mit 100 Hz (PWM-Zyklus 10ms) und acht Kanälen generiert werden. Der verwendete Controller ist ein [[AVR]] vom Typ ATmega32, welcher mit dem internen RC-Oszillator auf 8 MHz getaktet wird. Das Programm kann jedoch problemlos an so ziemlich jeden anderen AVR angepaßt werden. Die Programme wurden mit dem Optimierungsgrad -Os compiliert.&lt;br /&gt;
&lt;br /&gt;
=== Erster Versuch ===&lt;br /&gt;
&lt;br /&gt;
Das Programm ist recht kurz und übersichtlich. Im Hauptprogramm wird der Timer 1 initialisiert und der Output Compare 1A als variabler Timer verwendet, wobei die Output Compare Funktion nicht mit dem IO-Pin verbunden ist. Im Interrupt, welcher regelmäßig aufgerufen wird, werden nun in einer Schleife alle acht Kanäle geprüft. Alle Kanäle werden auf HIGH gesetzt, welche eine PWM-Einstellung größer als der aktuelle Zykluszähler haben. Sinnvollerweise werden erst alle Kanäle geprüft und das Ergebnis zwischengespeichert, am Ende erfolgt nur ein Zugriff auf den Port.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit einfachem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 255                   // PWM-Schritte pro Zyklus(1..255)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(152+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if PWM_STEPS &amp;gt; 255&lt;br /&gt;
    #error PWM_STEPS zu gross&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0, i=0, j=1;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
&lt;br /&gt;
    for (; i&amp;lt;8; i++) {    &lt;br /&gt;
    	if (pwm_setting[i] &amp;gt; pwm_cnt) tmp |= j;&lt;br /&gt;
            j&amp;lt;&amp;lt;=1;&lt;br /&gt;
	}&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Im [[AVR-Studio]] kann man den Code simulieren. Wichtig ist hier vor allem die Ausführungszeit des Interrupts. Bei 100 Hz PWM-Frequenz und 256 Schritten pro PWM-Zyklus wird diese Funktion immerhin 25600 mal pro Sekunde aufgerufen (PWM-Takt 25,6 kHz),  bei 8MHz Taktfrequenz stehen damit maximal 312 Takte zur Verfügung. Glücklicherweise ist die Funktion relativ kurz und der GCC leistet gute Arbeit. Der Interrupt benötigt hier 152 Takte, es verbleiben also jeweils 160 Takte zur Bearbeitung anderer Aufgaben. Das entspricht einer CPU-Belastung von ~49%. Das Programm benötigt 284 Byte Programmspeicher. Nicht schlecht für den Anfang.&lt;br /&gt;
&lt;br /&gt;
=== Zweiter Versuch ===&lt;br /&gt;
&lt;br /&gt;
Wo gibt es in diesem Programm noch Optimierungsmöglichkeiten? Nur im Interrupt, denn das ganze Programm besteht ja praktisch nur aus der Interruptroutine. Betrachten wir die Schleifen genauer müssen wir feststellen, daß die Indizierung von pwm_setting[] etwas Rechenzeit benötigt. Ebenso die Schiebeoperation von tmp, auch wenn das nur acht mal ein Takt ist. Wir können jetzt per Hand das machen, was der Compiler auch manchmal macht. Die Rede ist vom Loop-Unrolling. Dabei wird die Schleife durch mehrere diskrete Befehle ersetzt (entrollt). Der Vorteil dabei ist, daß die Befehle zur Berechnung und Prüfung der Zählvariable entfallen, außerdem können ggf. Werte im Voraus berechnet werden. Als Ergebnis hat man zwar ein etwas größeres Programm, doch das wird schneller ausgeführt! Ausserdem orientiert sich diese Version mehr am Original der Assemblerversion. Dadurch wird sie zusätzlich ein wenig kürzer und schneller.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit verbessertem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU 8000000L                  // Systemtakt in Hz&lt;br /&gt;
#define F_PWM 100                       // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_STEPS 256                   // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT PORTD                  // Port für PWM&lt;br /&gt;
#define PWM_DDR DDRD                    // Datenrichtungsregister für PWM&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
&lt;br /&gt;
#if (T_PWM&amp;lt;(93+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_setting[8];                    // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt=0;&lt;br /&gt;
    uint8_t tmp=0;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += (uint16_t)T_PWM;&lt;br /&gt;
        &lt;br /&gt;
    if (pwm_setting[0] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;0);&lt;br /&gt;
    if (pwm_setting[1] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;1);&lt;br /&gt;
    if (pwm_setting[2] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;2);&lt;br /&gt;
    if (pwm_setting[3] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;3);&lt;br /&gt;
    if (pwm_setting[4] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;4);&lt;br /&gt;
    if (pwm_setting[5] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;5);&lt;br /&gt;
    if (pwm_setting[6] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;6);&lt;br /&gt;
    if (pwm_setting[7] &amp;gt; pwm_cnt) tmp |= (1&amp;lt;&amp;lt;7);&lt;br /&gt;
    PWM_PORT = tmp;                         // PWMs aktualisieren&lt;br /&gt;
    if (pwm_cnt==(uint8_t)(PWM_STEPS-1))&lt;br /&gt;
        pwm_cnt=0;&lt;br /&gt;
    else&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablem Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 1;             // Timer läuft mit vollem Systemtakt&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
// nur zum Testen, im Anwendungsfall löschen&lt;br /&gt;
&lt;br /&gt;
    volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={27, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={0, 0, 0, 0, 0, 0, 0, 9};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
    tmp =0;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
&lt;br /&gt;
/*********************************************************************/&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Mit dieser Interruptroutine werden nur noch 93 Takte benötigt, die CPU-Belastung verringert sich auf  ~30%. Nicht schlecht. Der Programmcode steigt auf 324 Byte, aber das ist im Angesicht der Leistungsverbesserung zu verschmerzen. Weiter verringern kann man die CPU-Belastung durch eine niedrigere PWM-Frequenz oder eine geringere Anzahl Stufen der PWM. Wenn man beispielsweise mit 64 (6 Bit) statt 256 (8 Bit) Stufen auskommt verringert sich die Belastung um den Faktor 4.&lt;br /&gt;
&lt;br /&gt;
== Intelligenter Lösungsansatz ==&lt;br /&gt;
&lt;br /&gt;
Wenn auch eine CPU-Last von 30% recht akzeptabel erscheint, so hat man doch noch irgendwie das Gefühl, daß es noch besser geht. Aber wie? Mein Mathematikprofessor pflegte in so einem Fall die Anwendung der „Methode des scharfen Blicks“ ™. Was passiert eigentlich während eines gesamten PWM-Zykluses?&lt;br /&gt;
&lt;br /&gt;
*Zu Beginn werden alle IO-Pins gesetzt, deren PWM-Einstellung nicht Null ist.&lt;br /&gt;
*Die jeweiligen IO-Pins werden gelöscht, wenn der PWM-Zähler mit der PWM-Einstellung übereinstimmt&lt;br /&gt;
&lt;br /&gt;
Ja klar, aber da wir nur acht Kanäle haben, gibt es maximal 8 Zeitpunkte, an denen ein Pin gelöscht werden muss. Wenn mehrere Kanäle die gleiche PWM-Einstellung haben sind es sogar noch weniger. Alle anderen Interrupts verursachen keinerlei Änderung der IO-Pins und verbrauchen eigentlich nur sinnlos Rechenzeit. Ein Skandal!&lt;br /&gt;
&lt;br /&gt;
Was ist also zu tun? Wir wissen nun, daß es maximal 9 Ereignisse pro PWM-Zyklus gibt. Was ist damit zu machen?&lt;br /&gt;
&lt;br /&gt;
*Die PWM-Kanäle müssen in aufsteigender Folge sortiert werden.&lt;br /&gt;
*Wenn mehrere PWM-Kanäle den gleichen PWM-Wert haben müssen sie zusammengefaßt werden&lt;br /&gt;
*Die Zeitdifferenzen zwischen den einzelnen Ereignissen müssen berechnet werden&lt;br /&gt;
&lt;br /&gt;
Das ist eigentlich schon alles. Praktisch ist das mit einigen Kniffligkeiten verbunden, aber die sind lösbar. Am Ende steht eine Interruptroutine, welche maximal 9 mal pro PWM-Zyklus aufgerufen wird und die jeweiligen IO-Pins setzt bzw. löscht. Eine normale Funktion wird benutzt, um die PWM-Einstellungen der acht Kanäle in die notwendigen Informationen für die Interruptroutine umzuwandeln. Das hat unter anderem den Vorteil, daß nur dann zusätzlich Rechenzeit benötigt wird, wenn sich die PWM-Einstellungen ändern.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
    Eine 8-kanalige PWM mit intelligentem Lösungsansatz&lt;br /&gt;
&lt;br /&gt;
    ATmega32 @ 8 MHz&lt;br /&gt;
&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
// Defines an den Controller und die Anwendung anpassen&lt;br /&gt;
&lt;br /&gt;
#define F_CPU         8000000L           // Systemtakt in Hz&lt;br /&gt;
#define F_PWM         100L               // PWM-Frequenz in Hz&lt;br /&gt;
#define PWM_PRESCALER 8                  // Vorteiler für den Timer&lt;br /&gt;
#define PWM_STEPS     256                // PWM-Schritte pro Zyklus(1..256)&lt;br /&gt;
#define PWM_PORT      PORTB              // Port für PWM&lt;br /&gt;
#define PWM_DDR       DDRB               // Datenrichtungsregister für PWM&lt;br /&gt;
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle&lt;br /&gt;
&lt;br /&gt;
// ab hier nichts ändern, wird alles berechnet&lt;br /&gt;
&lt;br /&gt;
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt&lt;br /&gt;
//#define T_PWM 1   //TEST&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_PRESCALER)&amp;lt;(111+5))&lt;br /&gt;
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if ((T_PWM*PWM_STEPS)&amp;gt;65535)&lt;br /&gt;
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   &lt;br /&gt;
#endif&lt;br /&gt;
// includes&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdint.h&amp;gt;&lt;br /&gt;
#include &amp;lt;string.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// globale Variablen&lt;br /&gt;
&lt;br /&gt;
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte&lt;br /&gt;
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      &lt;br /&gt;
uint8_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen&lt;br /&gt;
uint8_t  pwm_mask_tmp[PWM_CHANNELS+1];        &lt;br /&gt;
uint8_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle&lt;br /&gt;
uint8_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert&lt;br /&gt;
&lt;br /&gt;
volatile uint8_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!&lt;br /&gt;
volatile uint8_t pwm_sync;                    // Update jetzt möglich&lt;br /&gt;
&lt;br /&gt;
// Pointer für wechselseitigen Datenzugriff&lt;br /&gt;
&lt;br /&gt;
uint16_t * isr_ptr_time  = pwm_timing;&lt;br /&gt;
uint16_t * main_ptr_time = pwm_timing_tmp;&lt;br /&gt;
uint8_t *  isr_ptr_mask  = pwm_mask;&lt;br /&gt;
uint8_t *  main_ptr_mask = pwm_mask_tmp;&lt;br /&gt;
&lt;br /&gt;
// Zeiger austauschen&lt;br /&gt;
// das muss in einem Unterprogramm erfolgen,&lt;br /&gt;
// um eine Zwischenspeicherung durch den Compiler zu verhindern&lt;br /&gt;
&lt;br /&gt;
void tausche_zeiger(void) {&lt;br /&gt;
    uint16_t * tmp_ptr16;&lt;br /&gt;
    uint8_t * tmp_ptr8;&lt;br /&gt;
&lt;br /&gt;
    tmp_ptr16 = isr_ptr_time;&lt;br /&gt;
    isr_ptr_time = main_ptr_time;&lt;br /&gt;
    main_ptr_time = tmp_ptr16;&lt;br /&gt;
    tmp_ptr8 = isr_ptr_mask;&lt;br /&gt;
    isr_ptr_mask = main_ptr_mask;&lt;br /&gt;
    main_ptr_mask = tmp_ptr8;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// PWM Update, berechnet aus den PWM Einstellungen&lt;br /&gt;
// die neuen Werte für die Interruptroutine&lt;br /&gt;
&lt;br /&gt;
void pwm_update(void) {&lt;br /&gt;
    uint8_t i, j, k, min;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    // PWM Maske für Start berechnen&lt;br /&gt;
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren&lt;br /&gt;
&lt;br /&gt;
    tmp=0;&lt;br /&gt;
    j = 1;&lt;br /&gt;
    for(i=1; i&amp;lt;=(PWM_CHANNELS); i++) {&lt;br /&gt;
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge&lt;br /&gt;
        pwm_setting_tmp[i] = pwm_setting[i-1];&lt;br /&gt;
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start&lt;br /&gt;
        j &amp;lt;&amp;lt;= 1;&lt;br /&gt;
    }&lt;br /&gt;
    main_ptr_mask[0]=tmp;                           // PWM Start Daten &lt;br /&gt;
&lt;br /&gt;
    // PWM settings sortieren; Einfügesortieren&lt;br /&gt;
&lt;br /&gt;
    for(i=1; i&amp;lt;=PWM_CHANNELS; i++) {&lt;br /&gt;
        min=255;&lt;br /&gt;
        k=i;&lt;br /&gt;
        for(j=i; j&amp;lt;=PWM_CHANNELS; j++) {&lt;br /&gt;
            if (pwm_setting_tmp[j]&amp;lt;min) {&lt;br /&gt;
                k=j;                                // Index und PWM-setting merken&lt;br /&gt;
                min = pwm_setting_tmp[j];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (k!=i) {&lt;br /&gt;
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen&lt;br /&gt;
            tmp = pwm_setting_tmp[k];&lt;br /&gt;
            pwm_setting_tmp[k] = pwm_setting_tmp[i];&lt;br /&gt;
            pwm_setting_tmp[i] = tmp;&lt;br /&gt;
            tmp = main_ptr_mask[k];&lt;br /&gt;
            main_ptr_mask[k] = main_ptr_mask[i];&lt;br /&gt;
            main_ptr_mask[i] = tmp;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden&lt;br /&gt;
&lt;br /&gt;
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze&lt;br /&gt;
    i=1;                        // Startindex&lt;br /&gt;
&lt;br /&gt;
    while(k&amp;gt;i) {&lt;br /&gt;
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  &amp;amp;&amp;amp; (k&amp;gt;i) ) {&lt;br /&gt;
&lt;br /&gt;
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden&lt;br /&gt;
            // oder PWM Wert ist Null&lt;br /&gt;
            if (pwm_setting_tmp[i]!=0)&lt;br /&gt;
                main_ptr_mask[i+1] &amp;amp;= main_ptr_mask[i];        // Masken vereinigen&lt;br /&gt;
&lt;br /&gt;
            // Datensatz entfernen,&lt;br /&gt;
            // Nachfolger alle eine Stufe hochschieben&lt;br /&gt;
            for(j=i; j&amp;lt;k; j++) {&lt;br /&gt;
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];&lt;br /&gt;
                main_ptr_mask[j] = main_ptr_mask[j+1];&lt;br /&gt;
            }&lt;br /&gt;
            k--;&lt;br /&gt;
        }&lt;br /&gt;
        i++;&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    // letzten Datensatz extra behandeln&lt;br /&gt;
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen&lt;br /&gt;
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
    if (pwm_setting_tmp[i]==0) k--;&lt;br /&gt;
&lt;br /&gt;
    // Zeitdifferenzen berechnen&lt;br /&gt;
    &lt;br /&gt;
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;&lt;br /&gt;
        k=1;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        i=k;&lt;br /&gt;
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);&lt;br /&gt;
        j=pwm_setting_tmp[i];&lt;br /&gt;
        i--;&lt;br /&gt;
        for (; i&amp;gt;0; i--) {&lt;br /&gt;
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);&lt;br /&gt;
            j=pwm_setting_tmp[i];&lt;br /&gt;
        }&lt;br /&gt;
        main_ptr_time[0]=(uint16_t)T_PWM*j;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // auf Sync warten&lt;br /&gt;
&lt;br /&gt;
    pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
    while(pwm_sync==0);&lt;br /&gt;
&lt;br /&gt;
    // Zeiger tauschen&lt;br /&gt;
    cli();&lt;br /&gt;
    tausche_zeiger();&lt;br /&gt;
    pwm_cnt_max = k;&lt;br /&gt;
    sei();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Timer 1 Output COMPARE A Interrupt&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER1_COMPA_vect) {&lt;br /&gt;
    static uint8_t pwm_cnt;&lt;br /&gt;
    uint8_t tmp;&lt;br /&gt;
&lt;br /&gt;
    OCR1A += isr_ptr_time[pwm_cnt];&lt;br /&gt;
    tmp    = isr_ptr_mask[pwm_cnt];&lt;br /&gt;
    &lt;br /&gt;
    if (pwm_cnt == 0) {&lt;br /&gt;
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM&lt;br /&gt;
        pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
        PWM_PORT &amp;amp;= tmp;                        // Ports löschen&lt;br /&gt;
        if (pwm_cnt == pwm_cnt_max) {&lt;br /&gt;
            pwm_sync = 1;                       // Update jetzt möglich&lt;br /&gt;
            pwm_cnt  = 0;&lt;br /&gt;
        }&lt;br /&gt;
        else pwm_cnt++;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // PWM Port einstellen&lt;br /&gt;
    &lt;br /&gt;
    PWM_DDR = 0xFF;         // Port als Ausgang&lt;br /&gt;
    &lt;br /&gt;
    // Timer 1 OCRA1, als variablen Timer nutzen&lt;br /&gt;
&lt;br /&gt;
    TCCR1B = 2;             // Timer läuft mit Prescaler 8&lt;br /&gt;
    TIMSK |= (1&amp;lt;&amp;lt;OCIE1A);   // Interrupt freischalten&lt;br /&gt;
&lt;br /&gt;
    sei();                  // Interrupts gloabl einschalten&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
// nur zum testen, in der Anwendung entfernen&lt;br /&gt;
/*&lt;br /&gt;
// Test values&lt;br /&gt;
volatile uint8_t tmp;&lt;br /&gt;
const uint8_t t1[8]={255, 40, 3, 17, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t2[8]={27, 40, 3, 0, 150, 99, 5, 9};&lt;br /&gt;
const uint8_t t3[8]={27, 40, 3, 17, 3, 99, 3, 0};&lt;br /&gt;
const uint8_t t4[8]={0, 0, 0, 0, 0, 0, 0, 0};&lt;br /&gt;
const uint8_t t5[8]={9, 1, 1, 1, 1, 1, 1, 1};&lt;br /&gt;
const uint8_t t6[8]={33, 33, 33, 33, 33, 33, 33, 33};&lt;br /&gt;
const uint8_t t7[8]={0, 0, 0, 0, 0, 0, 0, 88};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// Messung der Interruptdauer&lt;br /&gt;
    tmp =1;&lt;br /&gt;
    tmp =2;&lt;br /&gt;
    tmp =3;&lt;br /&gt;
&lt;br /&gt;
// Debug &lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t1, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t2, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t3, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t4, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
&lt;br /&gt;
    memcpy(pwm_setting, t5, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t6, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
    &lt;br /&gt;
    memcpy(pwm_setting, t7, 8);&lt;br /&gt;
    pwm_update();&lt;br /&gt;
*/&lt;br /&gt;
/******************************************************************/&lt;br /&gt;
&lt;br /&gt;
    while(1);&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Das Programm ist schon um einiges länger (968 Byte). Die Interruptroutine benötigt maximal 111 Takte und wird zwischen 2 bis 9 mal pro PWM-Zyklus aufgerufen. Zweimal, wenn alle PWM-Einstellungen gleich sind, 9 mal, wenn alle PWM-Einstellungen verschieden sind. Damit werden zwischen 222 bis 999 Takte benötigt, pro &#039;&#039;&#039;PWM-Zyklus&#039;&#039;&#039;, nicht pro PWM-Takt! Das entspricht einer &#039;&#039;&#039;CPU-Belastung von 0,3..1,2%&#039;&#039;&#039;! [http://de.wikipedia.org/wiki/Beifall Standing Ovations]! Die Funktion pwm_update() benötigt ca. 1500 bis 1800 Takte, das ist geringfügig abhängig von den PWM-Einstellungen, je nach dem ob die Daten schon sortiert sind und ob PWM-Werte mehrfach vorkommen. Bei einer Updaterate von 100 Hz (mehr ist physikalisch sinnlos) entspricht das einer CPU-Belastung von 2,3%, praktisch wird es wahrscheinlich weniger sein. Taktet man den AVR mit vollen 16 MHz halbiert sich die CPU-Belastung noch einmal. Beachtet werden sollte hier die Datenübergabe von der Funktion pwm_update() zur Interruptroutine. Hier werden jeweils zwei Zeiger verwendet, um auf Arrays zu zeigen. In zwei Arrays werden durch die Funktion die Berechnungen der neuen Daten vorgenommen. In den beiden anderen Arrays stehen die aktuellen Daten, mit welchen die ISR arbeitet. Um am Ende der Berechung ein relativ aufwändiges Kopieren der Daten zu vermeiden werden einfach die Zeiger vertauscht. Das ist wesentlich schneller als das Kopieren der Arrays! Im englischen spricht man hier von double buffering, also doppelter Pufferung. Dieses Prinzip wird oft angewendet. Würde man allerdings einfach am Ende die Zeiger tauschen käme es zu einem Crash! Der Interrupt kann jederzeit aktiv werden. Wenn dann die Zeiger nur halb kopiert sind greift die Interruptroutine auf zerstückelte Daten zu und macht Müll. Ebenso würde es zu Fehlfunktionen kommen, wenn während es PWM-Zyklus neue Daten in die Arrays kopiert werden. Das muß verhindert werden. Und zwar dadurch, daß über eine Variable eine Synchronisation durchgeführt wird. Diese wird am Ende des PWM-Zyklus gesetzt und signalisiert, daß neue Daten für den nächsten Zyklus kopiert werden können. Deshalb muss die Funktion pwm_update ggf. bis zu 1 vollen PWM-Zyklus warten, bis die Zeiger getauscht werden können. Wichtig ist dabei, daß die Variable pwm_sync, welche sowohl in der Funktion als auch im Interrupt geschrieben wird, als &#039;&#039;&#039;volatile&#039;&#039;&#039; deklariert wird. Denn sonst würde die Sequenz&lt;br /&gt;
&lt;br /&gt;
&amp;lt;c&amp;gt;&lt;br /&gt;
pwm_sync=0;             // Sync wird im Interrupt gesetzt&lt;br /&gt;
while(pwm_sync==0);&lt;br /&gt;
&amp;lt;/c&amp;gt;&lt;br /&gt;
&lt;br /&gt;
zum Stehenbleiben der CPU führen, weil der Compiler erkennt, daß die Variable nie ungleich Null sein kann und damit die Schleife endlos ausgeführt wird. Der Compiler kann prinzipbedingt nicht automatisch erkennen, daß die Variable im Interrupt auf 1 gesetzt wird.&lt;br /&gt;
&lt;br /&gt;
Bei dem schon recht hohen Prozessortakt von 8MHz und der relativ niedrigen PWM Frequenz von 100 Hz haben wir allerdings ein kleines Problem. Wenn beispielsweise nur ein Kanal den PWM-Wert 10 hat, alle anderen aber den Wert Null, dann passiert folgendes. Zum Begin eines PWM-Zyklus wird der eine Kanal aktiviert. Jetzt wird per Timer für 10xT_PWM = 3120 Takte gewartet. Jetzt wird dieser Kanal wieder gelöscht. Bis zum Begin des nächsten PWM-Zyklus muss jedoch noch (256-10)*T_PWM = 76752 Takte gewartet werden. Doch diese Zahl passt nicht mehr in eine 16 Bit Variable! Und damit kann sie auch nicht mit dem Timer verwendet werden. Der Ausweg heisst Vorteiler (engl. Prescaler). Damit kann der Timer langsamer getaktet werden und somit wird die gleiche Wartezeit mit weniger Timertakten erzielt. Zu beachten ist, dass die Einstellung im #define PWM_PRESCALER mit der realen Einstellung in TCCR1B übereinstimmen muss.&lt;br /&gt;
&lt;br /&gt;
Eine Einschränkung gilt allerdings für alle Soft-PWMs. Die PWM-Frequenz muss niedrig genug sein, damit sich die Interrupts nicht überschneiden. D.h. der Wert T_PWM muß immer größer sein als die Anzahl Takte der Interruptroutine. Das wird im Quelltext mit Hilfe von #if  . . #endif geprüft. Die +5 Takte sind eine Reserve. Dazu muß aber die Optimierung -Os eingeschaltet sein, sonst stimmen die Zahlen nicht!&lt;br /&gt;
&lt;br /&gt;
== Zusammenfassung ==&lt;br /&gt;
&lt;br /&gt;
Durch kritische Analyse ist es möglich, eine Software-PWM drastisch zu verbessern und die CPU-Belastung auf ein verschwindend geringes Maß zu reduzieren. Zudem zeigt dieses Beispiel ein oft vorkommendes Muster auf, welches gemeinhin als &#039;Time for Space&#039; (Zeit für Platz) bezeichnet wird. Man meint damit, dass es oft möglich ist, dramatische Einsparungen in der Laufzeit zu erreichen, wenn man gewillt ist dafür Speicherplatz zu opfern.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| Version  || Programmspeicher [Byte] || CPU-Belastung [%] &lt;br /&gt;
|- align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 1 || 284   || 49&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 2 || 324   || 30&lt;br /&gt;
|-  align=&amp;quot;center&amp;quot;&lt;br /&gt;
| 3 || 968   || 0,3..1,2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:AVR]]&lt;br /&gt;
&lt;br /&gt;
== Siehe auch ==&lt;br /&gt;
&lt;br /&gt;
* [[AVR-Tutorial: PWM]]&lt;br /&gt;
* [[AVR-GCC-Tutorial#PWM (Pulsweitenmodulation)|AVR-GCC-Tutorial: PWM]]&lt;br /&gt;
* [[Soft-PWM]] - PWM in Software&lt;br /&gt;
* [[LED-Fading]] - LED dimmen mit PWM&lt;/div&gt;</summary>
		<author><name>91.19.247.16</name></author>
	</entry>
</feed>