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;
}

20.04.2013

Źródła PK bez instalacji MSI

Kod budzi się po zimowym letargu. Wiosna pozytywnie wpływa na chęci, a i czasu jest trochę więcej. Na początek coś prostego dla wprawy.

Często zachodzi potrzeba użycia czystych źródeł z różnych wersji PK. Jak je uzyskać z oryginalnych instalatorów, ale bez instalacji? Uruchamiamy msiexec w trybie instalacji administracyjnej na przykład poleceniem:

msiexec /a plik.msi /qb TARGETDIR=katalog

Przykład. Aby uzyskać źródła Porting Kit 4.2 z pliku MicroFrameworkPK.msi wykonujemy polecenie:

msiexec /a MicroFrameworkPK.MSI /qb TARGETDIR=c:\

Pliki zostaną wypakowane do katalogu c:\MicroFrameworkPK_v4_2

14.12.2012

Karta SD. Przygotowanie i kompilacja PK NET MF

Jak połączenia już mamy zrobione to teraz trzeba przygotować odpowiednio porting kit i skompilować. Aha. Kartę SD wcześniej formatujemy z życiem FAT32. Możemy też utworzyć na niej jakieś pliki. Najlepiej tekstowe. Kartę od razu wkładamy w holder. Przykład modyfikacji i kompilacji dla .NET Micro Framework PK 4.2 QFE2. W innej wersji PK powinno być podobnie.

Uruchamiamy SolutionWizard. Do czego służy i mniej więcej jak korzystać pokazałem wcześniej. Wybieramy modyfikację projektu Discovery4 i nic nie zmieniamy, aż do ekranu "Feature Selection". Tutaj zaznaczamy: "FileSystem" i "SD". A na następnym ekranie "Generate Template" dla tych dwóch nowych funkcjonalności. Dalej standardowo.

SolutionWizard- add FSSolutionWizard - add SD Card
 SolutionWizard - generate template

SolutionWizard zrobił dwie rzeczy w katalogu naszej solucji (C:\MicroFrameworkPK_v4_2\Solutions\Discovery4). Zmodyfikował plik TinyCLR\TinyCLR.proj oraz utworzył katalogi: DeviceCode\FileSystem_Config_PAL i DeviceCode\SD_Config_HAL. W pliku TinyCLR.proj dodał funkcjonalności: Stream, FileSystem i SD oraz odpowiednie drivery: Stream, SPOT_IO, fs_pal, SD_BL, FS_FAT, FileSystem_Config_PAL_Discovery4, SD_Config_HAL_Discovery4. Wywalił zaś zaślepki fs_pal_stubs i FS_Config_stubs. Nas tak naprawdę interesują dodane katalogi.

Katalog DeviceCode\SD_Config_HAL zawiera dwa pliki: proj i cpp. Plików proj nie ruszamy. Plik cpp zawiera konfigurację drivera SD. Czyli np. gdzie jest podłączona karta (który SPI), z jaka częstotliwością odbywa się komunikacja, który pin to CS (ChipSelect) itp. Wprowadzamy tam modyfikacje.

W linii 30 dodajemy:

#pragma arm section rwdata = "g_SD_DeviceRegisters"

Tak aby cały ten blok pragma wyglądał tak:

#if defined(ADS_LINKER_BUG__NOT_ALL_UNUSED_VARIABLES_ARE_REMOVED)
#pragma arm section rwdata = "g_SD_BS_Config"
#pragma arm section rwdata = "g_SD_DeviceRegisters"
#endif

W linii 81 (po definicji struct SD_BLOCK_CONFIG g_SD_BS_Config) dodajemy:

struct SD_DEVICE_REGISTERS g_SD_DeviceRegisters;

Tak aby cały blok definicji struct wyglądał tak:

