Forum: Mikrocontroller und Digitale Elektronik Software PWM = 1000 % CPU-Last?


von Marcel H. (multiholle)


Lesenswert?

Ich habe endlich geschafft anhand der vielen Tutorials ein Software PWM 
für mein Atmega168 zu programmieren.

Leider wird die ISR, welche für das PWM-Signal zuständig ist, durch 
einen Timer in jedem Takt aufgerufen. Damit wäre die CPU voll 
ausgelastet.

20.000.000 Hz  1000 PWM-Länge  60 FPS / 256 ? = 1,...

Woher stammt eigentlich die 256 bei der Berechnung der Taktzyklen pro 
Timerdurchlauf? Wie kann ich die CPU-Last senken?
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
#include <avr/pgmspace.h>
5
#include <inttypes.h>
6
#include "speaker.h"
7
#include "leds.h"
8
9
// led intensity
10
volatile uint16_t intensity;
11
// logarithmic intensity table
12
const uint16_t pwm_table[64] PROGMEM = 
13
    {0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 
14
   15, 17, 19, 21, 23, 26, 29, 32, 36, 40, 44, 49, 55, 61, 68, 76, 85, 94, 105, 
15
   117, 131, 146, 162, 181, 202, 225, 250, 279, 311, 346, 386, 430, 479, 534, 
16
     595, 663, 739, 824, 918, 1023};
17
18
// interrupt service routine
19
ISR(TIMER0_OVF_vect) {
20
    static uint16_t pwm;
21
22
    // increase pwm counter
23
    pwm++;
24
    // pwm = pwm mod 1024
25
    if (pwm == 1024) {
26
        pwm = 0;
27
        leds_set(0b00001111);
28
    }
29
    // turn lights off
30
    if (pwm > intensity) {
31
        leds_set(0b00000000);
32
    }
33
}
34
35
// main
36
int main (void) {
37
    // init components
38
    leds_init();
39
    speaker_init();
40
41
    
42
    // set timer prescaler to 1/1
43
    TCCR0B |= (1 << CS00);
44
    // enable timer overflow interrupt
45
    TIMSK0 |= (1<<TOIE0);
46
    // enable global interrupts
47
    sei();
48
49
    while (1) {
50
        uint8_t i;
51
        // fade leds up
52
        for (i = 0; i < 64; i++) {
53
            intensity = pgm_read_word(pwm_table + i);
54
            _delay_ms(100);
55
        }
56
57
        // fade leds down
58
        for (i = 0; i < 64; i++) {
59
            intensity = pgm_read_word(pwm_table + 63 - i);
60
            _delay_ms(100);
61
        }
62
    }
63
}

von Simon B. (nomis)


Lesenswert?

Also, zunächst: Die 256 kommt vermutlich daher, dass Du den 
Overflow-Interrupt verwendest, der also aufgerufen wird sobald der 
8-bit-Zähler bis 256 gezählt hat. Es ist mitnichten so, dass der 
Overflow-Interrupt bei jedem Takt stattfindet...

Allerdings würde ich das hier ein bischen anders angehen, es ist nicht 
nötig, für jeden PWM-Schritt den Interrupt auszulösen.

In http://www.mikrocontroller.net/articles/Soft-PWM findest Du 
geschicktere Methoden.

Viele Grüße,
        Simon

von Marcel H. (multiholle)


Lesenswert?

Ich versuche gerade die Funktion der PWM Funktion auf dieser Seite 
http://www.mikrocontroller.net/articles/Soft-PWM nachzuvollziehen. Ich 
habe mir jetzt einen kleinen Teil herausgenommen und wollte diesen auf 
meinem Atmega 168 zum laufen bringen. Leider leuchtet keine der LEDs am 
Ausgang.

Die LEDs sollen nach dem Bitmuser von pwm_mask nacheinander aktiviert 
werden. Zwischen den einzelnen Mustern werden die Pausen aus pwm_timing 
eingehalten.

