5.05.2013

PWM

Dzisiaj postanowiłem się pobawić PWM. W PWM chodzi o to, że sterujemy wypełnieniem sygnału prostokątnego przy stałej częstotliwości tego sygnału. Jak to wygląda na obrazkach można zobaczyć tutaj: http://arduino.cc/en/Tutorial/PWM. Na płytce STM32F4Discovery mamy dostępne 8 wyjść PWM:

PWMChannel.PWM_0: PD12 //GREEN LED
PWMChannel.PWM_1: PD13 //ORANGE LED

PWMChannel.PWM_2: PD14 //RED LED
PWMChannel.PWM_3: PD15 //BLUE LED
PWMChannel.PWM_4: PE9
PWMChannel.PWM_5: PE11
PWMChannel.PWM_6: PE13
PWMChannel.PWM_7: PE14


Najlepsze jest to, że pierwsze cztery wyjścia kanałów PWM to diody znajdujące się na płytce, więc aby zobaczyć jakieś efekty nic nie musimy podłączać.

Częstotliwość sygnału i jego wypełnienie możemy regulować na dwa sposoby: ustawiając częstotliwość (właściwość Frequency) i procentowe wypełnienie (właściwość DutyCycle) lub czas jednego okresu sygnału (właściwość Period) i czas trwania stanu wysokiego (właściwość Duration). Tutaj mała uwaga. Ustawiając DytyCycle wpisujemy procenty w postaci ułamka czyli jeśli chcemy 21% to ustawiamy 0.21. Natomiast ustawiając Period i Duration jednostkę wskazujemy we właściwości Scale (nano, mili, mikro sekundy). Przykład - dioda zielona mruga raz na sekundę:

var pwm0 = new PWM(Cpu.PWMChannel.PWM_0, 1, 0.5, false);
pwm0.Start();

No dobra, a czy można równocześnie ustawić czerwoną diodę żeby mrugała 2 razy szybciej? Chyba nic prostszego:

var pwm0 = new PWM(Cpu.PWMChannel.PWM_0, 1, 0.5, false);
pwm0.Start();

var pwm2 = new PWM(Cpu.PWMChannel.PWM_2, 2, 0.5, false);
pwm2.Start();

I co? Kicha! Zielona zaczęła mrugać tak samo jak czerwona. Popatrzymy do pliku platform_selector.h w katalogu Solutions\Discovery4 PK:

#define STM32F4_PWM_TIMER {4,4,4,4,1,1,1,1}
#define STM32F4_PWM_CHNL  {0,1,2,3,0,1,2,3}
#define STM32F4_PWM_PINS  {60,61,62,63,73,75,77,78} // D12-D15,E9,E11,E13,E14

Okazuje się, że pierwsze 4 wyjścia PWM obsługuje ten sam timer (cztery kolejne zresztą też, ale inny). Jeśli jest to ten sam timer to wszystkie wyjścia PWM działają z takim samym okresem. Przestawienie okresu (nie ważne czy robi się to przez właściwość Period czy Frequency) na inną wartość dla jednego wyjścia będzie miało wpływ na pozostałe kanały. Dodatkowo trzeba pamiętać, że DutyCycle nie pozostanie stałe przy zmianie częstotliwości. Czyli jeśli chcemy, aby wypełnienie było stałe na poziomie 50% to zmieniając częstotliwość trzeba równocześnie przypisać wartość 0.5 do DutyCycle. Trzeba o tym pamiętać szczególnie w pętlach, gdzie zmieniana jest częstotliwość lub okres. (http://netmf.codeplex.com/workitem/1749)


Jednym z zastosowań PWM jest regulacja jasności świecenia diody LED. Małe wypełnienie - dioda słabo świeci, duże wypełnienie - doda jasno świeci. Może to być wykorzystane do regulacji podświetlenia w wyświetlaczach. Prosty programik, który rozjaśnia i ściemnia diodę czerwoną:

public static void Main()
{
    var pwm2 = new PWM(Cpu.PWMChannel.PWM_2, 300, 0, false);
    pwm2.Start();

    const int minBright = 0;
    const int maxBright = 100;
    int bright = minBright;
    int step = 1;

    while (true)
    {
        pwm2.DutyCycle = ToDutyCycle(bright);

        bright += step;
        if (bright > maxBright || bright < minBright)
        {
            bright = bright > maxBright ? maxBright : minBright;
            step = -step;

            Thread.Sleep(1000);
        }

        Thread.Sleep(40);
    }
}

private static double ToDutyCycle(double brightness)
{
    return brightness/100;
}

Jak widać kot nie jest skomplikowany. Przelatujemy w pętli od 0 do 100 (to jasność) i przypisujemy wprost proporcjonalnie wypełnienie (dzielimy przez 100, aby otrzymać wartości w procentach: 0..1). Migotanie diody zostało zniwelowane poprzez ustawienie częstotliwości na 300 Hz tak, aby wykorzystać bezwładność oka ludzkiego. Przy mniejszych wartościach niektórzy ludzie mogą dostrzegać migotanie. Program jednak nie działa tak jak trzeba. Dioda bardzo szybko się rozjaśnia i bardzo jasno świeci przez większość czasu. Winę za to ponosi nieliniowa charakterystyka oka ludzkiego. Dlatego musimy skorygować wypełnienie.

Spotkałem dwie "szkoły" takiej korekcji. Jedna używa funkcji wykładniczej o podstawie e (podstawa logarytmu naturalnego), a druga funkcji wykładniczej o podstawie 10 (podstawa logarytmu dziesiętnego). Gdzieś wyczytałem, że oko ludzkie ma charakterystykę logarytmiczną o podstawie 10 więc druga może jest bardziej adekwatna. W praktyce moje oko nie zauważyło różnicy między nimi. Wykres poniżej przedstawia obie funkcje:
pwm brightness dimming curve
A tak trzeba je zapisać w kocie:
private static double ToDutyCycleExp(double brightness)
{
    if (brightness < 1)
        return 0;

    if (brightness > 99)
        return 1;

    return (0.383*System.Math.Exp(0.0555*brightness) - 0.196)/100;
}

private static double ToDutyCycle10(double brightness)
{
    if (brightness < 1)
        return 0;

    if (brightness > 99)
        return 1;

    return System.Math.Pow(10, (2.55*brightness - 1)/84.33 - 1)/100;
}

Brak komentarzy:

Prześlij komentarz