struct SD_BLOCK_CONFIG g_SD_BS_Config =
{
    {
        SD_WP_GPIO_PIN,            // GPIO_PIN             Pin;
        SD_WP_ACTIVE,              // BOOL                 ActiveState;
    },

     &g_SD_DeviceInfo,             // BlockDeviceinfo
};

struct SD_DEVICE_REGISTERS g_SD_DeviceRegisters;

W linii 83 zaraz po definicjach struct zmieniamy pin z GPIO_PIN_NONE na PB12(dziesiętnie = 28), SD_MSK_SAMPLE_EDGE na TRUE (dane na zboczu narastającym zegara), częstotliwość zegara, oraz z którego interfejsu SPI ma korzystać (SPI1=0, SPI2=1, SPI3=2).

#define SD_CS                    (GPIO_PIN)28 // PB12
#define SD_MSK_SAMPLE_EDGE       TRUE // important
#define SD_CLOCK_RATE_KHZ        400 //check 10000
#define SD_MODULE                1 // SPI2

Tak aby cała konfiguracja wyglądała tak:

#define SD_CS                    (GPIO_PIN)28 // PB12
#define SD_CS_ACTIVE             FALSE
#define SD_MSK_IDLE              TRUE
#define SD_MSK_SAMPLE_EDGE       TRUE // important
#define SD_16BIT_OP              FALSE
#define SD_CLOCK_RATE_KHZ        400 //check 10000
#define SD_CS_SETUP_USEC         0
#define SD_CS_HOLD_USEC          0
#define SD_MODULE                1 // SPI2
#define SD_INSERT_ISR_PIN        GPIO_PIN_NONE
#define SD_EJECT_ISR_PIN         GPIO_PIN_NONE
#define SD_LOW_VOLTAGE_FLAG      FALSE

Częstotliwość zegara można zostawić na 400 kHz lub spróbować wyższą. U mnie karta chodzi bez problemów z częstotliwością 10000 kHz. Dodatkowo dla bezpieczeństwa można ustawić SD_CS_SETUP_USEC i SD_CS_HOLD_USEC na 1.

Katalog DeviceCode\FileSystem_Config_PAL również zawiera dwa pliki. I tak jak poprzednio modyfikujemy tylko cpp. Najpierw dodajemy obsługę FAT32.

W linii 10 po definicjach pragma dodajemy:

extern FILESYSTEM_DRIVER_INTERFACE g_FAT32_FILE_SYSTEM_DriverInterface;
extern STREAM_DRIVER_INTERFACE g_FAT32_STREAM_DriverInterface;

W linii 23 i 28 zmieniamy g_AvailableFSInterfaces i g_InstalledFSCount:

FILESYSTEM_INTERFACES g_AvailableFSInterfaces[] =
{
    { &g_FAT32_FILE_SYSTEM_DriverInterface, &g_FAT32_STREAM_DriverInterface },
};

const size_t g_InstalledFSCount = 1;


To nie wszystko. Trzeba jeszcze dodać kod odpowiedzialny za jej podmontowanie. Inaczej nie będziemy jej widzieli w .NET MF.

W linii 12 po dodanych liniach extern dodajemy:

extern struct BlockStorageDevice  g_SD_BS;
extern struct IBlockStorageDevice g_SD_BS_DeviceTable;
extern struct BLOCK_CONFIG        g_SD_BS_Config;

A w linii 23 ciało procedury FS_MountRemovableVolumes:

void FS_MountRemovableVolumes()
{
 if (BlockStorageList::AddDevice( &g_SD_BS, &g_SD_BS_DeviceTable, &g_SD_BS_Config, TRUE ));
 {
  FS_MountVolume( "SD", 0, 0, &g_SD_BS );  
 }
}

Uff. Cały plik FS_config_Discovery4.cpp ma wyglądać tak:

#include <tinyhal.h>

#if defined(ADS_LINKER_BUG__NOT_ALL_UNUSED_VARIABLES_ARE_REMOVED)
#pragma arm section rwdata = "g_AvailableFSInterfaces"
#endif