Hier mein Code. "led_set" aktiviert 8 LEDs mit Hilfe eines übergebenen 
Bitmusters.
1
/*** INCLUDE ******************************************************************/
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <inttypes.h>
5
#include "leds.h"
6
7
/*** DEFINE *******************************************************************/
8
#define F_CPU         20000000L
9
#define F_PWM         100L
10
#define PWM_PRESCALER 8
11
#define PWM_CHANNELS  8
12
#define PWM_STEPS     256
13
#define T_PWM         (F_CPU / (PWM_PRESCALER * F_PWM * PWM_STEPS))
14
15
/** VARS/CONSTS ***************************************************************/
16
volatile uint8_t pwm_count = 3;
17
volatile uint16_t pwm_timing[] = {2 * T_PWM, 230 * T_PWM, 23 * T_PWM};
18
volatile uint8_t pwm_mask[] = {0b00001010, 0b00001000, 0b00000000};
19
20
/** FUNCTIONS *****************************************************************/
21
ISR(TIMER0_COMPA_vect) {
22
  static uint8_t pwm = 0;
23
  OCR1A +=  pwm_timing[pwm];
24
  leds_set(pwm_mask[pwm]);
25
  pwm++;
26
  if (pwm == 3)
27
    pwm = 0;
28
}
29
30
int main (void) {
31
  leds_init();
32
33
  TCCR1B |= (1 << CS01);
34
  TIMSK1 |= (1 << OCIE1A);
35
  sei();
36
37
  while (1);
38
}

von Karl H. (kbuchegg)


Lesenswert?

Marcel Holle schrieb:
> Ich versuche gerade die Funktion der PWM Funktion auf dieser Seite
> http://www.mikrocontroller.net/articles/Soft-PWM nachzuvollziehen.

Schön.
Aber nächstes mal fang bitte einen neuen Thread an und hijacke nicht 
einen alten.

> Hier mein Code. "led_set" aktiviert 8 LEDs mit Hilfe eines übergebenen
> Bitmusters.

Und die Funktion sieht wie aus?

von Karl H. (kbuchegg)


Lesenswert?

Das hier

  TCCR1B |= (1 << CS01);
  TIMSK1 |= (1 << OCIE1A);


schaltet offenbar den Timer 1 ein.

Das hier

ISR(TIMER0_COMPA_vect) {

ist die Service Routine für den Timer 0.

Was passt da nicht zusammen?

Hinweis: Ehe man kompliziertere Dinge in eine ISR packt, ist es oft 
hilfreich in einer ISR einfach nur eine LED einzuschalten um zu sehen ob 
sie überhaupt aufgerufen wird.

von Frosch (Gast)


Lesenswert?

> 1000 % CPU-Last?

Seit wann hats denn 10-Kern-AVRs?

von Marcel H. (multiholle)


Lesenswert?

Karl heinz Buchegger schrieb:
> Schön.
> Aber nächstes mal fang bitte einen neuen Thread an und hijacke nicht
> einen alten.

Sorry, ich dachte es passt. Weil ich oben ja auf das Tutorial verwiesen 
wurden.

Karl heinz Buchegger schrieb:
> Das hier
>
>   TCCR1B |= (1 << CS01);
>   TIMSK1 |= (1 << OCIE1A);
>
>
> schaltet offenbar den Timer 1 ein.
>
> Das hier
>
> ISR(TIMER0_COMPA_vect) {
>
> ist die Service Routine für den Timer 0.
>
> Was passt da nicht zusammen?

Danke :) Manchmal braucht man wirklich nur mal kurz jemanden der einen 
Wachrüttelt, wenn man zu lange programmiert.

von Karl H. (kbuchegg)


Lesenswert?

Marcel Holle schrieb:
> Karl heinz Buchegger schrieb:
>> Schön.
>> Aber nächstes mal fang bitte einen neuen Thread an und hijacke nicht
>> einen alten.
>
> Sorry, ich dachte es passt. Weil ich oben ja auf das Tutorial verwiesen
> wurden.

Nur dann, wenn du 100% exakt genau dasselbe Problem hast, inklusive 
identischem Code.
Im Zweifel lieber neu anfangen.

Bei dir ist es, dass alles was vor dir steht, für dein Problem völlig 
irrelevant ist. Wenn der Verweis auf einen Artikel schon reichen würde, 
dann hätten wir hier im Forum vielleicht 15 bis 20 Monsterthreads in 
denen sich fast alles abspielt.

Oft merkt man nämlich nicht, dass jetzt ein anderer Fragesteller an der 
Reihe ist und dann studiert man vorhergehenden Beitrag um vorhergehenden 
Beitrag um dann auf Detailfragen zu antworten, die schon monatelang 
keinen mehr interessieren.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.