extern FILESYSTEM_DRIVER_INTERFACE g_FAT32_FILE_SYSTEM_DriverInterface;
extern STREAM_DRIVER_INTERFACE g_FAT32_STREAM_DriverInterface;

extern struct BlockStorageDevice  g_SD_BS;
extern struct IBlockStorageDevice g_SD_BS_DeviceTable;
extern struct BLOCK_CONFIG        g_SD_BS_Config;

void FS_AddVolumes()
{
}

void FS_MountRemovableVolumes()
{
 if (BlockStorageList::AddDevice( &g_SD_BS, &g_SD_BS_DeviceTable, &g_SD_BS_Config, TRUE ));
 {
  FS_MountVolume( "SD", 0, 0, &g_SD_BS );  
 }
}

FILESYSTEM_INTERFACES g_AvailableFSInterfaces[] =
{
    { &g_FAT32_FILE_SYSTEM_DriverInterface, &g_FAT32_STREAM_DriverInterface },
};
const size_t g_InstalledFSCount = 1;

#if defined(ADS_LINKER_BUG__NOT_ALL_UNUSED_VARIABLES_ARE_REMOVED)
#pragma arm section rwdata
#endif

Kompilujemy zgodnie z tym co napisałem wcześniej: Kompilacja Porting Kit dla STM32F4Discovery. Tutaj mała uwaga. Można śmiało poprawiać pliki i  kompilować bez usuwania katalogu C:\MicroFrameworkPK_v4_2\BuildOutput za każdym razem. Wówczas kompilacja przebiega znacznie szybciej. W wyniku kompilacji otrzymujemy pliki ER_CONFIG + ER_FLASH do wgrania tradycyjnie za pomocą MFDeploy.

Po zaprogramowaniu STM32F4Discovery kolejna próba. Uruchamiamy program napisany kilka postów wcześniej. Program normalnie się uruchomi, a VolumeInfo będzie jak na obrazku poniżej.


SD Card in NET MF on STM32F4Discovery

13.12.2012

Karta SD. Podłączenie sprzętu

Kartę SD będziemy podłączać do STM32F4Discovery przez interfejs SPI. Wyprowadzeń SD card nie będę rozpisywał, bo można sobie popatrzeć na wikipedi jak to wygląda. Przechodzimy do sedna. Użyjemy następujących wyprowadzeń karty:
  • 1 - CS (Card Select)
  • 2 - MOSI (Data IN)
  • 3 - GND (Ground)
  • 4 - VCC (Power)
  • 5 - SCL (Clock)
  • 6 - GND (Ground)
  • 7 - MISO(Data Out)
SD card + STM32F4DiscoveryPodłączanie karty na pająka czy lutowanie bezpośrednio do styków karty pozostawiam hardkorowcom. Ja użyłem holdera do karty SD kupionego za grosze. Przylutowałem do golpinów wyprowadzenia holdera i do testów powinno wystarczyć. Zastosowanie holdera ma jeszcze jedną zaletę. Przez specjalne ustawienie styków łączących, podczas wkładania i wyciągania karty, podłączanie i odłączanie zasilania i sygnałów danych odbywa się w odpowiedniej, bezpiecznej kolejności . 

Jak mowa o zasilaniu to miałem pewne obawy czy napięcie +3V (takie VDD ma płytka STM32F4 Discovery) wystarczy. Wszędzie piszą o zasilaniu z +3.3V. Popatrzyłem więc do dokumentacji SanDiska. Oni dopuszczają napięcie zasilające w zakresie: od +2.7V do +3.6V. Teraz z doświadczenia już wiem, że +3.0V w zupełności wystarczy. A używam karty Kongstona 128MB.

Ostatnie dwa piny holdera na razie nie są wykorzystywane. Przedostatni pin podłączony jest do styków sygnalizujących obecność karty, a ostatni do styków zabezpieczenia przed zapisem. W kartach SD to programista musi zadbać o rozpoznanie stanu blokady zapisu poprzez ten pin i odpowiednią jego obsługę.

Jak już wcześniej wspomniałem będziemy używać interfejsu SPI. Płytka STM32F4Discovery ma 3 interfejsy SPI. Piny drugiego z kolei portu: SPI2, są wolne więc użyjemy ich: PB13-SCL, PB14-MISO, PB15-MOSI, oraz PB12 jako CS. Schemat podłączenia poniżej:

SD card + NET MF + STM32F4Discovery


SD card + NET MF + STM32F4Discovery

12.12.2012

Karta SD w .NET MF na STM32F4Discovery

No oczywiście obsługi karty SD (SD card) nie mamy w porcie .NET Micro Framework dla STM32F4 Discovery. Sprawdzić to można prostym programem. Najpierw pobieramy obsługiwane systemy plików, a następnie odpytujemy o dostępne zasoby (wolumeny). Kot wygląda tak:

using Microsoft.SPOT;
using Microsoft.SPOT.IO;

public class Program
{
    public static void Main()
    {
        string[] availFs = VolumeInfo.GetFileSystems();
        if (availFs.Length == 0)
        {
            Debug.Print("No FS found");
            return;
        }

        foreach (string fs in availFs)
            Debug.Print("Available FS: " + fs);

        VolumeInfo[] volumes = VolumeInfo.GetVolumes();
        if(volumes.Length == 0)
        {
            Debug.Print("No volumes found");
            return;
        }

        foreach (VolumeInfo volume in volumes)
            Debug.Print("Volume: " + volume.Name);
    }
}

W wyniku dostaniemy wyjątek:

An unhandled exception of type 'System.NotSupportedException' occurred in Program.exe

Czyli na 100% nie mamy obsługi żadnego FAT, a co za tym idzie karty SD, w porcie NET MF dla STM32F4Discovery. Trzeba dodać FS i SD do solucji. 

Przez chwilę myślałem, że się nie da i miałem zrezygnować, ale teraz mogę powiedzieć, że karta SD działa w NET MF na STM32F4 Discovery poprawnie. I dodanie jej wcale nie jest trudne. Największym problemem dla takiego lamera jak ja było rozgryzienie co i jak trzeba poupychać w solucji.


8.12.2012

Brak komunikacji z VS (zdechły kot)

Czasami może się zdarzyć, że coś sknocimy w naszym programie i Visual Studio nie będzie się mógł połączyć z płytką STM32F4 Discovery. Mnie się to zdarzyło kilka razy podczas zabaw z portem USB. 

Co wówczas robimy? Wciskamy na płytce przycisk user button (to ten niebieski), przytrzymujemy go i naciskamy przycisk resetu (to ten czarny). Puszczamy oba przyciski. Powinny zapalić się trzy diody na płytce (bez niebieskiej). Oznacza to, że uruchomił się jedynie tinybooter, a nie nasz program. Teraz odpalamy MFDeploy i przyciskiem Erase usuwamy deployment. Proste?

7.12.2012

Nowa wersja .NET MF

Kilka dni temu pojawił się .NET MF 4.3 (RTM). Zawiera wszystkie funkcjonalności i poprawki z wersji .NET MF 4.2 QFE.

Najważniejsze zmiany to:
  • przystosowanie do Visual Studio 2012
  • zmniejszenie czasu uruchamiania
  • skrót do MFDeploy w menu start
  • poprawka funkcji ToString("d4")
  • wynik funkcji ToBase64String zgodny z wersją .NET na PC
  • poprawki driverów sieci i usb
  • dodanie do ExtendedWeakReferences funkcji Flush
Przystosowanie do Visual Studio 2012 w praktyce oznacza, że wersja 4.3 nie będzie już dostępna dla Visual Studio 2010.